SOP — the video editor
Run a cut of an ad from footage to a Frame.io version. The tool is video-pipeline/; its architecture, stage list and gate list live in video-pipeline/README.md and are not repeated here. This file is the procedure.
Before anything
Render on the droplet, never on a laptop. root@188.166.225.209, key ~/.ssh/atlas_video_worker, code at /opt/atlas/video-pipeline. The droplet is not a git checkout; code gets there by rsync. The laptop disk has filled twice on video and the render is CPU-bound anyway.
Nothing Alex watches is a half-res or correction-only draft. He reads flatness and softness immediately. Drafts are for checking structure; anything shown to him goes through 11-finish.sh, which is full resolution with the look pass. The receipt records look and fullRes so this is checkable rather than claimed.
Frame.io is never deleted from. See ../rules/frameio-never-deletes.md. The client allows GET, POST, and PATCH for two narrow things. If a stray upload needs removing, leave it and say so.
Before you cut anything
These come from the OLOID post-mortem, where nine versions were abandoned. Each one cost a day. Full account: ../knowledge/oloid-post-mortem-what-the-editors-do.md.
- Open the reference before the footage. If the client has delivered work in the corpus, look at its contact sheet. If there is a delivered cut of the same script, look at that first. One frame of the editors' cut answered questions that two days of guessing did not. The 785-video library is the reference, not a deliverable.
- Use every footage folder you were given. Never exclude one in a log line. "Old" or "not maintained" describes the index, not the footage: on OLOID that judgement discarded 141 B-roll clips, 9 consultation clips and 22 person clips, and left a 23-clip pool that could not reach the standard.
- Never cover the speaker's head. A still that overlaps an A-roll segment gets a talent matte drawn back over it, so the photograph sits behind him. Stage 07 requests this;
MatteLayerdraws it. - Check who is speaking.
node tools/check-speaker.mjs <project>measures pitch per beat against the talent's baseline. A crew member reading a line to the talent matches the script word for word and passes every text check; she was 218 Hz against his 137. - Speed is per beat, not per read. Measure the speech rate of each beat and speed that beat. A single global rate tightened a hook that was already tight and left the body slow.
- Coverage and cut rate are separate decisions. "Cover more of the talking head" is not "cut faster". Ask what each shot is doing before setting a shot length.
- A rule you apply without judgement will produce a worse cut. Once-per-source is right, until it forces a cut-in out of a beat that simply needed a different shot length.
- Sound design is a layer, not a default.
assets/sfx/bank.jsoncatalogues 64 effects by the moment they serve: risers lead into a claim or reveal, hits land on a cut or a word, whooshes cover a picture change and must vary. The OLOID cut shipped twelve copies of one pop and nothing else. - Check the voice chain on a gap, not on the speech. Measure mean volume in a pause and during a phrase. If the separation is under about 15 dB the room tone is riding up and the ad will sound unclean however good the words are.
Checks that run before a render
| Check | Catches |
|---|---|
tools/check-speaker.mjs | a different speaker on a beat |
| script coverage, in stage 07 | the brief's words missing from screen |
| caption-not-in-brief, in stage 07 | transcription errors reaching the screen ("phone" for "home") |
| script-omission, in stage 07 | copy dropped from the brief without an approver |
| the post-render audio diff | crew talk or fluffs left in, and slate words |
The two commands
# structure check: 540x960, correction grade only, 20-40 s once conform is cached. Never shown to Alex.
bash stages/10-draft.sh <project> <tag>
# what Alex watches: build, conform, look, matte, proof, full-res render, master to -14 LUFS / -1 dBTP
bash stages/11-finish.sh <project> <tag>
Both are safe to re-run: every stage checks for its own artefact first, so the same command is the resume command. Neither touches Frame.io.
Delivering
Delivery is opt-in and separate, because it is the one outward-facing step:
bash stages/11-finish.sh <project> <tag> --deliver # new VERSION of the asset in decisions.deliverable
bash stages/11-finish.sh <project> <tag> --deliver --as-master "Name.mp4" # a separate delivery asset instead
Which of those two is right is Alex's call per project, not the agent's. Ask; do not pick.
The music beat grid is required
Cut placement is decided against the grid at assets/beat-grids/<track>.beats.json, produced by tools/beats.py. It is committed, because a missing grid used to be a warning that silently produced the same manifest with no cuts on the beat at all. A missing grid now fails the build and names the remedy. work/audio/ is the scratch copy and is read first; assets/beat-grids/ is the copy of record.
How cuts land on the beat, and why the window is asymmetric, is documented at the top of snapCut in stages/07-build-manifest.mjs. The short version: a cut may be pulled 0.22 s earlier onto a beat and only 0.06 s later, so the picture never arrives after the line that motivates it, and a cut with no beat inside that window keeps the time the meaning gave it.
What is in git and what is only on the droplet
| Thing | Where it lives | If it is lost |
|---|---|---|
| stages, tools, Remotion source, style cards | git | nothing to recover |
projects/<id>/ decisions, script, brief, b-roll library | git | the edit is gone; this is the product |
music .wav, sfx, generated assets, beat grids | git | nothing to recover |
| conform / look / matte / proxy caches | droplet only | regenerable, costs render time |
work/out/*.mp4 renders and masters | droplet only | regenerable from the manifest |
| corpus artefacts | git, see the corpus SOP | machine-hours and API spend |
The rule behind that table: anything a decision was made in goes to git. Anything a machine can rebuild from those decisions does not, because it is media and media does not belong on the laptop or in the repo.
Traps already paid for
pkill -f <pattern>matches the ssh command line and kills your own remote shell. Use the bracket trick ("[1]1-finish") and put the kill and the relaunch in separate ssh calls.- A render that logs
SIGKILLis not trustworthy even when it exits 0.11-finish.shchecks for this and fails; lowerRENDER_CONCURRENCYif it fires. - Never open a browser tab that plays audio while working on this. Alex has hunted for the sound on his own machine.
- Three whisper processes is the maximum on the 8 GB droplet, and only when nothing else is rendering.