Self-hosted service
alfg/ffmpeg-commander avatar
alfg/ffmpeg-commander

ffmpeg-commander: a browser workbench for FFmpeg commands, and the daemon behind it

A web-based GUI for quickly generating common FFmpeg command-line operations.

1,128 stars115 forksTypeScriptMIT

At a glance

What is it?
ffmpeg-commander is a React and Vite web UI that turns FFmpeg encoding options into command lines, with presets, shareable URLs and an optional ffmpegd daemon that can run the encode. It deliberately covers common operations rather than the whole FFmpeg surface.
Who is it for?
Adopt ffmpeg-commander if you mostly encode video and audio and want a form instead of memorising FFmpeg flags, and if you can live with the assumptions the tool bakes into each preset. Do not adopt it for multi input or map work, since those sit in the project TODO list, and do not treat its output as authoritative, because the project states that generated options may vary with your FFmpeg version and build.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 22 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A form in front of an intimidating flag set

FFmpeg has a large option set, and the pitch of ffmpeg-commander is that most of that surface is not needed to produce a sane encode. The project describes itself as a simple web UI for generating common FFmpeg encoding operations, and its Why section is blunt about the motivation: the options are intimidating, so the goal was a simple interface inspired by HandBrake. The author also says the tool does not cover all FFmpeg options and that some assumptions are made when generating the output.

That last sentence is the most useful line in the documentation, and it should shape how anyone evaluates the project. This is a command generator with opinions, not a thin wrapper over the full FFmpeg grammar. It is for the person who knows what an H264 ladder at CRF means and wants the flag order spelled out, and for the person who does not know yet and wants a reasonable first attempt. Both users end up reading the generated command before running it.

Presets are the unit of work, and they are grouped by intent

The Preset picker is where the project puts its domain knowledge. The recipes are grouped rather than listed as a flat menu: General covers H264 and VP9 ladders from 360p to 1080p with CRF and fixed bitrate; Web & Streaming covers H264 and HEVC MP4 with faststart, VP9 WebM constant quality and AV1; Social & Mobile covers vertical 9:16 1080x1920 and square 1:1 1080x1080; Archive & Quality covers visually lossless H264, 10-bit HEVC archive and 4K UHD; Restore & Cleanup covers deinterlacing DVD or broadcast, denoise and deband, and stabilising action cam footage; Audio Only covers MP3, M4A and AAC, FLAC and a mono podcast mix; Utility covers remux to MP4, stripping audio, a ten second preview clip, timelapse and slow motion.

Two details make the presets more useful than a static list. Your own presets are saved to local storage, so custom recipes survive a browser restart. And every setting is also encoded in the URL, so a tweaked recipe can be handed to a colleague as a link. The combination means a preset library lives in one person's browser while a specific configuration travels through a URL, and the URL is the shareable unit.

Command generation is kept away from the React components

The source layout shows a deliberate separation between the parts that produce a command and the parts that draw the form. src/lib/ holds framework-agnostic modules for command generation, the URL contract, presets, storage and the ffmpegd client, with the test suite in src/lib/__tests__. src/components/ contains the React components, split into ui/ primitives and sections/ form groups, and src/hooks/ holds the state for the form, presets, theme and the ffmpegd connection.

That layout has a practical consequence for anyone who wants to reuse the command builder inside another tool. The generation logic and the URL contract are not entangled with React, so they can be exercised without a browser, and the test suite lives next to them. The tooling around it is modern and small: package.json declares React and React DOM at 19, Vite 8, Tailwind CSS 4 with the Vite plugin, Vitest 5 for the tests, oxlint for linting and TypeScript 6. The project is written in TypeScript, with the usual trio of tsconfig.json, tsconfig.app.json and tsconfig.node.json splitting application and tooling code.

ffmpegd is what turns a generator into a GUI

On its own, ffmpeg-commander produces text. The ffmpegd companion application is what closes the loop: it exposes a websocket server that accepts encode tasks and sends realtime progress updates back to the browser, so the page behaves like a GUI for ffmpeg rather than a command clipboard. The client for it lives in src/lib/ alongside command generation, and the connection state has its own hook.

The integration is where the interesting constraints sit. The app connects to ffmpegd at localhost:8080, and ffmpegd only accepts connections from a fixed list of origins, so the site you load ffmpeg-commander from has to be on that list. When you run the Vite dev server, the config proxies /ws and /files to localhost:8080 and presents an origin that ffmpegd accepts, precisely because the dev server's own port is not on the list. A daemon on another host or port is reached by turning on ffmpegd in the Options tab and entering its address under Daemon address, for example mybox:9000 or an https:// address, and that value is stored in the browser's localStorage.

There is one browser rule worth internalising: an https page may only open a plain ws:// connection to localhost. Reaching a daemon on another machine from https://ffmpeg-commander.com means serving the daemon over https, for example behind a TLS reverse proxy, and entering an https:// address.

Running it locally takes two commands

The project is a Vite app, so the development loop is short. Node 20 or newer is stated in the README, with NVM recommended for managing versions. Clone, install, start the dev server:

bash
npm install
npm run dev

Then load http://localhost:5173/ in the browser. The same README documents the check-and-build trio, which maps onto the scripts declared in package.json:

bash
npm test
npm run lint
npm run build

Under the hood npm test runs vitest run, npm run lint runs oxlint, and npm run build runs tsc -b before the Vite build, so type checking is part of the build rather than a separate step. Deployment is to GitHub Pages through the .github/workflows/github-pages.yml workflow on every push to master, with a manual path as well:

bash
npm run deploy

That script builds and then publishes the dist directory with gh-pages. The public directory and index.html at the root are the static shell Vite serves, and the homepage field in package.json points at https://ffmpeg-commander.com.

The Node version requirement disagrees with itself

Here is a concrete trap for a new contributor. The README states that Node 20 or newer is needed and recommends NVM. The package.json in the repository declares an engines field of node >=22.0.0. Both statements cannot be the contract, and the machine readable one is the one npm checks on install, so a contributor on Node 20 can read the README, run npm install, and get a warning or failure that the documentation never mentioned.

The same tension exists in the feature set. The TODO list names two gaps: support for multiple inputs and the map option, and expanded filter options. Both are things real encoding work runs into quickly, especially the map option, which decides which streams end up in the output. The project also warns that generated options may vary based on your FFmpeg version and build configuration, which means a command generated on one machine is not guaranteed to be valid on another. And the ffmpegd pairing rule, where releases sharing a major.minor version speak the same payload format, is another place where a version mismatch turns into a confusing failure rather than a clear error.

MIT licensed, no tagged releases, last push on 2026-10-01

The licence is MIT, with the text in the LICENSE file at the root, so the obligations for a project that vendors or forks the code are short: keep the notice and the licence text. The repository is not archived, and the last push was on 2026-10-01, which puts the code in recent shape.

What the project does not have is a release history. The repository has no GitHub releases, and the version lives in package.json, currently 2.1.0. That matters for two reasons. The public site is deployed from pushes to master rather than from a tag, so there is no frozen artifact to point a colleague at. And the ffmpegd pairing rule depends on major.minor version numbers, so a web app that moves with master and a daemon released separately can drift apart in exactly the place where the websocket payload format is defined.

For a project of this size the absence of releases is a small tax rather than a real risk, but it is the kind of detail that bites when a team tries to standardise on it. The README also points to a customised Docker build of FFmpeg in the docker-ffmpeg repository, which is the more useful companion for anyone who needs a specific encoder build rather than the one their system ships.

Editorial conclusion

Adopt ffmpeg-commander if you mostly encode video and audio and want a form instead of memorising FFmpeg flags, and if you can live with the assumptions the tool bakes into each preset. Do not adopt it for multi input or map work, since those sit in the project TODO list, and do not treat its output as authoritative, because the project states that generated options may vary with your FFmpeg version and build. Verify the Node requirement first: the README says Node 20 or newer while package.json declares an engines field of node >=22.0.0, so build on Node 22 or newer and confirm the ffmpegd origin allowlist includes wherever you host the page.

Frequently asked questions

Does ffmpeg-commander encode video itself?

Not on its own. It generates FFmpeg command lines, and the optional ffmpegd companion application runs them over a websocket connection at localhost:8080 and streams progress back to the browser.

How do I run ffmpeg-commander on my own machine?

Run npm install and npm run dev from a clone, then open http://localhost:5173/ in the browser. The project is built with React, Vite and Tailwind CSS, and deploys to GitHub Pages on every push to master.

Which Node version does ffmpeg-commander need?

The README says Node 20 or newer and recommends NVM, but package.json declares an engines field of node >=22.0.0. Building on Node 22 or newer avoids the mismatch.

Can I share a specific ffmpeg-commander configuration?

Yes. Every setting is also encoded in the URL, so a tweaked recipe can be shared as a link, while your own presets are saved to local storage in the browser.

How do I point ffmpeg-commander at a remote ffmpegd daemon?

Turn on ffmpegd in the Options tab and enter its address under Daemon address, for example mybox:9000 or an https:// address, and it is saved in the browser's localStorage. ffmpegd only accepts connections from a fixed list of origins, so the page you load must be on that list, and a remote daemon needs to be served over https.

What licence is ffmpeg-commander released under?

MIT, with the licence text in the LICENSE file at the root of the repository. The project is also published as a static site on GitHub Pages at https://ffmpeg-commander.com/.

Official sources

  1. alfg/ffmpeg-commander on GitHub
  2. Issues
  3. License: MIT
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/alfg-ffmpeg-commander.svg)](https://hysenlabs.com/projects/alfg-ffmpeg-commander)

Was this useful?