METHODOLOGY · LOCALCONVERT
How LocalConvert converts files
A technical, honest description of what happens between drop and download — the libraries, the threat model, the limits, and how to verify any of it yourself.
“In-browser” means a specific thing.
Marketing copy on this site says conversion happens “in your browser.” That is a shorthand for a precise architecture, and it’s worth unpacking.
When you load this page, your browser performs the usual handshake: HTML, CSS, and JavaScript are fetched from our static host. When you begin a conversion, additional WebAssembly binaries — FFmpeg, ImageMagick, or Pandoc — are fetched from the same origin and handed to the browser’s WASM runtime. Those binaries are then instantiated inside a Web Worker, a sandbox that runs off the main thread and has its own scope, its own memory, and its own postMessage channel back to the page.
The file you drop is read into an ArrayBuffer on the page, transferred to the worker, and passed to the WASM module
through its in-memory filesystem. The encoder writes to that same
filesystem, the result is transferred back to the page, and a Blob URL is created for the download.
At no point does the page issue an XMLHttpRequest or a fetch() that includes the file payload. The only network activity after the
initial bundle load is whatever the browser does on its own —
font requests, image preloads, the rest. Your file is not in that
traffic.
What we promise, what we don’t.
Trust models on the open web are conditional. Here is the conditional trust we are asking for, in plain terms.
Hosting. The static site is delivered by a commercial CDN (Cloudflare and Netlify, depending on edge). The HTML, JavaScript, and WASM binaries you receive are signed by TLS and have integrity guarantees against an in-flight attacker. They are not signed against a malicious push to our hosting account. If our hosting were compromised, an attacker could ship JavaScript that exfiltrated your file before the WASM module touched it.
Mitigations. A content-security policy restricts the origins the page can talk to, which limits where an attacker could send anything they stole. Subresource Integrity (SRI) is a stronger guarantee — the page would refuse to execute scripts whose hash doesn’t match a pinned value — and we’ll be honest: we are not running SRI today. Reproducible builds, which would let you re-derive our bundle from source, are also on the roadmap and not yet shipped. We name these gaps because hiding them would undermine the rest of this page.
Browser sandbox. WebAssembly running in a worker cannot read your disk, your clipboard (unless granted), or any other site’s storage. It cannot persist a file outside the tab except by triggering a download dialog, which always requires user action. This is enforced by the browser, not by us.
Analytics. We run Plausible, self-hosted, for completely anonymous aggregate statistics. No cookies, no localStorage, no IP retention — Plausible hashes the IP with a daily-rotating salt and discards the original. We see page-view counts and referrers in aggregate. We do not see who you are, and we never see your files.
Which encoder runs for what.
Each output format maps to a specific encoder. The quality knobs you see in Settings pass straight through to the underlying tool — we don’t add a custom encoder, and we don’t hide the primitives.
- Images ImageMagick’s native encoders. JPEG quality (1–100), PNG compression level, WebP quality and lossless toggle, AVIF speed and quality. ICC profile preservation is on by default.
- Audio FFmpeg with
libmp3lame,aac,libopus,flac, andpcm_s16le. Settings exposes bit rate and sample rate. - Video FFmpeg with
libx264for H.264,libvpx-vp9for VP9, andlibwebmmuxing for WebM. CRF, preset, and target bitrate are exposed. - Documents Pandoc’s built-in readers and writers, invoked with default options for round-trip safety. We do not currently expose Pandoc filters from the UI.
How we get 4–8× on video.
Video encoding is embarrassingly parallel: a modern x264 encode can
saturate every core on your laptop and still want more. WebAssembly
supports threads through SharedArrayBuffer,
but that API was disabled in browsers after Spectre. To re-enable it,
a page must prove it is in a cross-origin isolated context.
In plain terms: the browser has to be sure that no other site’s resources are sharing the same process. We satisfy that by sending two headers on every response.
Cross-Origin-Opener-Policy: same-origin— isolates the browsing context from windows opened by other origins, and from windows that opened us.Cross-Origin-Embedder-Policy: credentialless— requires that every sub-resource we load is either same-origin, explicitly opted in via CORP, or fetched without credentials.
Once both headers are in effect, the browser flips crossOriginIsolated to true, and SharedArrayBuffer becomes available. The multi-threaded FFmpeg build spawns a pool of
workers, each pinned to a CPU core, and the encode scales close to
linearly with core count. On an 8-core M-series Mac, a one-minute
1080p H.264 clip that takes around 40 seconds single-threaded finishes
in roughly six.
The same isolation that unlocks the speed also strengthens the sandbox: a cross-origin iframe cannot peek into our memory, and we cannot reach into anyone else’s.
The honest bits.
Running native tooling in a browser tab has real ceilings. We’d rather you hit them with eyes open than be surprised.
Memory. Browser tabs are sandboxed to a few gigabytes of address space — the practical ceiling varies by browser and device. Files larger than 1–2 GB will sometimes succeed and sometimes fail with an out-of-memory error from the WASM allocator. A desktop with FFmpeg or HandBrake will always be the right tool for a feature-length 4K render.
Format coverage. Some legacy or niche formats are
partially supported. RealMedia (.rmvb)
decoders are flaky in WASM builds. Windows ICO files that contain
multiple cursor frames can lose the secondary frames in some round-trips.
DRM-protected inputs do not work; that is a deliberate omission, not
a bug.
Quality presets. The encoder presets are FFmpeg’s
own — ultrafast through veryslow,
CRF 0–51, and so on. We do not ship a custom rate-distortion
tuner; if you know how x264 behaves, it behaves the same here.
Cold start. The first conversion in a session pays the cost of downloading and instantiating the WASM module. After that the binary is in the browser cache and the worker stays warm.
Mobile devices. Phones and tablets work, but the ceiling is lower. iOS in particular caps WebAssembly memory more aggressively than desktop Safari, so a video that converts cleanly on a laptop may run out of memory on a phone. We do not warn for this proactively because the limit varies by device generation and browser version; if a mobile conversion fails, a desktop retry is almost always the answer.
Hardware acceleration. Browser WebAssembly does not expose GPU encoders. Native FFmpeg on a desktop can use NVENC, Intel QuickSync, or VideoToolbox; the WASM build runs on the CPU only. That’s a 2–5× performance gap for video relative to a native install on the same machine, and it’s a permanent structural limit of the platform, not a bug we can fix.
Trust, then verify.
You don’t have to take our word for any of this. The Network panel in your browser’s developer tools shows every request the page makes.
Open DevTools (F12 or Cmd‑Option‑I), switch to the Network tab, clear the log, and run a conversion. You’ll see the initial bundle — HTML, JS chunks, the WASM blob — arrive once. After that the log should be silent except for ambient things like favicon refreshes. There is no request whose body contains your file. If you are paranoid, filter by request size: nothing the size of your input ever leaves.
You can also pull the plug. Once the WASM module is loaded, switch your machine to airplane mode and run a conversion. It will succeed. That is the simplest end-to-end proof that the work is local.
For the truly paranoid: the source code is on GitHub and the production bundle is shipped without obfuscation. The minified JavaScript can be unminified by any modern formatter; the WASM binaries are derived from the upstream FFmpeg, ImageMagick, and Pandoc projects. You don’t have to take that on faith either — the build steps are public and the resulting hashes can be checked against your own re-build.
Process Monitor on Windows, Activity Monitor on macOS, or lsof on Linux will confirm at the OS level that the browser tab is not
opening sockets while a conversion is in progress, with the obvious
exception of background telemetry the browser itself emits. None of
that traffic carries your file payload.
Found a bug?
Issues, edge cases, and feature requests live on GitHub. A good bug report includes the input format, the target format, your browser and operating system, and — if you can share it — a small sample file that reproduces the problem.
github.com/VERT-sh/VERT/issues
Last updated 2026-05-27