# Recording → capture → edit

This is the production handoff for the existing Remotion project. It reuses
`RecordingClock`, `alignToRecording`, the Trail bitmap/camera adaptation, the
bundled fonts, and the OBS worksheet. It is not another editor or playback
synchronizer. Read `RECORDING_ALIGNMENT.md`, `ASSET_ADAPTATION.md` and the
relevant `RECIPE_GEOMETRY.md` entries alongside it.

**Boundary:** preserve the supplied talking head outside `public/`. OBS plays
that recording, including its only narration. Google Slides plays the separate
silent presentation. No compositing, replacement voice, duplicated narration,
or default music. Real production rendering requires the single consolidated
storyboard/demo approval. A synthetic-only pass never establishes Poe behavior,
human approval, or actual OBS/Slides/Zoom readiness.

## 1. Prepare a supported local environment

The tested profiles are Linux x86-64, Node 24.13.1, CPython 3.14.7 for media
checks, and CPython 3.13 for the optional local ASR adapter. Other platforms are
untested. FFmpeg/ffprobe with libx264 and a working Chrome/Chromium installation
are explicit prerequisites. `HOST_SETUP.md` records the browser library and
network-interface failure modes encountered on the test host.

```bash
npm ci
npm run typecheck
npm test
python3.14 -m venv .media-env
.media-env/bin/python -m pip install --require-hashes -r requirements-linux-py314.lock
python3 scripts/test-media.py
export PRODUCTION_PYTHON="$PWD/.media-env/bin/python"
export REMOTION_BROWSER_EXECUTABLE=/path/to/working/chrome-headless-shell
```

Initial dependency/browser/model provisioning can require internet. No source
media, transcript, capture, or composition is uploaded by the included tools.
To reproduce offline, prefetch the locked wheels, `npm ci` dependencies and
browser/model, then use pip `--no-index --find-links wheelhouse --require-hashes`.
The original study's six-distribution lock is retained; the Remotion dependency
lock is extended only for the optional browser capture/playback adapter.

## 2. Inspect the recording before selecting recipes

Put the supplied file under `private/`, not under the served asset directory.
The inspector hashes it, records actual stream metadata, compares decoded PCM
tracks, and creates an untrimmed 16 kHz mono analysis derivative. Duplicate
tracks do not imply multiple speakers. If distinct tracks exist, it refuses to
guess. Inspect them and repeat with an explicit stream index:

```bash
python3 scripts/media.py --report private/inspect-run.json inspect \
  --source private/recording.mp4 --out private/intake --audio-stream 1
```

The selected stream's original offset relative to the container start is
retained. ASR time on `analysis.wav` is mapped back by that offset; do not trim
leading silence and then call the new clock original recording time.

For optional local transcription, provision a supported faster-whisper model
directory through your authorized tooling. The adapter never downloads one:

```bash
python3.13 -m venv .asr-env
.asr-env/bin/python -m pip install --require-hashes -r requirements-asr-linux-py313.lock
.asr-env/bin/python scripts/transcribe.py --audio private/intake/analysis.wav \
  --model /path/to/local/model --out private/raw-asr.json
python3 scripts/media.py --report private/transcript-run.json import-asr \
  --intake private/intake/intake.json --asr private/raw-asr.json \
  --out private/transcript.json --engine faster-whisper-1.2.1 \
  --revision MODEL_REVISION --proper-nouns PRODUCT_NAME
```

Another authorized ASR tool may supply `segments[].words[]` with `word`, `start`,
`end`, and optional `probability`, relative to the analysis WAV. Retain its raw
file, engine/revision and command. The supplied adapter records every model
file hash, parameters and software version. Do not describe estimated word
decimals as human-certified timing or infer measured vocal stress from meaning.

The importer flags words whose first 120 ms fall below a **triage** acoustic
threshold. The default is −35 dBFS over 20 ms windows, adjustable at inspection;
it is not a universal silence detector. Keep the raw times. Add review decisions
under `review_decisions` with `uncertainty_id`, `status` (`resolved` or
`accepted-uncertain`), `reason`, and `evidence`. For real production also require
`human_verified: true` after that review. Do not delete uncertain words.

Watch the source's actual gesture and ending. Retain `tail-observations.json`
with `source_sha256`, `evidence_kind: "visual-inspection"`, `start_seconds`,
`end_seconds`, and `observations`. Noisy but calm footage can be usable; an
audio silence threshold cannot establish a wave or pose. The worked fixture
has a raw first-word estimate at 0 although its acoustic onset is near 0.9 s;
it also misrecognizes a name and a number. Those uncertainties remain visible.

## 3. Derive the review packet

Use `examples/transfer/packet.json` as a complete worked input, not as a timing
preset. `src/production.ts` is the typed contract and executable validator.
The `schemaVersion` is 1. Paths in a packet are relative to the downloaded
project root; every input is bound by SHA-256. Generate a binding with
`sha256sum relative/file` or Python's `hashlib.file_digest`.

- `recording`, `intake`, `transcript`: immutable input bindings.
- `timing.worksheet`: the existing OBS `timing-private.json`, schema 1 with
  seconds `C,H,T,E,L,P,B,R1,R2,R3`; no second worksheet is introduced.
  `nominalReactionSeconds` lies within the observed range. `evidenceKind` is
  `synthetic-fixture` or `measured-rehearsal`, never implied by decimal precision.
- `timing.tailObservation`: the source-bound visual record. In production,
  `rehearsalEvidence` additionally binds a record containing `kind:
  "measured-rehearsal"`, `setup`, and at least three `trials` with
  `reaction_seconds`. These are claims to audit against the real rehearsal,
  not measurements manufactured by this software.
- `identity` and `exportProfile`: original title/label/accent and explicit
  resolution/rate/format/color/no-audio settings. The implemented study profiles
  are 1280 or 1920 pixels wide, 16:9, 24/30/60 fps, H.264/yuv420p/MP4/BT.709.
  Supported encoding is not approval of a real presentation profile.
- `captures`: manifest binding, stable `objectId`, actual settled-result time,
  authorized runtime/version/instructions, expected/actual outcome and evidence.
  The canonical `expected` and `actual` outcome strings must agree; a mismatch
  is a content/capture review conflict, not permission to rewrite the narration.
  `unavailable` or `failed` stops the handoff. Verify real Poe against its current
  checkout instructions, exports, runtime and authorized data; no access means
  a precise conflict report, not invented proof.
  If one screen recording contains several verified results, separate capture
  IDs may reference that same manifest. Give each result its own settlement
  time and outcome evidence; retain the same `objectId` when it is the same
  object. A later result must not borrow an earlier result's proof claim.
- `beats`: recording phrase bounds and proof anchor; reference URL, inspected
  source interval, reason and contrasting alternative; proposed motion; explicit
  capture-time map; complete proof and protected reading frames.

For each `segments` entry, `fromFrame/endFrame` are half-open local output
frames. `mode: "play"` advances capture time at 1× from `sourceStartSeconds`;
`mode: "hold"` uses a specific already captured frame. Omitted waits require
an exact gap in `omissions` with `afterLocalFrame`, source start/end, reason,
and a visible `disclosure`. The adapter renders that disclosure for two seconds.
Do not accelerate an actual interaction or reset an object's identity silently.

Choose `motion.kind` from `hold`, `cut`, or `push`; `actionFrame`, `changeFrame`,
`zoom`, `focusX/Y` use the existing full-frame camera geometry. Motion must end
before proof. `evidenceKind: "proposed-edit"` distinguishes these choices from
`reference.evidenceKind: "source-observation"`. Source timestamps and easing
values are not interchangeable evidence.

First anchor completed proof to a recorded phrase, then work backward. Keep
native product motion when it communicates the action; do not add a camera
move to every shot. Account for actual capture waits and reading time. The
provided reading budget is a proposed floor to test with a reader at meeting
size, not a guarantee. If necessary, omit a documented wait, shorten surplus
holds, choose a cut, or bring a conflict to review. Do not rewrite fixed speech.

## 4. Capture real behavior and import encoded PTS

Use the product-native collaborative browser when available. If it explicitly
reports unavailable, the included optional adapter can record a permitted local
or authenticated browser surface. It uses real browser actions, never generated
result graphics. Credentials/account setup must be authorized separately; the
example does not grant access or make external writes.

```bash
node scripts/capture.mjs --url http://127.0.0.1:8765/demo.html \
  --actions examples/transfer/capture-actions.json --out private/capture \
  --browser "$REMOTION_BROWSER_EXECUTABLE" --width 1920 --height 1080
python3 scripts/media.py --report private/capture-import.json capture \
  --source private/capture/capture.webm --out public/captures/demo \
  --kind native-recording --useful-region 0 0 1920 1080
```

The action list supports `click`, `select`, `fill`, `wait`, and `assert-text`;
see the worked list for the exact fields. Preserve the action ledger and actual
result. **Its wall times are not encoded PTS.** Import the actual file, inspect
decoded frames around initiation/arrival/settlement, and enter capture-relative
seconds from `capture.json`. Every decoded frame is hashed. Native browser
capture here has measured 25 fps cadence; replay holds each captured frame until
the next PTS. This is not a claim of 30/60 fps native capture.

`--useful-region x y width height` is the inspected useful rectangle, not the
container dimensions by default. Check the moving capture for letterboxing,
browser UI, downscaled content and interpolation. Useful width/height must cover
output dimensions times maximum zoom. If not, recapture closer or reduce the
crop. The supplied adapter cannot make padded low-resolution content sharper.

## 5. One review, then the checked render

Consolidate the timed storyboard, demo plan/results, transcript decisions,
reference choices, original identity, capture map/omissions, cue, measured
range, usable tail, export profile and unresolved constraints into the single
review. `check` validates the packet but is not approval:

```bash
node scripts/production.mjs check private/packet.json out/review-check
node scripts/production.mjs digest private/packet.json
```

The digest covers all packet fields except `approval`, including input hashes.
For approved real work set `approval.state: "approved"`, `planSha256` to that
digest, and `humanEvidence` to the retained authorization's path/hash. Do not
author the human's approval yourself. A material packet change invalidates it.
Synthetic studies instead use `mode: "synthetic-study"` and `approval.state:
"synthetic-only"`; they cannot be represented as production-ready.

```bash
node scripts/production.mjs render private/packet.json out/new-render
```

Use a new output directory. The command validates all source/capture bindings,
imports the OBS clock into `alignToRecording`, compiles the explicit edit,
renders with the existing camera/font system, encodes, probes the actual movie,
fully decodes it, compares every encoded frame with the rendered reference,
and rechecks source hashes. It writes a fresh `.run.json` status even on failure.
The temporary served render assets contain only referenced capture frames and
fonts, never the talking-head recording or transcript.

The output includes `compiled-edit.json`, expected `frames/`, `presentation.mp4`,
actual `encoded-check.json`, and `pairing.json`. The encoder applies the adopted
SDR study conversion and explicit H.264 color metadata; the checker requires
actual BT.709 matrix, range, primaries and transfer fields. Flags/sidecars alone
are not a pass. Never re-tag unknown third-party footage as if it were converted.

Every encoded frame, including motion, joins and protected holds, is compared
at its actual PTS. Declared tolerance is mean RGB error below 4/255 and every
64×64 tile below 18/255. This is a lossy-encoding tolerance, not a perceptual
guarantee or a detector for every possible one-pixel error. Missing reference
frames, added audio, incorrect cadence/geometry/color or corrupt middle frames
fail. A renderer can faithfully render a bad editorial plan; inspect that too.

## 6. Inspect moving output and deliver the pair

Serve only the intended local output directory. Do not serve a private project
root or publish source recordings. For the permitted synthetic example:

```bash
python3 -m http.server 8766 --bind 127.0.0.1 --directory out/new-render
node scripts/playback.mjs --url http://127.0.0.1:8766/presentation.mp4 \
  --out out/playback-640 --width 640 --height 360 --browser "$REMOTION_BROWSER_EXECUTABLE"
```

The playback check runs the entire encoded file at 1× without seeking and
records video-frame callbacks, elapsed/media clocks, displayed dimensions,
errors and frame drops. Repeat at the intended meeting size and inspect text,
action/result order, object identity, cut boundaries, cue and ending. Screenshots
are supplemental, not a substitute for moving playback. Automated playback is
not a human reading/comprehension check or an actual meeting rehearsal.

`pairing.json` identifies both files, nominal/observed start offsets, cue,
presentation end and minimum usable reserve. Rehearse the real OBS virtual
camera/virtual microphone and Slides/Zoom pair using the OBS runbook, with
single narration, audience-side lip-sync, presentation alignment, and manual
mute/video-off before usable tail end. Keep those gates **untested** until done.

## Worked transfer and limits

`examples/transfer/packet.json` is a 33 s Moss Ledger companion derived from its
35 s synthetic recording and actual fixture runtime. It retains raw ASR errors,
two identical audio tracks plus a silent track, low-level noise during the calm
tail, a genuine seven-second app wait, a 5.4 s declared omission, stable object
identity, a still-camera result beat and one later settled-detail push. The title
cuts to the actual app at 2 s. Invented synthetic reactions `.55/.65/.75` give a
2.55 s minimum usable reserve. The figure leaves its calm pose at 33 s even
though the file continues to 35 s. These are fixture facts, not real calibration.

Replay without changing core constructions:

```bash
python3 scripts/prepare-example.py
node scripts/production.mjs check examples/transfer/packet.json out/example-check
node scripts/production.mjs render examples/transfer/packet.json out/example-render
```

The download retains the encoded synthetic recording and capture, raw ASR and
intake records. It does not ship hundreds of redundant decoded PNGs. The first
command decodes the supplied capture locally and refreshes the packet's bindings;
it needs no network, private files or new editorial choices. Run it once in a
fresh extracted project. Do not count an unprepared example as a verified packet.

To recapture, serve `examples/transfer`, use its action list, import the new
capture and re-observe its PTS; do not reuse old wall-to-source times. Replace
the capture binding/map in a new packet. The main site includes the original
study verifier's bounded `--only` mode and corrupted-frame controls separately.
Real-product access, human review, and a real meeting remain untested by this
synthetic transfer.
