Edit video by editing the transcript. Drag across words, press backspace, the video loses that moment.
A self-hosted replacement for Descript — which is per seat: $16 Hobbyist, $24 Creator, $50 Business per user per month billed annually. Five people on Business is $3,000 a year for a tool that is, underneath, ffmpeg and a transcript.
Everything runs on your machine. whisper.cpp for the transcript, ffmpeg for the cut. Only the transcript is ever sent to a model, and only when you ask it to make a judgment call.
raw.mp4 ──▶ transcript ──▶ cut by deleting words ──▶ graphics ──▶ captions ──▶ final.mp4
whisper.cpp the transcript IS HyperFrames word-level ffmpeg
the timeline + GSAP animation
brew install ffmpeg whisper-cpp
npm install
npm run kve -- doctorNode 20 or newer.
cd web
npm install
npm run dev # http://localhost:4325Point it at a video, wait for the transcript, and start deleting words.
| Transcript editing | drag to select, ⌫ to cut, ⌘Z to undo, ⌘F to find |
| Fix a mishearing | double-click any word — timings never move, so nothing resyncs |
| Remove dead air | ffmpeg finds the pauses. No model, no key |
| Filler words, repeated takes | detected and proposed, never auto-applied |
| "Cut this to 60 seconds" | your model, working in sentences |
| Chapters, clip finding | same |
| Rearrange | move a passage earlier; the preview plays the new order |
| More than one camera | cut B-roll and a second angle into the same edit |
| Icons, images, graphics | rendered in the project's theme, previewed live over the video |
| Subtitles | .srt and .vtt, timed to the edit rather than the tape |
| Audio | two-pass loudness to −16 LUFS, denoise, music bed |
| Export | a job with named steps you can watch and stop |
The preview plays the edit without rendering it — removed spans are skipped live, so there is nothing between changing your mind and hearing the result.
The editor is a front-end onto a CLI that does the work, and the CLI is still there:
npm run kve -- new ~/Movies/raw.mp4 --slug myvideo
npm run kve -- roughcut plan myvideo
npm run kve -- roughcut apply myvideo
npm run kve -- graphics gen myvideo g001 --spec '{"type":"stat","value":47,"suffix":"s","label":"From 4:10 of raw tape"}'
npm run kve -- captions build myvideo
npm run kve -- export myvideoIt is also drivable by a coding agent. Open the folder with Claude Code, Kimi CLI or similar and say "start a new project for ~/Movies/raw.mp4 and do the rough cut" — .claude/skills/ carries the editorial judgment, and nothing in this repo calls an LLM on its own.
Both front-ends write the same edl.json and render through the same pipeline, so a project moves between them freely.
The model never touches a frame.
It is shown a numbered list of sentences and answers with integers. Those integers are clamped, straightened, merged, converted to media time, snapped to frame boundaries, and only then handed to ffmpeg. There is nothing a model can say — including a confident hallucination — that produces a broken video. The worst case is an edit smaller than you asked for.
That is also why it works with a 7B model running locally, not just a frontier one.
Kimi by default, because api.moonshot.ai/v1 is OpenAI-compatible and its cached-input pricing suits the access pattern: the same long transcript gets re-sent to trim it, then chapter it, then pull clips. Paste a key in the app, or use the repo root .env:
MOONSHOT_API_KEY=sk-...
# or anything else that speaks the OpenAI API
LLM_BASE_URL=http://localhost:11434/v1
LLM_MODEL=qwen2.5-coder:7bA local model needs no key, and then nothing leaves the machine at all.
No voice cloning, no eye-contact correction, no green screen, no screen recording. Each is a separate deep model, and leaving them out is why this exists.
Speaker labels are not built yet, so an interview currently transcribes as one voice.
npm test # engine
cd web && npm test # editorThe interesting ones run against real 4K footage rather than synthetic words, because the bugs that mattered were invisible to synthetic data: whisper stretches word ends across silence, so word gaps cannot detect dead air; and a keep list reordered to open with the passage at 107s produced a timeline placing its words 71 seconds in.
MIT.