video-editing 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +29 -0
- package/README.md +83 -0
- package/dist/assets/DejaVuSansMono.ttf +0 -0
- package/dist/chunk-63HIQBDT.js +4321 -0
- package/dist/chunk-CU3NBKB4.js +307 -0
- package/dist/cli.js +1594 -0
- package/dist/index.d.ts +3569 -0
- package/dist/index.js +300 -0
- package/dist/job-OFCSLPBV.js +25 -0
- package/dist/templates/author-edl.prompt.md +33 -0
- package/dist/templates/fix.prompt.md +23 -0
- package/dist/templates/review.prompt.md +30 -0
- package/package.json +69 -0
- package/skills/video-editing/SKILL.md +254 -0
- package/skills/video-editing/references/adversarial-review.md +44 -0
- package/skills/video-editing/references/cli-reference.md +181 -0
- package/skills/video-editing/references/editing-brief.md +44 -0
- package/skills/video-editing/references/hard-rules.md +38 -0
- package/skills/video-editing/references/scribe-collapse.md +37 -0
- package/skills/video-editing/references/timeline-v2-vocabulary.md +162 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: video-editing
|
|
3
|
+
description: >-
|
|
4
|
+
Edit a talking-head video reel into a tight, clean cut using ONLY the local
|
|
5
|
+
`video-editing` CLI — never the video-use repo, never the clean-cut skill,
|
|
6
|
+
never raw Python helpers or ffmpeg by hand. Removes bad retakes, false starts,
|
|
7
|
+
stutters and cut-off words (keep the LAST complete take, but keep the CLEANEST
|
|
8
|
+
take when an earlier one is clean and a later one stutters), tightens every
|
|
9
|
+
pause to ~0.13s, cold-opens on the strongest hook, trims outro dead air.
|
|
10
|
+
Vertical 9:16, clean cuts only (no grade/subtitles/overlays by default). Two
|
|
11
|
+
mandatory gates: SCREEN before cutting and CONTENT-VERIFY (re-transcribe the
|
|
12
|
+
rendered preview at every join) after. Use whenever the user drops a
|
|
13
|
+
talking-head clip / reel / vlog / interview and wants it edited or tightened —
|
|
14
|
+
phrasings like "edit this reel", "cut the bad retakes", "remove the false
|
|
15
|
+
starts", "clean up this recording", "tighten this reel", "tighten this", "I
|
|
16
|
+
flubbed some lines, fix it", "get rid of the dead air", "make a clean cut of
|
|
17
|
+
this", or just a video path with "make this cleaner". Handles Hebrew/RTL.
|
|
18
|
+
This is the single source of truth for reel edits; it supersedes and replaces
|
|
19
|
+
the clean-cut and video-use skills.
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Video Editing (CLI-only)
|
|
23
|
+
|
|
24
|
+
Turn a raw talking-head recording into a tight, clean vertical cut. The local
|
|
25
|
+
**`video-editing`** CLI is the single source of truth and owns ALL execution
|
|
26
|
+
(probe, transcribe, screen, bake master, render, QC, content-verify). **You**
|
|
27
|
+
read the transcript and author the EDL; the CLI does everything executable.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
Requirements: **Node ≥ 20**, **ffmpeg**/**ffprobe** on PATH, and an **ElevenLabs API key**
|
|
32
|
+
(`ELEVENLABS_API_KEY`) for transcription.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install -g video-editing # the CLI
|
|
36
|
+
npx skills add ItayElgazar/clipready --skill video-editing # this skill
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## HARD RULE — execution boundary (read first)
|
|
40
|
+
|
|
41
|
+
- Use **only** the `video-editing` CLI (`video-editing <subcommand>`). Run
|
|
42
|
+
`video-editing --help` to see commands.
|
|
43
|
+
- **NEVER** run ffmpeg/ffprobe/transcription by hand — the CLI owns all execution.
|
|
44
|
+
- If a capability seems missing, STOP and report it as a CLI gap. Do not reach around the CLI.
|
|
45
|
+
- Your only hand-authored artifact is `<job>/edl.json`.
|
|
46
|
+
|
|
47
|
+
## Prerequisites
|
|
48
|
+
|
|
49
|
+
- `video-editing` on PATH (`which video-editing`), plus `ffmpeg`/`ffprobe`.
|
|
50
|
+
- ElevenLabs key: the CLI owns it (env `ELEVENLABS_API_KEY` or `~/.config/video-editing/.env`;
|
|
51
|
+
set once with `video-editing config set-key <key>`). Transcription is cached per source
|
|
52
|
+
and is a paid call — never force a re-transcribe of an unchanged source.
|
|
53
|
+
|
|
54
|
+
## Workflow
|
|
55
|
+
|
|
56
|
+
1. **Init** — `video-editing init-job --source "<video>" --job-dir "/vercel/sandbox/jobs/<job_id>"`
|
|
57
|
+
→ job dir + `source.json`. Always pass `--job-dir` explicitly (there is no `~/Downloads` on
|
|
58
|
+
the sandbox); use the job dir given in the task prompt.
|
|
59
|
+
2. **Transcribe + screen** — `video-editing transcribe --job-dir "<job>"` → word-level
|
|
60
|
+
transcript + screening artifacts (`flags.txt`, `takes_packed.md`, `word_dump.txt`, `coverage.json`).
|
|
61
|
+
3. **GATE 1 — SCREEN before cutting** — read `<job>/flags.txt`: resolve every **[A]**
|
|
62
|
+
over-long/collapsed token (retakes hide here), judge every **[B]** repeated phrase
|
|
63
|
+
(retake vs intentional), note **[C]** gaps/audio-events. Confirm `<job>/coverage.json`
|
|
64
|
+
(last word within ~2s of duration). See `references/scribe-collapse.md`.
|
|
65
|
+
4. **Author the EDL** — read `<job>/word_dump.txt` (and `prompts/author-edl.prompt.md` if present),
|
|
66
|
+
then write `<job>/edl.json` per the brief below. To inspect any moment visually:
|
|
67
|
+
`video-editing inspect --job-dir "<job>" --video vertical_src --start <a> --end <b>`.
|
|
68
|
+
(Or let the CLI scaffold the prompt: `video-editing cut-bad-retakes --job-dir "<job>"`.)
|
|
69
|
+
5. **Render preview** — `video-editing render --job-dir "<job>" --mode preview`.
|
|
70
|
+
Render **auto-runs the amplitude silence-trim pass** (Gate 3) first: it scans the audio
|
|
71
|
+
with `silencedetect` and excises every sub-floor silence the transcript can't see —
|
|
72
|
+
pauses baked *inside* a stretched word token, room tone, breaths — then trims silent
|
|
73
|
+
range edges. Your authored `edl.json` is backed up to `edl.raw.json` and rewritten with
|
|
74
|
+
the tightened ranges (more ranges, shorter total). It is **transcript-aware**: it never
|
|
75
|
+
splits inside a single word token (spelled acronyms like "WWX", held vowels, numbers).
|
|
76
|
+
Tune or disable: `video-editing desilence --job-dir "<job>"` (flags below) /
|
|
77
|
+
`render … --no-desilence`.
|
|
78
|
+
6. **GATE 2 — CONTENT-VERIFY** — `video-editing verify --job-dir "<job>" --json`.
|
|
79
|
+
It re-transcribes the preview at every join (healing OFF) and flags duplicated phrases
|
|
80
|
+
across a join (= a retake left in) and over-long gaps (= dead air). Adjudicate with
|
|
81
|
+
`video-editing inspect --job-dir "<job>" --join <i>`. See `references/adversarial-review.md`.
|
|
82
|
+
7. **Fix → re-render → re-verify** until `verify` is clean (`references/fix` rules apply).
|
|
83
|
+
8. **Final** — `video-editing render --job-dir "<job>" --mode final`.
|
|
84
|
+
|
|
85
|
+
## The standing editing brief (apply exactly)
|
|
86
|
+
|
|
87
|
+
- Output **VERTICAL 9:16**, **CLEAN CUTS ONLY** (`grade:"none"`, no subtitles, no overlays).
|
|
88
|
+
- **CUT** bad retakes / false starts / cut-off / stuttered words — keep the **LAST complete take**,
|
|
89
|
+
BUT keep the **CLEANEST** take when an earlier take is clean and a later one stutters.
|
|
90
|
+
- **TIGHTEN every pause** to ~0.12–0.14s of air (remove dead air). The render's amplitude
|
|
91
|
+
pass (Gate 3) enforces this automatically — it cuts **every silence below the dB floor**
|
|
92
|
+
(default −36 dB, pauses ≥0.14s, leaving ~0.09s air). This catches the silences the
|
|
93
|
+
transcript misses (dead air baked inside a long word token, room tone, breaths). It runs
|
|
94
|
+
on every render; you don't hand-tighten silences in the EDL.
|
|
95
|
+
- **CUT ALL SILENCES below the dB floor** — for a more/less aggressive cut, tune the pass:
|
|
96
|
+
`video-editing desilence --job-dir "<job>" --noise-db -39 --min-silence 0.30` (gentler) or
|
|
97
|
+
`--noise-db -34 --min-silence 0.12 --lead-pad 0.03 --trail-pad 0.04` (harder). Re-render +
|
|
98
|
+
re-verify after. To start clean, restore `cp edl.raw.json edl.json` then re-run.
|
|
99
|
+
- **COLD-OPEN** on the strongest hook (trim weak/dead intro).
|
|
100
|
+
- **TRIM outro dead air** (trailing breaths/ambient/silence/device noise).
|
|
101
|
+
- **KEEP intentional rhetorical repeats.**
|
|
102
|
+
- Never cut inside a word — snap every edge to a transcript word boundary; pad cut edges
|
|
103
|
+
30–200ms (lead ~0.05s, trail ~0.08s; ~0.03–0.05s at retake joins, staying inside the silence).
|
|
104
|
+
The renderer adds 30ms audio fades, so tight pads are safe.
|
|
105
|
+
- Hebrew/RTL: read the transcript word-by-word with explicit timestamps to avoid reorder confusion.
|
|
106
|
+
|
|
107
|
+
## The gates
|
|
108
|
+
|
|
109
|
+
1. **SCREEN before cutting** — Scribe hides retakes in over-long/dropped tokens; always read `flags.txt` first.
|
|
110
|
+
2. **DESILENCE on render** (automatic) — the amplitude pass cuts every sub-dB-floor silence the
|
|
111
|
+
transcript can't see and trims silent edges. Transcript-aware (won't split spelled acronyms).
|
|
112
|
+
3. **CONTENT-VERIFY after preview** — `video-editing verify` re-transcribes joins; a waveform/visual check
|
|
113
|
+
alone does NOT catch a retake left in (abandoned take + keeper render as one continuous clean span).
|
|
114
|
+
A **≥3-word** duplicate across a join = a real retake left in → **error**, fix it. A bare **2-word**
|
|
115
|
+
repeat = **warning**: adjudicate with `inspect` — it is usually a spelled acronym / number / emphasis
|
|
116
|
+
(e.g. "WW" → "דבל יו דבל יו"), not a retake, and is safe to keep.
|
|
117
|
+
|
|
118
|
+
## EDL format
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"version": 1,
|
|
123
|
+
"sources": {"SRC": "<job>/vertical_src.mp4"},
|
|
124
|
+
"grade": "none",
|
|
125
|
+
"ranges": [
|
|
126
|
+
{"source": "SRC", "start": 11.35, "end": 14.12, "beat": "HOOK", "quote": "…", "reason": "cleanest take; stops before the slip at 38.46"}
|
|
127
|
+
],
|
|
128
|
+
"overlays": [],
|
|
129
|
+
"total_duration_s": 51.33
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Ranges are KEEP segments in source seconds; the gaps between them are the cuts. A retake range
|
|
134
|
+
must END at the **last clean word before the abandoned attempt**. `total_duration_s` = Σ(end−start).
|
|
135
|
+
|
|
136
|
+
## Timeline v2 (preferred when present)
|
|
137
|
+
|
|
138
|
+
When the CLI exposes a `timeline compile` verb (run `video-editing timeline --help`),
|
|
139
|
+
**author `<job>/timeline.json` instead of hand-writing `edl.json`.** timeline.json is the
|
|
140
|
+
single authored artifact: cuts (keep-ranges) plus optional **captions, zooms, overlays, and
|
|
141
|
+
audio blocks**. The compiler is compiler-grade — it validates, resolves phrase anchors to exact
|
|
142
|
+
source seconds, and emits `resolved/plan.json`, `resolved/diagnostics.json`, and a legacy
|
|
143
|
+
`edl.json`.
|
|
144
|
+
|
|
145
|
+
**Prerequisite — bake the vertical master first.** `timeline compile` probes
|
|
146
|
+
`<job>/vertical_src.mp4`, and `transcribe` alone does NOT create it: after transcribing, run
|
|
147
|
+
`video-editing cut-bad-retakes --job-dir "<job>" --render none` once (it bakes the vertical
|
|
148
|
+
master AND writes the screening artifacts the SCREEN gate needs anyway). Skipping this makes
|
|
149
|
+
`timeline compile` fail with a raw ffprobe "No such file or directory" — that error means run
|
|
150
|
+
the bake, not that your timeline is wrong.
|
|
151
|
+
|
|
152
|
+
Loop until clean:
|
|
153
|
+
|
|
154
|
+
1. Author/patch `<job>/timeline.json`.
|
|
155
|
+
2. `video-editing timeline compile --job-dir "<job>" --json` — read the diagnostics and iterate
|
|
156
|
+
until it exits **0**:
|
|
157
|
+
- **exit 2 = validation failed** (schema / semantic / capability) — fix the reported `path`s.
|
|
158
|
+
- **exit 3 = ambiguous anchor** — disambiguate with
|
|
159
|
+
`video-editing timeline resolve --phrase "…" --near <t>` (or `--occurrence <n>`), then bake the
|
|
160
|
+
resolved seconds / occurrence into the anchor and re-compile.
|
|
161
|
+
3. **Never hand-edit anything under `resolved/`** — it is generated; re-compile instead.
|
|
162
|
+
4. compile emits the legacy `edl.json`, so the **render → desilence → verify → final** steps are
|
|
163
|
+
**unchanged** — run them exactly as in the workflow above.
|
|
164
|
+
|
|
165
|
+
**Phrase → seconds ONLY via `timeline resolve`.** Never eyeball `word_dump.txt` for anchor
|
|
166
|
+
times — `resolve` returns word-accurate `start`/`end` and flags ambiguity so you disambiguate
|
|
167
|
+
deliberately (`--near <srcSeconds>` or `--occurrence <n>`).
|
|
168
|
+
|
|
169
|
+
Captions: enable with `video-editing timeline captions generate --job-dir "<job>" [--preset grouped]`
|
|
170
|
+
— cues are **derived at compile**, you don't hand-write cue timings. The default preset `grouped`
|
|
171
|
+
puts up to 4 words in each cue, starting a new cue on long word gaps (>0.6s), cut joins (a cue
|
|
172
|
+
never spans a join), and sentence-ending `.` `?` `!` (same characters in Hebrew); `opus-karaoke`
|
|
173
|
+
is the word-by-word opt-in. Timing/grouping knobs live in `captions.style` in timeline.json:
|
|
174
|
+
`maxWords` (default 4), `leadS` (default 0.08 — each cue appears that many seconds early, clamped
|
|
175
|
+
to the previous cue / segment start), `holdS` (default 0.9 — each cue holds on screen up to that
|
|
176
|
+
long past its last word to bridge the gap to the next cue, killing blink). **Word styling**: an
|
|
177
|
+
`op:"style"` caption override colors and/or emphasizes the anchored words — e.g. "make word X red"
|
|
178
|
+
is `{"op":"style","anchor":{"type":"words","source":"SRC","phrase":"X"},"color":"#ff5c5c"}`;
|
|
179
|
+
`emphasis:"pop"|"highlight"` animates the word at its onset. `captions.style.karaoke: true`
|
|
180
|
+
adds a per-word highlight sweep inside grouped cues (staged). Validate anytime with
|
|
181
|
+
`video-editing timeline validate --job-dir "<job>" --json`.
|
|
182
|
+
|
|
183
|
+
### Authoring zooms
|
|
184
|
+
|
|
185
|
+
A zoom is a punch-in on `rect` (unit-canvas crop, `x+w<=1`, `y+h<=1`) at `scale`
|
|
186
|
+
(`1 < scale <= 3`), anchored to a phrase or source span. Static by default (hard in/out).
|
|
187
|
+
For a **ramp**, add `to: {scale?, rect?}` — the zoom animates from the base rect/scale to
|
|
188
|
+
`to` over the zoom window, shaped by `ease: {in, out}` (both > 0 → smooth in-out). Zooms
|
|
189
|
+
resolve onCut like everything else (`clip` default). Full examples:
|
|
190
|
+
`references/timeline-v2-vocabulary.md`.
|
|
191
|
+
|
|
192
|
+
### Authoring overlays & assets
|
|
193
|
+
|
|
194
|
+
- Text: `kind:"text"` (positioned) / `kind:"title-card"` (full-frame card) + `text`.
|
|
195
|
+
- **Image/sticker**: `kind:"sticker"` (or `"image"`) + `asset: {ref: "assets/logo.png"}` —
|
|
196
|
+
`ref` is a **job-dir-relative path**; keep files under `<job>/assets/`. Position with
|
|
197
|
+
`layout.x/y` (center), size with `size.w/h` (canvas fractions; sticker default ~0.25 of
|
|
198
|
+
width), stack with `z` (0–50). Animate with `enter`/`exit`
|
|
199
|
+
(`{preset: fade|pop|slide-up|slide-down|scale-in, durationS ≤ 2}`).
|
|
200
|
+
- A referenced file that does not exist fails `timeline compile` with `asset-missing` —
|
|
201
|
+
put the file in place first.
|
|
202
|
+
- On `review push --timeline` / `--complete`, referenced assets are **uploaded to
|
|
203
|
+
clipready automatically** (sha256-cached in `<job>/review/assets.json`, so unchanged
|
|
204
|
+
files are never re-uploaded); the push payload carries the `{ref: asset_id}` manifest.
|
|
205
|
+
- `compose` copies referenced assets into `<job>/composition/media/` and renders them.
|
|
206
|
+
|
|
207
|
+
### Delivering captions/zooms
|
|
208
|
+
|
|
209
|
+
The ffmpeg `render --mode final` path is **CUT-ONLY** — it never draws the compiled
|
|
210
|
+
captions/zooms/overlays from `resolved/plan.json`. Deliver those visual layers with either:
|
|
211
|
+
|
|
212
|
+
- **`video-editing compose --job-dir "<job>" --render`** — local and free. Builds a declarative
|
|
213
|
+
HyperFrames composition in `<job>/composition/` (compressed proxy of `vertical_src.mp4`,
|
|
214
|
+
the CONTINUOUS cut audio extracted from `preview.mp4`, and `index.html`) and renders it with
|
|
215
|
+
the `hyperframes` CLI (needs it on PATH: `npm install -g hyperframes`). Prerequisites:
|
|
216
|
+
`resolved/plan.json` (run `timeline compile`) and `preview.mp4` (run `render --mode preview`).
|
|
217
|
+
Without `--render` it only builds the composition and prints the exact `hyperframes render`
|
|
218
|
+
command to run. Output defaults to `<job>/composed.mp4`.
|
|
219
|
+
- **The ClipReady cloud render** (`cloud render`), which renders the same plan server-side
|
|
220
|
+
(requires a ClipReady account + API key — see the review-round section below).
|
|
221
|
+
|
|
222
|
+
Do **NOT** fall back to the legacy `render --build-subtitles` ASS burn for timeline-v2 jobs —
|
|
223
|
+
it requires a libass ffmpeg and renders the old 2-word subtitle style, not the compiled cues.
|
|
224
|
+
|
|
225
|
+
### Working a ClipReady review round
|
|
226
|
+
|
|
227
|
+
> Cloud features require a ClipReady account + API key (`CLIPREADY_API_BASE`, `CLIPREADY_API_KEY`).
|
|
228
|
+
|
|
229
|
+
When the job is a clipready review round (you have an API base + credential + video id), the
|
|
230
|
+
CLI syncs the round for you — no hand-rolled curl:
|
|
231
|
+
|
|
232
|
+
1. `video-editing review pull --job-dir "<job>"` — writes `<job>/review/round.json` (run id,
|
|
233
|
+
requested changes) and `<job>/review/instruction.md`, and hydrates `<job>/timeline.json`
|
|
234
|
+
from the server when you don't already have one (it never overwrites a local timeline).
|
|
235
|
+
Add `--source` if the source video isn't local yet.
|
|
236
|
+
2. `video-editing review push --stage edl --job-dir "<job>"` when you start working the round.
|
|
237
|
+
3. Edit `timeline.json` per the instruction, then
|
|
238
|
+
`video-editing review push --timeline --job-dir "<job>"` after each meaningful change — it
|
|
239
|
+
compiles locally (diagnostics + exit 2 if not clean) and the review page updates live.
|
|
240
|
+
4. Preview/verify locally exactly as in the workflow above (render preview, CONTENT-VERIFY, QC).
|
|
241
|
+
5. `video-editing review push --complete --duration <s> --outcomes outcomes.json --job-dir "<job>"`
|
|
242
|
+
— compiles fresh (must be clean) and delivers the terminal payload (edl + timeline + plan).
|
|
243
|
+
`outcomes.json` is `[{"change_id": "chg_…", "ok": true, "note": "…"}]`, one entry per
|
|
244
|
+
`[chg_…]` marker in the instruction; omit `--outcomes` when there are no markers.
|
|
245
|
+
|
|
246
|
+
## References
|
|
247
|
+
|
|
248
|
+
- `references/cli-reference.md` — every subcommand + flag (incl. `verify`, `inspect`).
|
|
249
|
+
- `references/timeline-v2-vocabulary.md` — the full timeline.json v2 vocabulary, one JSON
|
|
250
|
+
example per feature (word styling, karaoke, zoom ramps, stickers, enter/exit, grade).
|
|
251
|
+
- `references/editing-brief.md` — the full EDL-authoring procedure.
|
|
252
|
+
- `references/hard-rules.md` — production-correctness rules + anti-patterns.
|
|
253
|
+
- `references/scribe-collapse.md` — why retakes hide in the transcript (Gate 1).
|
|
254
|
+
- `references/adversarial-review.md` — the content-verify pass (Gate 2).
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Gate 2 — content-verify after preview
|
|
2
|
+
|
|
3
|
+
A single editing pass leaves retakes in. The reliable failure mode: the abandoned
|
|
4
|
+
first attempt and the keeper render as **one continuous clean-audio span**, so a
|
|
5
|
+
waveform / visual self-eval looks perfect while the *content* is duplicated across
|
|
6
|
+
the join. The fix is to **re-transcribe the rendered preview at each join** — which
|
|
7
|
+
is exactly what `video-editing verify` does. Run it for every real cut.
|
|
8
|
+
|
|
9
|
+
## Run it
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
video-editing verify --job-dir "<job>" --json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- Default (per-join): re-transcribes a ~6s window centered on each cut boundary
|
|
16
|
+
(from `cut_boundaries.json`), **healing OFF**.
|
|
17
|
+
- `--full`: re-transcribes the whole preview and also checks head/tail dead air.
|
|
18
|
+
|
|
19
|
+
It reports, per join:
|
|
20
|
+
- **duplicated phrase across the join** → a retake left in (severity `error`). It only
|
|
21
|
+
flags **near-adjacent** duplication (the retake fingerprint), not legitimate topical
|
|
22
|
+
repeats spaced apart.
|
|
23
|
+
- **over-long gap straddling the join** → dead air not tightened (warning, or error if large).
|
|
24
|
+
|
|
25
|
+
Writes `verify.json` (same shape family as `review.json`); exit code 2 on `needs-fixes`.
|
|
26
|
+
|
|
27
|
+
## Adjudicate + fix
|
|
28
|
+
|
|
29
|
+
For any finding, eyeball it: `video-editing inspect --job-dir "<job>" --join <i>` and read
|
|
30
|
+
the PNG (clipped word, leftover false-start syllable, audio pop, broken frame?).
|
|
31
|
+
|
|
32
|
+
Then FIX the EDL and re-render + re-verify:
|
|
33
|
+
- **Retake still present** → set that range's END at the **last clean word BEFORE the
|
|
34
|
+
abandoned attempt**.
|
|
35
|
+
- **Intentional repeat wrongly cut** → restore it.
|
|
36
|
+
- **Clipped boundary** → re-snap to the transcript word boundary.
|
|
37
|
+
- **Leading / trailing dead air** → trim it.
|
|
38
|
+
|
|
39
|
+
Re-snap every edge to a word boundary; keep pads tight (30–200ms); keep `grade:"none"`,
|
|
40
|
+
`overlays:[]`; update `total_duration_s`. Re-render `--mode preview`, re-run `verify`.
|
|
41
|
+
Cap at ~3 fix passes; if findings persist, surface them rather than looping forever.
|
|
42
|
+
|
|
43
|
+
A real retake left in / an intentional repeat wrongly cut / a clipped word at a join /
|
|
44
|
+
leading-or-trailing dead air ⇒ not clean. Minor taste calls are not defects.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# `video-editing` CLI reference
|
|
2
|
+
|
|
3
|
+
All execution goes through this CLI. Quote paths (they contain spaces).
|
|
4
|
+
|
|
5
|
+
## Pipeline commands
|
|
6
|
+
|
|
7
|
+
- **`init-job --source <video> [--job-dir <dir>] [--force]`**
|
|
8
|
+
Probe the source and create the job dir + `source.json`. Always pass `--job-dir`
|
|
9
|
+
explicitly on the sandbox (e.g. `/vercel/sandbox/jobs/<job_id>`) — there is no
|
|
10
|
+
`~/Downloads` to default into. Prints the job dir.
|
|
11
|
+
|
|
12
|
+
- **`transcribe (--source <video> | --job-dir <dir>) [--language <iso>] [--num-speakers <n>] [--no-heal] [--force] [--api-key <key>]`**
|
|
13
|
+
Word-level ElevenLabs Scribe transcription (auto-heals collapsed/dropped spans)
|
|
14
|
+
+ screening artifacts: `flags.txt`, `takes_packed.md`, `word_dump.txt`, `coverage.json`.
|
|
15
|
+
Cached per source — `--force` re-transcribes (paid). `--no-heal` disables healing.
|
|
16
|
+
|
|
17
|
+
- **`cut-silences (--source|--job-dir) [--min-gap 0.30] [--target-gap 0.13] [--lead-pad 0.05] [--trail-pad 0.08] [--render preview|none] [--force-transcribe]`**
|
|
18
|
+
Prepare a silence-only EDL-authoring prompt (`prompts/cut-silences.prompt.md`);
|
|
19
|
+
renders a preview if `edl.json` already exists.
|
|
20
|
+
|
|
21
|
+
- **`cut-bad-retakes (--source|--job-dir) [--with-sound-waves] [--quality-review] [--render preview|none] [--language] [--num-speakers] [--force-transcribe]`**
|
|
22
|
+
Prepare the full author-edl prompt (`prompts/author-edl.prompt.md`). `--with-sound-waves`
|
|
23
|
+
writes join QC PNGs (needs `edl.json` + `preview.mp4`). `--quality-review` writes the review prompt.
|
|
24
|
+
|
|
25
|
+
- **`desilence --job-dir <dir> [--noise-db -36] [--min-silence 0.14] [--lead-pad 0.04] [--trail-pad 0.05] [--render preview|none] [--force]`** ← Gate 3
|
|
26
|
+
Amplitude silence-trim. Runs `silencedetect=noise=<db>dB` over each EDL source, then for every
|
|
27
|
+
keep-range **excises internal silences ≥ `--min-silence`** (leaving `lead-pad + trail-pad` of air)
|
|
28
|
+
and **trims silent leading/trailing edges**. Catches silence the transcript can't see (dead air
|
|
29
|
+
baked inside a stretched word token, room tone, breaths). **Transcript-aware** — never splits inside
|
|
30
|
+
a single word token (spelled acronyms, held vowels, numbers). Backs the original EDL up to
|
|
31
|
+
`edl.raw.json`, rewrites `edl.json`, marks it `desilenced`. Idempotent. Higher `--noise-db`
|
|
32
|
+
(e.g. −34) + lower `--min-silence` = more aggressive.
|
|
33
|
+
**Runs automatically at the start of every `render`** (skip with `render … --no-desilence`); the
|
|
34
|
+
standalone command is for tuning + inspecting the result before rendering.
|
|
35
|
+
|
|
36
|
+
- **`render --job-dir <dir> --mode preview|draft|final [--build-subtitles] [--no-subtitles] [--no-loudnorm] [--no-desilence]`**
|
|
37
|
+
Render the EDL: **desilence (unless `--no-desilence`)** → per-segment extract → lossless concat →
|
|
38
|
+
(optional subs) → loudnorm (−14 LUFS / −1 dBTP / LRA 11). preview=CRF22/1080p, draft=CRF28/720p,
|
|
39
|
+
final=CRF20/1080p. Writes `preview.mp4`/`draft.mp4`/`final.mp4` + `cut_boundaries.json`.
|
|
40
|
+
|
|
41
|
+
## QC / verification commands
|
|
42
|
+
|
|
43
|
+
- **`verify --job-dir <dir> [--full] [--window 6] [--gap 0.6] [--json] [--api-key <key>]`** ← Gate 4 (content)
|
|
44
|
+
Re-transcribes the rendered preview with healing OFF and reports, per join,
|
|
45
|
+
**duplicated phrases** and **over-long gaps** (dead air not tightened → warning/error).
|
|
46
|
+
A duplicate of **≥3 words** = a retake left in → **error**. A bare **2-word** repeat = **warning**
|
|
47
|
+
(spelled acronym / number / emphasis like "WW" → "דבל יו דבל יו" — adjudicate with `inspect`, usually safe).
|
|
48
|
+
Default: per-join ~6s windows from `cut_boundaries.json`; `--full` re-transcribes the whole preview and
|
|
49
|
+
also checks head/tail dead air. Writes `verify.json`; exit code 2 if `needs-fixes`.
|
|
50
|
+
|
|
51
|
+
- **`inspect --job-dir <dir> (--join <i> | --start <s> --end <s>) [--video preview|vertical_src] [--transcript <path>] [-o <png>] [--n-frames 10]`**
|
|
52
|
+
Filmstrip + waveform + word-label PNG for a window or a cut-boundary index.
|
|
53
|
+
`timeline` is an alias. Use it to adjudicate a verify finding or pick a cut edge.
|
|
54
|
+
|
|
55
|
+
## TimelineV2 commands (`timeline.json` v2 — preferred when present)
|
|
56
|
+
|
|
57
|
+
Exit codes for this group: `0` ok, `1` error, `2` validation failed, `3` ambiguous anchor.
|
|
58
|
+
|
|
59
|
+
- **`timeline validate --job-dir <dir> [--json]`**
|
|
60
|
+
Schema + semantic + capability validation of `<job>/timeline.json`. Prints errors/warnings and
|
|
61
|
+
the capabilities the doc `uses`. Exit `2` if invalid.
|
|
62
|
+
|
|
63
|
+
- **`timeline compile --job-dir <dir> [--json]`**
|
|
64
|
+
Compile `timeline.json` → `resolved/plan.json` + `resolved/diagnostics.json` + **legacy `edl.json`**
|
|
65
|
+
(so render/verify are unchanged). Iterate until it exits `0`. Exit `2` = validation failed,
|
|
66
|
+
`3` = an ambiguous phrase anchor (disambiguate with `timeline resolve`). Never hand-edit `resolved/`.
|
|
67
|
+
|
|
68
|
+
- **`timeline resolve --job-dir <dir> --phrase <text> [--near <srcSeconds>] [--occurrence <n>] [--source <name>] [--json]`**
|
|
69
|
+
Resolve a transcript phrase to **exact source seconds** (normalized, word-accurate match). The
|
|
70
|
+
ONLY way to turn a phrase into an anchor time — never eyeball `word_dump.txt`. Multiple hits →
|
|
71
|
+
exit `3` with per-occurrence context; pick one with `--near` (nearest source time) or
|
|
72
|
+
`--occurrence` (1-based). Not found → fuzzy suggestions, exit `1`.
|
|
73
|
+
|
|
74
|
+
- **`timeline captions generate --job-dir <dir> [--preset grouped|opus-karaoke] [--json]`**
|
|
75
|
+
Enable captions in `timeline.json` (preset only — cues are **derived at compile**, not hand-authored).
|
|
76
|
+
Default preset `grouped`: up to `captions.style.maxWords` (default 4) words per cue, breaking on
|
|
77
|
+
word gaps >0.6s, cut joins, and sentence-ending `.` `?` `!`; `opus-karaoke` is the word-by-word
|
|
78
|
+
opt-in. Style knobs in `captions.style`: `maxWords` (4), `leadS` (0.08 — cues display that early,
|
|
79
|
+
clamped), `holdS` (0.9 — cues hold to bridge the gap to the next cue). Reports the derived cue
|
|
80
|
+
count via a dry compile.
|
|
81
|
+
|
|
82
|
+
- **`compose --job-dir <dir> [--render] [--quality draft|standard|high] [--fps 30] [--out <path>] [--proxy-crf 23] [--json]`**
|
|
83
|
+
Build a local HyperFrames composition from `resolved/plan.json` (cut + captions/zooms/overlays)
|
|
84
|
+
in `<job>/composition/`: `media/source.mp4` (compressed proxy of `vertical_src.mp4`, libx264
|
|
85
|
+
CRF `--proxy-crf`, skipped when up to date), `media/audio.m4a` (the CONTINUOUS cut audio
|
|
86
|
+
extracted from `preview.mp4` — the QC preview must exist), referenced image/sticker assets
|
|
87
|
+
copied under `media/` (preserving their job-dir-relative paths), and `index.html` (with a
|
|
88
|
+
generated GSAP timeline when the plan uses animated vocabulary). Without `--render`,
|
|
89
|
+
prints the composition dir + the exact `hyperframes render` command. With `--render`, runs
|
|
90
|
+
`hyperframes render --quality <q> --fps <n> --output <out>` (default `<job>/composed.mp4`)
|
|
91
|
+
in the composition dir — requires the `hyperframes` CLI on PATH (`npm install -g hyperframes`).
|
|
92
|
+
`--json` prints `{compositionDir, entry, out?, segments, cues, zooms, overlays}`. This is the
|
|
93
|
+
LOCAL delivery path for compiled captions/zooms — the ffmpeg `render` is cut-only; never use
|
|
94
|
+
`--build-subtitles` for timeline-v2 jobs.
|
|
95
|
+
|
|
96
|
+
## Cloud thin-client commands (clipready render/QA/artifact API)
|
|
97
|
+
|
|
98
|
+
Every cloud verb resolves credentials the same way and supports `--json` (machine shape;
|
|
99
|
+
exit `0` ok, `1` error). Resolution precedence:
|
|
100
|
+
|
|
101
|
+
- **base**: `--api-base` → env `CLIPREADY_API_BASE` → env `API_BASE`
|
|
102
|
+
- **credential** (sent as `Authorization: Bearer <credential>`): `--api-key` → env `CLIPREADY_API_KEY` → env `RUN_TOKEN`
|
|
103
|
+
- **video id**: `--video-id` → env `VIDEO_ID`
|
|
104
|
+
|
|
105
|
+
A missing base / credential / (required) video id is a clear error → exit `1`.
|
|
106
|
+
|
|
107
|
+
- **`cloud render --job-dir <dir> --mode preview|final [--wait] [--poll-interval 10] [--out <path>] [--json]`**
|
|
108
|
+
Submit a render. Without `--wait`: prints `render_id` and exits `0`. With `--wait`: polls status
|
|
109
|
+
(max 30 min) until terminal; on `completed`, downloads the signed `video_url` to `--out`
|
|
110
|
+
(default `<job>/preview.mp4` or `<job>/final.mp4`). `failed` → exit `1` with the error. Needs a video id.
|
|
111
|
+
|
|
112
|
+
- **`cloud status <render_id> [--json]`**
|
|
113
|
+
One status GET: `pending|running|completed|failed` (+ `video_url` when completed). `failed` → exit `1`.
|
|
114
|
+
|
|
115
|
+
- **`qa frames --start <s> --end <s> [--cols <n>] [--interval <s>] [--job-dir <dir>] [--json]`**
|
|
116
|
+
Server-render a filmstrip PNG for the window and download it to `<job>/qa/frames_<start>_<end>.png`
|
|
117
|
+
(`./qa/` when no `--job-dir`). Prints the local path + grid metadata (Read the PNG to inspect). Needs a video id.
|
|
118
|
+
|
|
119
|
+
- **`qa waveform --start <s> --end <s> [--job-dir <dir>] [--json]`**
|
|
120
|
+
Same, for a waveform: downloads `<job>/qa/waveform_<start>_<end>.png` and writes the word list to
|
|
121
|
+
`<job>/qa/waveform_<start>_<end>.words.json`. Prints both paths. Needs a video id.
|
|
122
|
+
|
|
123
|
+
- **`job pull [--job-dir <dir>] [--only <name,name>] [--json]`**
|
|
124
|
+
List the video's artifacts and fetch each (or the `--only` subset) into the job dir (default cwd),
|
|
125
|
+
**preserving relative paths** (`resolved/plan.json` → `<job>/resolved/plan.json`). Prints a summary.
|
|
126
|
+
`.json` artifacts are written pretty-printed; others verbatim. Needs a video id. Allowlisted names include
|
|
127
|
+
`word_dump.txt`, `takes_packed.md`, `flags.txt`, `coverage.json`, `edl.json`, `verify.json`,
|
|
128
|
+
`cut_boundaries.json`, `timeline.json`, `resolved/plan.json`, `resolved/diagnostics.json`.
|
|
129
|
+
|
|
130
|
+
- **`review pull [--job-dir <dir>] [--source] [--json]`**
|
|
131
|
+
Fetch the clipready review session for the video into the job dir (default cwd): writes
|
|
132
|
+
`<job>/review/round.json` (round/run ids, mode, status, requested changes) and
|
|
133
|
+
`<job>/review/instruction.md` (the round's editorial instruction — or a "no active round" note).
|
|
134
|
+
Writes `<job>/timeline.json` from the session ONLY when no local timeline.json exists (never
|
|
135
|
+
overwrites yours). With `--source`, also downloads the source video (to the `source.json`
|
|
136
|
+
filename, else `<job>/source.mp4`) when absent locally. Needs a video id.
|
|
137
|
+
|
|
138
|
+
- **`review push (--stage <type> [--message <text>] | --timeline | --complete --duration <s> [--outcomes <file>]) [--job-dir <dir>] [--run-id <id>] [--json]`**
|
|
139
|
+
Push review-round events (exactly one mode per call; run id defaults to `review/round.json`
|
|
140
|
+
from `review pull`). `--stage` posts a progress event
|
|
141
|
+
(`provisioning|transcribing|edl|render_preview|qc|render_final|uploading|log`), optional
|
|
142
|
+
`{message}` payload. `--timeline` compiles `<job>/timeline.json` locally (same sequence as
|
|
143
|
+
`timeline compile`, needs ffmpeg) and pushes a `timeline_update` with the raw timeline JSON,
|
|
144
|
+
the compiled plan, and its hash — the review page updates live. `--complete` compiles fresh
|
|
145
|
+
(must be clean) and pushes the terminal `completed` event
|
|
146
|
+
(`{edl, timeline, plan, duration_s, outcomes?}`; `--outcomes` is a JSON file of
|
|
147
|
+
`[{change_id, ok, note?}]`, bare array or `{outcomes: [...]}`). Compile failures print
|
|
148
|
+
diagnostics and exit `2` (nothing is pushed); cloud errors exit `1`. Needs a video id.
|
|
149
|
+
Both `--timeline` and `--complete` **upload the timeline's referenced assets
|
|
150
|
+
automatically** (image/sticker overlays, audio beds): each ref is sha256-cached in
|
|
151
|
+
`<job>/review/assets.json` so unchanged files are never re-uploaded, and the push
|
|
152
|
+
payload includes an `assets: {ref: asset_id}` manifest. A missing asset file fails the
|
|
153
|
+
compile (`asset-missing`) before anything is uploaded.
|
|
154
|
+
|
|
155
|
+
- **`skill install [--target <dir>] [--json]`**
|
|
156
|
+
Copy the bundled `video-editing` agent skill into `<target|~/.claude/skills>/video-editing`
|
|
157
|
+
(overwriting). Prints what was installed.
|
|
158
|
+
|
|
159
|
+
## Utility
|
|
160
|
+
|
|
161
|
+
- **`--version`** — print the CLI version.
|
|
162
|
+
- **`status --job-dir <dir> [--json]`** — job state + artifact existence.
|
|
163
|
+
- **`review prompt --job-dir <dir> [--write-warnings]`** — write the review prompt;
|
|
164
|
+
optionally convert `review.json` → `warnings.json`. (`--open-app` is not usable on the
|
|
165
|
+
sandbox — it launches a local browser; skip it, there is no display.) This was the old
|
|
166
|
+
top-level `review` verb — `review` is now a group (`prompt` / `pull` / `push`).
|
|
167
|
+
- **`config set-key <key>` / `config path`** — manage the ElevenLabs key
|
|
168
|
+
(`~/.config/video-editing/.env`; on the sandbox the CLI reads env `ELEVENLABS_API_KEY`,
|
|
169
|
+
no need to run `config set-key`).
|
|
170
|
+
|
|
171
|
+
## Typical session
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
video-editing init-job --source "source.mp4" --job-dir /vercel/sandbox/jobs/<job_id>
|
|
175
|
+
video-editing transcribe --job-dir /vercel/sandbox/jobs/<job_id>
|
|
176
|
+
# read flags.txt (Gate 1), author edl.json
|
|
177
|
+
video-editing render --job-dir /vercel/sandbox/jobs/<job_id> --mode preview
|
|
178
|
+
video-editing verify --job-dir /vercel/sandbox/jobs/<job_id> --json # Gate 2
|
|
179
|
+
# fix edl.json as needed, re-render, re-verify
|
|
180
|
+
video-editing render --job-dir /vercel/sandbox/jobs/<job_id> --mode final
|
|
181
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Editing brief — authoring the EDL
|
|
2
|
+
|
|
3
|
+
The standing style for a single talking-head vertical reel and the exact
|
|
4
|
+
step-by-step for authoring `<job>/edl.json`. All execution is via the
|
|
5
|
+
`video-editing` CLI; you only write the EDL.
|
|
6
|
+
|
|
7
|
+
## Standing brief (apply exactly)
|
|
8
|
+
|
|
9
|
+
- **Output VERTICAL 9:16.** The CLI bakes `vertical_src.mp4`; point the EDL at it.
|
|
10
|
+
- **CUT:** bad retakes, false starts, cut-off / stuttered words — keep the **LAST
|
|
11
|
+
complete take**, EXCEPT keep the **CLEANEST** take when an earlier take is clean
|
|
12
|
+
and a later one stutters into completion.
|
|
13
|
+
- **TIGHTEN EVERY PAUSE:** trim 0.3–1s inter-phrase gaps down to ~0.12–0.14s of air.
|
|
14
|
+
- **COLD-OPEN** on the strongest hook (trim weak/dead intro).
|
|
15
|
+
- **TRIM outro dead air** (footsteps / ambient / silence / trailing breaths / device noise).
|
|
16
|
+
- **CLEAN CUTS ONLY:** `grade:"none"`, no subtitles, no overlays.
|
|
17
|
+
- **KEEP intentional rhetorical repeats.**
|
|
18
|
+
|
|
19
|
+
### Retake-vs-intentional discriminator (for a flagged repeated phrase)
|
|
20
|
+
|
|
21
|
+
- **RETAKE** (cut it, keep the last/cleanest complete pass) if the earlier
|
|
22
|
+
instance cuts off mid-word (a fragment / trailing "-") OR is immediately restarted.
|
|
23
|
+
- **INTENTIONAL** (keep BOTH) if both instances are complete and serve rhetoric /
|
|
24
|
+
parallel structure and are separated by other content.
|
|
25
|
+
- Decide every flagged repeat — never blind-cut.
|
|
26
|
+
|
|
27
|
+
## Steps
|
|
28
|
+
|
|
29
|
+
1. **Init + transcribe + screen** (CLI does these): `init-job` → `transcribe`.
|
|
30
|
+
Then read `<job>/flags.txt` (Gate 1) and confirm `<job>/coverage.json`.
|
|
31
|
+
2. **Read the word-level transcript** at `<job>/word_dump.txt` (every word/audio_event
|
|
32
|
+
as `[start-end] type: text` with `---- gap Xs ----` at gaps ≥0.30s). THINK HARD and
|
|
33
|
+
list, with exact timestamps: every retake/false-start/stutter (and which take to KEEP),
|
|
34
|
+
every silence (head/inter-phrase/outro), the cold-open point, the intentional repeats.
|
|
35
|
+
When text and audio seem to disagree, `video-editing inspect --video vertical_src --start <a> --end <b>` and read the PNG.
|
|
36
|
+
3. **Write `<job>/edl.json`** — KEEP segments in source seconds; gaps between them are
|
|
37
|
+
the cuts. Snap every in/out to a transcript word boundary (lead ~0.05s, trail ~0.08s;
|
|
38
|
+
~0.03–0.05s at retake joins, clamped to stay inside the silence and never include a
|
|
39
|
+
false-start word). Ranges chronological, non-overlapping. `total_duration_s` = Σ(end−start).
|
|
40
|
+
**A retake range must END at the LAST CLEAN WORD before the abandoned attempt.**
|
|
41
|
+
4. **Render** `--mode preview`, then **verify** (Gate 2), fix, repeat. Finalize `--mode final`.
|
|
42
|
+
|
|
43
|
+
See `cli-reference.md` for exact commands, `hard-rules.md` for correctness, and
|
|
44
|
+
`scribe-collapse.md` / `adversarial-review.md` for the two gates.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Hard rules (production correctness — non-negotiable)
|
|
2
|
+
|
|
3
|
+
These produce **silent failures** if violated. The CLI enforces the rendering
|
|
4
|
+
rules internally; the EDL-authoring rules are on you.
|
|
5
|
+
|
|
6
|
+
## The CLI guarantees (do not try to replicate by hand)
|
|
7
|
+
|
|
8
|
+
- **Per-segment extract → lossless `-c copy` concat** (no double-encode).
|
|
9
|
+
- **30ms audio fades at every segment boundary** (`afade` in/out) — which is why
|
|
10
|
+
tight pads (down to ~30ms) at retake joins are safe.
|
|
11
|
+
- **Loudnorm** to −14 LUFS / −1 dBTP / LRA 11 (two-pass for final, one-pass for preview).
|
|
12
|
+
- Subtitles, if ever enabled, are applied LAST in the filter chain.
|
|
13
|
+
- Word-level verbatim ASR with auto-heal; transcripts cached per source.
|
|
14
|
+
|
|
15
|
+
## EDL-authoring rules (yours to uphold)
|
|
16
|
+
|
|
17
|
+
- **Never cut inside a word.** Snap every edge to a transcript word boundary.
|
|
18
|
+
- **Pad every cut edge** (30–200ms). Scribe timestamps drift 50–100ms; padding absorbs it.
|
|
19
|
+
Tightest (~0.03–0.05s) at retake joins, staying inside the silence.
|
|
20
|
+
- A **retake range ends at the last clean word BEFORE the abandoned attempt.**
|
|
21
|
+
- Clipped tokens ending in `-` (`s-`, `re-`, `li-`) are danger zones — leave an 80–150ms
|
|
22
|
+
guard band before them when there's room.
|
|
23
|
+
- Keep the first token of the chosen take even if it's a connector ("and/so/but").
|
|
24
|
+
- `grade:"none"`, `overlays:[]`, no subtitles (clean-cut pass).
|
|
25
|
+
- Keep `total_duration_s` = Σ(end−start); ranges chronological + non-overlapping.
|
|
26
|
+
|
|
27
|
+
## The two mandatory gates
|
|
28
|
+
|
|
29
|
+
1. **SCREEN before cutting** — read `flags.txt`; resolve every [A], judge every [B]. See `scribe-collapse.md`.
|
|
30
|
+
2. **CONTENT-VERIFY after preview** — `video-editing verify`; a visual/waveform check alone
|
|
31
|
+
misses a retake left in. See `adversarial-review.md`.
|
|
32
|
+
|
|
33
|
+
## Anti-patterns (consistently fail)
|
|
34
|
+
|
|
35
|
+
- Trusting the transcript without screening it (Scribe collapses/drops hide retakes).
|
|
36
|
+
- Running ffmpeg or ad-hoc helper scripts by hand — forbidden; use only the `video-editing` CLI.
|
|
37
|
+
- A visual-only check at a retake join (the duplicated content reads as continuous clean audio).
|
|
38
|
+
- Re-transcribing an unchanged source (paid + pointless; it's cached).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Gate 1 — the Scribe collapse / drop gotcha
|
|
2
|
+
|
|
3
|
+
The single most important thing before reasoning about cuts: **retakes hide in
|
|
4
|
+
the transcript.** Trust the text without screening and you will leave duplicated
|
|
5
|
+
content in the cut.
|
|
6
|
+
|
|
7
|
+
## What happens
|
|
8
|
+
|
|
9
|
+
When Scribe hits a hard-to-segment chunk (fast speech, a stutter, a false start
|
|
10
|
+
immediately followed by the retake), it sometimes:
|
|
11
|
+
|
|
12
|
+
- **Collapses** the chunk into ONE over-long token (a "word" lasting >~1.2s), or
|
|
13
|
+
- **Drops** a stretch of audio silently — no tokens for that span.
|
|
14
|
+
|
|
15
|
+
Either way the retake is invisible in the text, a waveform looks fine, and the
|
|
16
|
+
cut "reads" clean — but the abandoned take is still in the render.
|
|
17
|
+
|
|
18
|
+
## How the CLI + you catch it
|
|
19
|
+
|
|
20
|
+
1. **`video-editing transcribe` auto-heals** most collapses/drops (re-transcribes the
|
|
21
|
+
suspect window in isolation and splices the recovered words back). Watch its log.
|
|
22
|
+
2. **`video-editing transcribe` writes `flags.txt`** — read it (Gate 1):
|
|
23
|
+
- **[A]** over-long / collapsed tokens (>~1.2s) — content is hidden inside one of these.
|
|
24
|
+
- **[B]** repeated phrases — the retake fingerprint (false starts, reworded restarts).
|
|
25
|
+
- **[C]** audio events + big gaps.
|
|
26
|
+
Resolve every [A]; judge every [B] (retake vs intentional).
|
|
27
|
+
3. **Verify coverage** via `coverage.json`: the last word must end within ~2s of the
|
|
28
|
+
media duration. If it ends well before EOF, a tail was dropped.
|
|
29
|
+
|
|
30
|
+
## When a span is still suspect
|
|
31
|
+
|
|
32
|
+
Inspect it: `video-editing inspect --job-dir <job> --video vertical_src --start <a> --end <b>`
|
|
33
|
+
and read the filmstrip+waveform PNG to see what was actually said before choosing the cut.
|
|
34
|
+
|
|
35
|
+
> Screening + inspecting suspicious windows is what isolates retakes Scribe collapsed
|
|
36
|
+
> into one token. Skipping it is the #1 cause of a "clean-looking" cut that still has a
|
|
37
|
+
> retake in it.
|