All work

Framegrab

A local-first video frame extractor: native-resolution frames pulled entirely in the browser, nothing uploaded.

Kind
Tool
Role
Solo: design and engineering
Period
2026
Status
Live
Stack
Vite, React 18, TypeScript, Zustand, Web Workers

How do you pull clean, native-resolution frames out of a video without sending the file anywhere?

Framegrab: a dashed drop zone for a video file, above three notes on local processing.
Framegrab2026

No backend, by design

The video is read with File + ObjectURL and never leaves the device: no API, no database, no analytics. That makes privacy an architectural property rather than a promise: there is no server that could receive the file.

Frames are exported individually or as a ZIP, encoded in the browser.

Everything on-device

Decoding, frame analysis and encoding all happen in the browser; the only server involved is the one serving the static build.

Client CPU and memory carry the load, but there is no infrastructure to run, and no upload to leak.

Native resolution, flat memory

Frames are drawn to a canvas sized to the video’s real dimensions, not the CSS player size, so every export is native resolution. Batch extraction seeks sequentially, holds one full-resolution blob at a time, commits results to the store in chunks, yields to the event loop, and can be cancelled mid-run.

Analysis runs off the main thread in a worker that only receives ImageBitmaps; the video file itself is never copied into worker memory, which keeps long and 4K videos from crashing the tab.

Sequential seeks, chunked commits

Extraction does one frame at a time and commits state in chunks, so memory stays flat and the interface keeps responding instead of freezing on the first big video.

Deliberately slower than maximally parallel extraction: for a local tool the worst failure is a crashed tab, not patience.

Find Best Frames

A two-phase finder surfaces the shots worth keeping without allocating a single full-resolution image. The scan probes up to 150 evenly spaced timestamps at thumbnail size and scores them in the worker on sharpness by Laplacian variance, exposure quality, and a 64-bit perceptual hash.

Selection normalises sharpness against the scan’s own maximum, blends in exposure, drops blurry frames using an adaptive floor from the scan median, removes near-duplicates by hash distance, and enforces a minimum time gap between picks. Only the winners are extracted, at native resolution, when they are saved.

Scoring is a plug-in, not a pipeline

Analysis sits behind a FrameAnalyzer interface, so new signals (faces, composition, duplicate detection) can be added without touching the extraction pipeline.

A little indirection up front, in exchange for isolating the part most likely to change.

Want something like this built?

Email me