Commit 7d94c80
videoeditor-record: web-based narration recorder (
* feat: videoeditor-record — record narration takes in a local web recorder
New crate videoeditor-record and a `record` subcommand: `videoeditor
record <episode>` serves a one-page teleprompter + mic recorder on
127.0.0.1 and writes kept takes straight into the episode, ready for the
normal render/assemble pipeline.
- Capture in the browser (getUserMedia/MediaRecorder): device picker,
permission UX, and live level metering are already solved there —
cross-platform native capture is not. localhost is a secure context,
so the mic works without TLS. Raw voice: echoCancellation /
noiseSuppression / autoGainControl all disabled.
- UI: clip sidebar with per-take fit status, big teleprompter text,
3-2-1 countdown, elapsed-vs-window timer that flags overruns live,
level meter (WebAudio), review player, keyboard-driven flow
(space/enter/r/arrows).
- Server (tiny_http, sync — no async runtime added): uploads (webm/opus
or mp4/aac) transcode via ffmpeg to the exact mp3 format the TTS path
produces; every kept take lands in audio/takes/<id>/ and the replaced
clip is archived, so no take is ever lost; audio/clips.json is rebuilt
from disk (self-healing) and each save returns the episode fit-check.
- Clip ids off the URL are allowlist-sanitized (no path traversal).
- guide.md pipeline/director-loop + CLAUDE.md layout updated.
Live-tested end to end against a scratch episode: webm upload →
transcode → archive (take_001/take_002/replaced_003) → manifest →
fit-check response; traversal attempts rejected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(record): take coaching — level checks + ElevenLabs Scribe review before you keep a take
POST /api/review/<id> analyzes a pending take without keeping it, and the
UI shows a COACH panel while you listen back:
- always (no API needed): duration vs scene window, mean/max dBFS via
ffmpeg volumedetect, clipping and too-quiet flags
- with ELEVENLABS_API_KEY: Scribe transcription → script-vs-spoken word
diff (LCS; dropped words + ad-libs), words/sec pacing, dead-air gaps
from word timestamps, tagged background audio events
- coaching lines synthesize it ("dropped from the script: boomer",
"dead air at 3.0s", "clipping — back off the mic", or "clean take —
ship it"); degrades gracefully to local-only metrics without a key
Tests: LCS diff (missing/added words), clean-take praise, multi-fault
coaching (overrun + clipping + drops + racing + dead air + cough).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(record): stereo capture + archive every take
- capture and transcode stereo (interface sends a stereo mix; assembly
normalizes all narration to stereo anyway), 192k mp3 archive quality
- L/R channel meters in the UI instead of a single downmixed bar
- every take is archived to audio/takes/<id>/take_NNN.mp3 the moment
it is reviewed — retakes lose nothing; keep promotes the archived
file via /api/keep/<id>/<n> without re-uploading
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(record): takes browser + per-clip approvals
- GET /api/takes/<id> lists every archived take (take_* and replaced_*)
with duration and approval state; GET /audio/takes/<id>/<file> serves
them for audition
- POST /api/approve/<id>/<file> promotes an archived take to the clip
and records the pick in audio/takes/approvals.json (in-episode, so
approvals survive restarts and travel with the repo)
- keep after recording is now just an approval of the freshly archived
take; A/B switching approved takes doesn't pile up duplicate archives
- UI: takes panel under the prompter with ▶ audition and approve
buttons, approved take highlighted
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(record): persist each take's coach report beside the audio
- review saves take_NNN.json next to take_NNN.mp3 — the archive holds
the analysis, not just the audio
- /api/takes/<id> returns each take's saved review; the takes panel
shows comparison stats per row (script %, pace, peak, clipped, long,
pauses) with full coaching notes on hover
- takes panel refreshes as soon as a new take is analyzed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(record): studio layout + modular front-end
- three-column layout: clips | takes rail | stage — takes and coach
move out of the teleprompter area for good
- state-aware stage via body class: recording dims the rails so only
the script competes for attention; review shrinks (never hides) the
script and shows one review card: coach stats, notes, transcript,
player, keep/retake
- takes rail newest-first (list_takes sorts descending)
- uber index.html split into web/: index.html + style.css + 6 plain ES
modules (state/mic/record/review/takes/main) talking over an event
bus — no framework, no build step, still embedded via include_str!
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(record): inline take review via progressive disclosure
Tap a take row to unfold its saved coach report — coaching notes +
transcript — right in the rail (chevron rotates, height animates via
the grid 0fr→1fr trick, one row open at a time). Replaces the hidden
hover tooltip. Play/approve stop propagation so they don't toggle.
Expansion is keyed clip/file so re-renders keep it open.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(record)!: Leptos front end + nix-first toolchain + playwright e2e
The recorder UI is now Rust end to end: crates/videoeditor-record-ui is
a Leptos 0.8 (CSR) app compiled to wasm with trunk. videoeditor-record
embeds the COMMITTED dist (ui/, regenerate with `just ui`) so the CLI
stays one self-contained binary and both crates publish to crates.io
with no wasm toolchain required by consumers.
- same REST API, same stylesheet contract, same studio layout: clips |
takes rail (newest first, inline disclosure of the saved coach
report) | teleprompter stage that shrinks-but-never-hides the script
during review; body-class state machine unchanged
- audio interop (getUserMedia, stereo ChannelSplitter analysers,
MediaRecorder) via web-sys; wasm-bindgen pinned =0.2.121 to match
nixpkgs' wasm-bindgen-cli (bump together)
- nix devshell now carries the whole toolchain: rust w/ wasm32 target
(rust-overlay), trunk, wasm-bindgen-cli, node, playwright + pinned
browsers (PLAYWRIGHT_BROWSERS_PATH); nix build needs no wasm because
the dist is committed
- e2e/: 9-test playwright journey against the real binary + real
Chromium with a fake mic — boot, record→coach→keep, newest-first,
disclosure, approve-from-rail, audition, retake, clip nav, and the
script-never-hidden guarantee; `just e2e`, wired into CI (ubuntu)
- vanilla JS front end deleted
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* build(record): stable UI pipeline — private UI crate, auto-rebuilt dist, drift guard
- videoeditor-record-ui is publish = false (+ release-plz release =
false): it doesn't stand alone; its trunk build ships inside
videoeditor-record's committed ui/ dist, which publishes to crates.io
as part of that crate
- no manual steps: `just e2e` depends on `just ui` (dist always rebuilt
from source before the suite), and `just check` — which CI runs on
every push, no wasm toolchain required — verifies ui/.source-hash
against the UI source, so a stale committed dist cannot merge
- dist regenerated with the nix toolchain so bytes stay stable across
machines; docs updated to match
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>videoeditor record) (#9)1 parent d225924 commit 7d94c80
34 files changed
Lines changed: 5424 additions & 15 deletions
File tree
- .github/workflows
- crates
- videoeditor-record-ui
- src
- videoeditor-record
- src
- ui
- videoeditor
- src
- e2e
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
35 | 48 | | |
36 | 49 | | |
37 | 50 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
19 | 34 | | |
20 | 35 | | |
21 | 36 | | |
| |||
0 commit comments