ossclip.
A local-first CLI that turns a talking-head take into a finished short: filler words and dead air cut, word-timed kinetic captions, face-aware framing, and LLM-planned, code-rendered on-screen graphics — title cards, stat cards, diagrams, terminal and chat mockups — planned against a hand-built, Zod-typed scene library. Plus a cover image sized for the platform.
Your footage never leaves your machine. Transcription is local (whisper.cpp), rendering is local (Remotion); the only network calls are the LLM planning ones — on your own API key, or on your existing Claude Code subscription.
Scope, honestly: ossclip is at its best polishing a take you have already cut down. For long-form input, --clip <seconds> selects the single strongest window (the producer's judgement, sentence-snapped) and produces only that — one clip, not N. Without it, 20 minutes in is a polished 20 minutes out.
AI can make mistakes: the cut, captions and graphics are generated — review the output before publishing.
working end to end · pre-1.0 ★ Star on GitHub
Left: the raw take. Right: what ossclip produce returns.
One ossclip produce run, end to end — every stage says what it did and why.
Install
Two commands, on macOS, Linux, or Windows (plain PowerShell — no WSL, no admin rights). The only prerequisite is Node ≥ 22 from nodejs.org.
npm install -g ossclip
ossclip setup
ossclip setup provisions everything into one folder (~/.ossclip): a static ffmpeg build, a prebuilt whisper.cpp whisper-cli (on macOS both come via Homebrew), and the transcription model — small.en, ~466 MB, the biggest piece of a ~600 MB total. It shows the plan with sizes and asks before downloading, resumes interrupted downloads, verifies checksums, and skips anything you already have — an ffmpeg already on your PATH stays yours. Paths are recorded in ~/.ossclip/config.json, so nothing edits your PATH. Uninstall = npm rm -g ossclip plus deleting ~/.ossclip.
Setup also offers to save an LLM key for --produce: a logged-in Claude Code is detected automatically, or paste an ANTHROPIC_API_KEY or GEMINI_API_KEY. Skip it freely — cut + captions run fully local without one. If anything looks wrong later, ossclip doctor prints a line per prerequisite and the exact fix.
Licence note, since setup downloads binaries: the static ffmpeg builds it fetches (BtbN) are GPL, downloaded onto your machine at your request — nothing GPL ships inside the MIT npm package.
Manual install (if you'd rather own the toolchain)
brew install ffmpeg whisper-cpp # macOS; Linux/Windows: ffmpeg from your package manager,
# whisper-cli from https://github.com/ggml-org/whisper.cpp/releases
mkdir -p ~/.ossclip/models
curl -L -o ~/.ossclip/models/ggml-small.en.bin \
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.en.bin
ossclip doctor checks all of it — with the per-platform command for anything missing. Binaries can live anywhere: point OSSCLIP_FFMPEG / OSSCLIP_WHISPER (or config.json) at them. Setup and manual install compose — setup only ever fills the gaps doctor would flag.
small.en is the default. A mistranscribed word ends up in your captions and on a graphic, so accuracy beats speed here — and --produce runs a repair pass over the transcript before anything is drawn. ossclip setup --model <name> downloads base.en (~142 MB) or medium.en (~1.5 GB) instead.
Quick start
# The whole thing: cut + captions + LLM-planned graphics + cover
ossclip produce input.mp4 --produce -o out.mp4
# Just the cut and captions — no LLM, no network
ossclip produce input.mp4 -o out.mp4
# See what would be cut and why, without rendering
ossclip produce input.mp4 --no-render
# Long-form in, one short out: the strongest ~60s window
ossclip produce podcast.mp4 --produce --clip 60 -o clip.mp4
# Check the whole toolchain first
ossclip doctor
# Open the editor over what a run produced (bare `edit` opens a project picker)
ossclip edit "<work directory>"
# Landscape (YouTube/desktop) instead of the 9:16 default
ossclip produce input.mp4 --produce --aspect 16:9 -o out.mp4
How it works
- Transcribe & repair — whisper.cpp locally, then an LLM pass that fixes mishearings (
cloud code→Claude Code) under a strict gate: a rewrite is refused, a mishearing is fixed, and every repair is logged with what was heard. - Cut — silences and fillers go by measured thresholds (
--cleanup exact…aggressive); the report says what was cut and why. - Captions — word-timed kinetic captions with an active-word highlight, routed around the platform's safe areas, the active graphic, and any text burned into the source.
- Producer — the LLM plans a beat sheet and scenes against the component library, steered by a measured framing brief (a wide band never lands on a close-up), grounded against the transcript so a stat it prints is a stat that was said.
- Verify — layouts that would crop the head are repaired, graphics route around the source's own on-screen text, on-screen copy is reconciled with the captions.
- Render — Remotion, locally, plus a cover JPEG at the OUTPUT's aspect: 9:16 framed for the profile grid's square crop, 16:9 framed as a thumbnail shown whole.
The work directory
Every run writes <input dir>/.ossclip/<name>-<hash>/ — the hash is of the source's CONTENT, so the same footage reuses its cache (rename it and you still land in the same place; --workdir starts a separate project, deleting the directory forces a clean run): the transcript, the analysis, production.json, render-props.json, report.txt, usage.json, the cached LLM plan, and command.json — the recorded invocation the editor's Render button replays. It is a cache: delete it to force a clean run; keep it and re-runs are near-instant.
Each run appends to usage.json — one entry per run with its provider, models, tokens and cost — and stamps the same provenance onto production.json, so a workdir always says who planned it even when a later run answers entirely from cache.
overrides.json — a file the producer never writes. Re-running produce re-plans the video and keeps your edits. Re-rendering from the editor replays the same configuration as the recorded run — flags, aspect, and provider (the resolved provider is pinned into command.json, so an env-detected choice can't drift).Models & cost
Without --llm, ossclip picks: GEMINI_API_KEY → ANTHROPIC_API_KEY → the Claude Code CLI (your Pro/Max subscription — plan usage, not API credits). Calls are tiered: editorial judgement (the beat sheet, transcript repair) goes to the main model; mechanical calls go to a smaller sibling.
▸ llm: 3 calls · 130,749 in / 8,321 out tokens · ~$0.72 of API-rate work, covered by the subscription · 92s
Reported token counts are exact where the provider reports them and marked (est) where derived; a model with no known price gets tokens and no cost guess. Per-call breakdown in report.txt, raw records in usage.json.
Editing
Select, drag, retype — every edit lands in overrides.json, which the producer never overwrites.
ossclip edit "<work directory>" # opens http://127.0.0.1:5174
ossclip edit # bare: pick from recent projects, or browse
Keybinds
Press ? in the editor for this list in-app.
transport
| space | play / pause |
| J / K / L | reverse · pause · forward (tap again to speed up) |
| ← / → | step one frame back / forward |
| ⌘/ctrl + ← / → | jump to and select the previous / next scene |
selection
| click | select scene or element |
| ⌥ + ← / → | select previous / next scene |
| esc | clear selection · close dialogs |
editing
| ⌘/ctrl + B | split the scene at the playhead |
| delete / backspace | delete the selected scene (restorable) |
| ⌘/ctrl + Z | undo |
| ⌘/ctrl + ⇧ + Z | redo (⌘Y works too) |
| ⌘/ctrl + S | save |
| double-click caption word | retype it in place |
| drag element / corner handles | move · resize |
| drag picture | pan the video framing |
view
| ⌘/ctrl + scroll on preview | zoom the view (never edits) |
| ⌥-drag / middle-drag | pan the zoomed view |
| ⌘/ctrl + scroll on timeline | zoom the timeline |
| ? | the in-app reference |
Scene components
Every component sizes its type to the slot it is given (nothing overflows the platform-safe area), animates in with a staggered rise, and leaves through a uniform exit fade. All text on them is editable in place.
Layouts
Layout places the video; the component always renders. Portrait (9:16) and landscape (16:9) each get frame-appropriate geometry — the split axis follows the frame's long edge.
| layout | arrangement |
|---|---|
| full-bleed | the talking head, full frame |
| blurred-behind | speaker blurred and dimmed behind a centred graphic |
| video-top | video band on top, graphic below (portrait) |
| pip-bubble | big graphic + the speaker in a round bubble (roundness and placement editable per scene) |
| graphic-only | the graphic owns the frame |
| lower-third | picture whole, card in the bottom band (broadcast lower third) |
| split-left / split-right | speaker fills one half, graphic the other (landscape; stacks in portrait) |
produce flags
| --produce | run the LLM producer brain. Without it: cut + captions only |
| --clip <seconds> | produce only the strongest ~N-second window of a long take, sentence-snapped (requires --produce; a short-enough source is produced whole). The report says what was chosen and why |
| --intent "<text>" | what the video should be — steers the producer's editorial choices |
| --speaker "<who>" | who is on camera — helps repair recognise a mangled name |
| --cleanup <level> | exact | light | standard | aggressive — how hard to cut silence and fillers |
| --aspect <ratio> | 9:16 (default) or 16:9 — landscape export with landscape-native layouts |
| --source-fit <mode> | cover crops to fill; contain shows the whole frame inset — the landscape-source escape hatch |
| --llm <provider> | claude | claude-cli | gemini | mock |
| --llm-model / --llm-fast-model | override the editorial / mechanical model (same disables tiering) |
| --whisper-model <name> | transcription model for this run (base.en | small.en | medium.en) |
| --no-repair | skip the ASR mishearing repair |
| --scenes <path> | hand-authored scenes JSON — no LLM in the loop |
| --source-is-edited | the source already has burned-in graphics — keep ossclip's off them (also what turns the source-text scan on) |
| --blooper-marker <word> | say the word on camera and the flubbed take is cut, back to the start of the sentence it spoiled. Off unless given |
| --no-cover / --cover <path> | skip, or redirect, the cover image |
| --no-render | stop after writing the props |
| --workdir <dir> | where the cache lives |
Configuration
~/.ossclip/config.json — all optional:
{
"model": "small.en",
"speaker": "Ahsan, host of the Code with Ahsan channel",
"fastModel": "claude-haiku-4-5-20251001",
"ffmpegPath": "ffmpeg",
"whisperPath": "whisper-cli",
"modelDir": "~/.ossclip/models",
"browserExecutable": "/path/to/chrome",
"pricing": { "claude-opus-5": { "inputPerMTok": 15, "outputPerMTok": 75 } }
}
Provider keys come from the environment, and ossclip loads .env files before choosing a provider — first hit wins per key, a real environment variable always beats a file: $OSSCLIP_ENV_FILE → .env walking up from the cwd → ~/.ossclip/.env.
Env vars override the file: OSSCLIP_FFMPEG, OSSCLIP_FFPROBE, OSSCLIP_WHISPER, OSSCLIP_MODEL_DIR, OSSCLIP_MODEL, OSSCLIP_FAST_MODEL, OSSCLIP_SPEAKER, OSSCLIP_BROWSER.
Licensing
ossclip's own code is MIT — see the repo's LICENSE.
Rendering uses Remotion, which is source-available under its own two-tier licence, not MIT: free for individuals, non-profits, and for-profit companies up to the size stated in its terms; larger for-profit companies need a paid Remotion Company Licence. If you use ossclip inside a company, check Remotion's LICENSE — its terms are authoritative.
docs/PHASE1-FINDINGS.md. This page: docs/site/index.html, self-contained, ready for GitHub Pages.