DEV Community

Cover image for I Built a Browser Gallery for Every MP4 Frame
Holomatar
Holomatar

Posted on Originally published at holometer-tools.hashnode.dev AI-assisted

I Built a Browser Gallery for Every MP4 Frame

I thought the feature was finished when FFmpeg.wasm could turn an MP4 into a ZIP of PNG files. The conversion worked, but the workflow did not: users still had to download and unpack everything before they could find one useful frame.

So I moved the missing step into the browser. Video Frame Tool now extracts every frame, renders the complete gallery, tracks a selection, and exports one PNG, a selected ZIP, or the entire sequence. The source MP4 stays on the device.

Live demo: https://holometer.net/video-frame-tool/en/

This post covers the implementation choices that mattered most: numbered FFmpeg output, Blob URL lifecycles, selection state, memory costs, and browser-safe downloads.

Why a ZIP-only workflow was not enough

The first version converted an MP4 and returned a ZIP. Technically, that solved frame extraction. In practice, it moved the real work somewhere else: users still had to download the archive, extract it, open a file browser, change to thumbnail view, and search through hundreds of nearly identical images.

The useful workflow is closer to a contact sheet:

  1. choose a video;
  2. inspect every result in sequence;
  3. select only the moments that matter;
  4. download those frames without repeating the conversion.

That sounds like a small interface change, but it affects memory management, progress reporting, object URL cleanup, selection state, and download behavior.

The browser-only extraction pipeline

The conversion runs with FFmpeg compiled to WebAssembly. The selected MP4 is copied into FFmpeg's in-memory filesystem, then every decoded frame is written as a numbered PNG.

The extraction step is conceptually equivalent to:

ffmpeg -i input.mp4 -vsync 0 frame_%08d.png
Enter fullscreen mode Exit fullscreen mode

Eight-digit filenames keep lexical and numeric order identical. That avoids the common frame_1, frame_10, frame_2 sorting problem when the files are displayed or passed back into an encoder.

After FFmpeg finishes, the app reads the PNG files, sorts them by frame number, creates browser object URLs, and adds them to the gallery. The source video and extracted PNGs are not sent to a conversion server.

The first run downloads roughly 32 MB of FFmpeg WebAssembly assets. That cost is shown before conversion because hiding a large first-load dependency makes a local tool feel broken on a slow connection.

A real test: 36 full-resolution frames

For a repeatable check, I used a 1.2-second, 1080×1920 MP4. The browser extracted and displayed 36 PNG frames. Together, those PNGs occupied 50.4 MB in memory even though the source MP4 was only about 140 KB.

That difference is the central constraint of client-side frame extraction: compressed video is small, while decoded lossless frames are not.

The gallery reports the frame count, dimensions, and total PNG size before the user downloads anything. In this test I selected three frames from different moments, then confirmed that the interface offered all three paths:

  • download one frame as a PNG;
  • download the selected three as one ZIP;
  • download all 36 as one ZIP.

Every extracted video frame displayed in a browser gallery with one-frame, selected-ZIP, and all-ZIP download controls

Keeping hundreds of frames responsive

Every gallery thumbnail is backed by a Blob URL rather than a base64 string. Base64 expands the data and makes large DOM attributes expensive. Blob URLs let the browser keep the binary data outside the HTML while normal <img> elements render the previews.

Those URLs still need a lifecycle. When a new video is chosen, the old gallery is cleared and its object URLs are revoked. Otherwise, repeatedly converting videos in one tab would leave the previous PNG data in memory.

The selection model is deliberately simple: a Set stores the selected frame names, and the buttons derive their labels and enabled state from that set. “Select all” adds the complete ordered list; “Clear selection” empties it. Building the ZIP happens only after the user chooses a download action, so merely browsing the gallery does not create a second full copy of every PNG.

Why individual browser downloads need special handling

Triggering 300 separate downloads is hostile to both the user and the browser. It can produce hundreds of permission prompts or be blocked as abusive behavior.

Video Frame Tool therefore uses three explicit operations:

  • one PNG for a single useful moment;
  • selected ZIP for a small reference set;
  • all ZIP for animation and batch-processing work.

This also makes the tool useful outside illustration. A developer can pick state-transition frames for a bug report, an animator can compare spacing, and a pixel artist can export a short sequence for cleanup.

Loading a public X video without pretending it is private

The local-file path is fully client-side. The X URL path has a necessary network boundary: the app sends the public post ID to the FxTwitter API to resolve the available media URL, then downloads the selected public MP4 from X's delivery host. Frame conversion happens locally after that download.

Private, deleted, or age-restricted posts are not supported. A public URL also does not grant reuse rights, so the page tells users to process only videos they own or have permission to use.

Turning image sequences back into MP4 or GIF

The reverse path accepts individual images or a ZIP. Filenames are naturally sorted so frame_2.png stays before frame_10.png. Images with different aspect ratios are not stretched; each frame is fitted inside the first image's canvas and padded with black or white.

MP4 output uses H.264 with a broadly compatible pixel format. GIF output first builds a palette, then applies it during encoding to avoid the worst color banding. Users can choose common frame rates such as 8, 12, 15, 24, 30, or 60 fps.

Limits that still matter

This is not a replacement for a desktop video editor. Long 4K clips can exhaust browser memory, and PNG extraction is intentionally lossless and therefore large. The safest workflow is to begin with a short clip, confirm the result, and only then increase duration or resolution.

The tool also does not repair motion blur, camera shake, compression artifacts, or hidden body parts. It exposes the frames that already exist; it does not invent missing visual information.

Try it

Video Frame Tool is free, requires no account, and works in a modern desktop browser:

https://holometer.net/video-frame-tool/en/

If you try it, I am especially interested in the point where the workflow becomes uncomfortable: extraction time, gallery size, selection, or ZIP creation. Those limits are more useful than a successful five-frame demo when deciding what to optimize next.

Top comments (0)