All posts
FFmpegEngineering16 Sep 2026 · 3 min read

Shipping FFmpeg inside a desktop app

JPJean Perez

A desktop app that bundles FFmpeg ships a specific binary, built a specific way, under a specific license. Each of those is a decision that has to be made before the app can process video offline at all.

Static, so nothing else has to be installed

FFmpeg can be built two ways. A shared build links against separate library files, libx264.so, libavcodec.so, and so on, that have to exist on the machine at runtime and match what the binary expects. A static build compiles all of that into one self-contained executable with no external dependencies to go missing. For a library you control on your own servers, shared can make sense: smaller footprint, shared updates across binaries that all link the same library. For a binary you're handing to someone else's machine, where you have no say over what's already installed there, static is what makes "download the app and it works" true on the first run instead of a support ticket about a missing .dll.

LGPL and GPL aren't the same binary

FFmpeg's own code is LGPL by default. Several of the components people actually reach for, libx264 for H.264 encoding among them, are GPL, and turning them on with --enable-gpl makes the whole compiled binary GPL rather than LGPL. This is general, not legal advice, but the practical shape of it is worth knowing: an LGPL build is the easier one to distribute alongside closed-source software, since LGPL is written to allow that. A GPL build carries GPL's stronger copyleft, and the usual pattern is to treat a GPL FFmpeg binary as a separate program the app spawns as a subprocess, never code linked into the app itself. Shipping that binary still comes with GPL's obligations for FFmpeg's own source.

Which one you get depends on how a specific prebuilt binary was compiled. BtbN publishes both GPL and LGPL static builds of FFmpeg for Windows and Linux, so picking LGPL there is a real option. evermeet.cx, the usual source for prebuilt macOS FFmpeg binaries, doesn't publish an LGPL static build for macOS at all, only GPL. Shipping FFmpeg on macOS from that source means shipping a GPL binary whether or not Windows and Linux, from a different source, ship LGPL ones. That mismatch across platforms is still an open decision for BeemMeUp.

Pinning the exact binary, not "latest"

A build pipeline that downloads "the current FFmpeg build" at build time will, eventually, ship a different binary than the one it shipped last time, with no code change in the app to explain it. Encoders can appear, disappear or change behaviour between builds. The fix is pinning to one specific build by its sha256 checksum: download that exact file, verify the hash matches before bundling it, and only move to a newer build on purpose.

# Recorded once, when the build is chosen and tested
FFMPEG_SHA256="<sha256 of the exact archive>"

# Every build: download, then refuse anything that doesn't match
echo "$FFMPEG_SHA256  ffmpeg.zip" | sha256sum --check

Verifying the checksum is what turns "we downloaded a build" into "we downloaded the build we tested against." Without it, an upstream file changing underneath a URL is indistinguishable from a deliberate upgrade until something breaks.

Keeping the binary outside the archive

Electron packs an app's source into a single archive file for distribution rather than shipping thousands of loose files. That's fine for source and assets the app only ever reads. An executable is different. The OS runs a binary from a real file on a real path, so a binary inside the archive has to be extracted to a temporary file before it can run, or it can't run at all, depending on how it's called.

The usual practice is to exclude bundled executables from the archive and ship them alongside it as ordinary files, so the app runs them straight from their real path. It's also easy to get wrong without noticing, because the app still builds. In BeemMeUp's case a missing ignore rule had sealed about 150 MB of FFmpeg binaries into the archive; excluding them took the archive from 168 MB to 8.4 MB.

Of the four, only the macOS license is still open. The rest are settled, and the checksum pin keeps them settled until an upgrade is a decision rather than an accident.