While rebuilding natehaskins.com I started recording tutorial footage again. The obvious move was to ask a cloud model to caption each file — but a twenty-minute screen recording is expensive to process that way, and I would be paying again every time I re-exported.

Why local first

Deterministic work belongs on your machine. Transcription, ffmpeg filters, and file copies are repeatable scripts. Judgment calls — how to phrase a blog post, what to cut from a tutorial, how to frame a DevRel page — are where I still want a language model in the loop.

So I asked Cursor to scaffold a small Python tool in this repo: faster-whisper for speech-to-text, ASS for styled captions, ffmpeg for burn-in. The agent handled Dockerfile updates, gitignore rules for tmp/ and .mp4 files, and npm scripts to run it the same way every time.

01 Whisper transcript
02 ASS captions
03 ffmpeg burn-in
04 YouTube listing

What lives in the repo

tools/caption/caption.py does the heavy lifting. npm run caption -- path/to/video.mp4 transcribes, writes captions.ass, and outputs a new MP4 with readable bottom-center text sized for mobile. Work artifacts land in tmp/.caption-<name>/ — transcript JSON, ASS, listing draft, and thumbnail — all gitignored.

For YouTube, npm run youtube -- prepare runs transcription (or reuses it), builds youtube-listing.json from the transcript — title, description, chapter timestamps — and generates a simple thumbnail.jpg (blurred frame + title overlay). npm run youtube -- upload publishes the video and sets the thumbnail when the file is present.

Dev container matters

ffmpeg, Python, and Whisper dependencies do not belong on my host OS if I can avoid it. The .devcontainer Dockerfile installs what the caption tool needs; Cursor updates it as the scripts evolve. Rebuild the container, run the same npm command — that is the whole portability story.

Chapters

Select a chapter to jump the video above directly to that moment.

What comes next

Phase one was captions. Phase two is wiring the output into this site automatically — upload to R2, create the blog post, sync the Trello card. That is the same pattern as the rest of this rebuild: local tools for bytes, agents for structure, git for history.

If you are recording tutorials in Cursor and burning tokens on every export, try pushing the ffmpeg-shaped work local first. You only need to build the pipeline once.