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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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
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.
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.
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.
- How to trim a video without losing qualityTrimming only loses quality if the video gets re-encoded. Here is when that happens, when it does not, and the trade you are making either way.
- FFmpeg: trim a video without re-encodingThe exact -ss, -t and -c copy commands for a lossless trim, why the flag order matters, and how to cut a section out of the middle with the concat demuxer.
- How to check if an online video editor uploads your fileEvery browser tool claims to be private. You can verify the claim in about ninety seconds with the tools already in your browser. Here is exactly how.