Skip to content
Under the hood

How video editing works in the browser with FFmpeg WebAssembly

Every cut, speed-up and slow-down on this site happens inside the tab you are reading. This is the whole mechanism, from the file picker to the download link, with nothing hidden behind a server.

What FFmpeg WebAssembly is

FFmpeg is the open-source project virtually all video software leans on for decoding, filtering and encoding. It is a C program, normally run from a command line on a server or a desktop. WebAssembly is a portable binary instruction format that lets compiled C and C++ run inside a browser at close to native speed, in the same sandbox as the rest of the page.

It is the same program taking the same arguments, which is why the finished panel will show you the exact command it ran. Swap the file names and it is a command you can paste into a terminal with FFmpeg installed and get the same output — the editor is a front end for arguments, not a black box.

The @ffmpeg/ffmpeg package ships that compiled build. This site vendors two copies of it: a multi-threaded core that uses several CPU cores at once, and a single-threaded fallback. The core is about 32 MB, fetched the first time you apply an edit and cached by your browser afterwards.

Why the page sets COOP and COEP headers

The multi-threaded core needs SharedArrayBuffer to share memory between its worker threads, and browsers only expose that to a page that is cross-origin isolated. So the server sends Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: credentialless with every response. If those headers are missing or a browser does not support them, the editor quietly loads the single-threaded core instead; the same edits work, they just take longer.

Step by step: what happens when you press Apply

  1. 01

    Load the engine

    The WebAssembly core is fetched, or pulled from cache. If SharedArrayBuffer is available the multi-threaded build is used; otherwise it falls back to the single-threaded one automatically.

  2. 02

    Write to the virtual filesystem

    Your video is read into memory as a Uint8Array and written into FFmpeg's in-memory filesystem. This is the moment worth being clear about: there is no network request here, because the "filesystem" is a block of the tab's own memory.

  3. 03

    Build the filter graph

    Your edits are sorted by start time and turned into a list of output segments. Deleted ranges simply produce no segment; sped-up and slowed-down ranges produce a segment with a speed factor. The result is a single filter_complex string.

  4. 04

    Run FFmpeg once

    ffmpeg.exec() is called with that graph. Every edit you stacked is applied in this one pass, so the video is only decoded and re-encoded a single time no matter how many edits there were. Progress events drive the bar in the UI.

  5. 05

    Read the output back

    The finished file is read out of the virtual filesystem, wrapped in a Blob, and exposed as an object URL — which is what the download button points at.

  6. 06

    Clean up

    Input and output are both deleted from the virtual filesystem so the memory is released rather than held until you close the tab.

Converting without editing takes exactly the same path. With a file loaded and no edits on the timeline, choosing a different output format, a resolution cap or mute turns the apply button into "Convert video". The filter graph then holds a single passthrough segment covering the whole clip, and the file is still decoded and re-encoded — a format or resolution change here is never a remux. Mute on its own is the one exception: with no edits, the resolution on Original and MP4, MKV or MOV as the output, the editor first tries a stream copy (-c:v copy -an), which is lossless and near-instant, and falls back to the re-encode only if the source codec cannot go in the chosen container. The converter pages say what that means for each format pair. A run in progress can be stopped with the Cancel button; that halts the engine outright, so it is reloaded from cache on the next run.

How the three operations are built

Delete

Removing a section

FFmpeg's trim and concat filters do the work. The video before the selection and the video after it are trimmed independently, each has its timestamps rebased with setpts=PTS-STARTPTS (audio uses atrim and asetpts), and the two are concatenated back together with the gap gone. Cut a video.

Fast

Speeding up a section

The clip becomes three segments — before, selection, after. The middle one has its presentation timestamps divided by the speed factor with setpts=(PTS-STARTPTS)/speed, which is what makes the frames play faster. Audio goes through atempo, chained when the factor exceeds atempo's 2× per-instance limit. The three segments are then concatenated. Speed up a video.

Slow

Slowing down a section

Identical machinery with a factor below 1, so dividing the timestamps stretches them out instead of compressing them. Audio uses the same atempo value, chained below its 0.5× floor for factors like 0.25. No frames are interpolated — the existing frames are simply held for longer. Slow down a video.

Why re-encoding is unavoidable

All three operations run through filter_complex, which rewrites the decoded video stream. A stream you are filtering cannot also be stream-copied, so the output of any edit or format change is always encoded fresh. The encoder is libx264 at the ultrafast preset with CRF 28 and AAC audio — settings picked because the encode is running on your CPU inside a sandbox rather than on a render farm. Choosing WebM output switches to VP8 video and Opus audio instead, which the WebM container requires.

The alternative would be a keyframe-aligned stream copy: lossless, near-instant, and only able to cut where a keyframe happens to sit. For a screen recording that can be several seconds from where you actually wanted the cut, which is why this editor takes the re-encode.

What leaves your device, and what does not

Your video does not. It is read from disk into the tab, processed in memory, and written back out as a download. There is no upload endpoint in this application to send it to.

What does leave your device is ordinary web traffic: the HTML, CSS and JavaScript for the page, the FFmpeg WebAssembly core on first use, and an analytics request per page view — plus a small ping recording how long the page was open when you leave it. The privacy page says exactly what that analytics call contains — it is a page-view record, not anything about your file.

Going deeper

Longer write-ups of the three things this page summarises: the re-encode trade, the command-line equivalents, and how to verify the no-upload claim yourself.

All video editing guides