You are a Node.js developer. You need to convert a video file to MP4. You search for "fluent-ffmpeg convert to mp4" and land on the fluent-ffmpeg npm documentation. You want a working code snippet that takes an input file and produces an MP4 output.
This post gives you that snippet. It also explains why the same code that works on your laptop will break in production on serverless platforms, and shows you a hosted alternative that gives you the full power of ffmpeg without needing to install or manage a binary.
TL;DR
- fluent-ffmpeg is the most popular npm wrapper for ffmpeg with over 8,300 GitHub stars and 2,000+ npm dependents.
- Converting a video to MP4 takes about five lines of code.
- The package requires the ffmpeg binary to be installed on every machine that runs your code (local, CI, production).
- fluent-ffmpeg is deprecated and archived as of May 2025. No more updates, bug fixes, or security patches.
- Serverless platforms (Vercel, AWS Lambda, Cloudflare Workers) cannot run the ffmpeg binary without fragile workarounds.
- Very Good FFmpeg offers a TypeScript SDK that runs your exact ffmpeg commands on dedicated infrastructure with no binary to install, a 6-hour job timeout, and 16 dedicated CPU cores.
What is fluent-ffmpeg and why do Node developers use it?
fluent-ffmpeg is an npm package that wraps the ffmpeg command-line tool into a chainable JavaScript API. Instead of spawning a child process with a raw shell command, you call methods like .videoCodec(), .audioCodec(), and .output() on a fluent interface.
Node developers use it because it abstracts away shell scripting. You write JavaScript, not bash. The package handles argument escaping, process lifecycle, and event-driven progress reporting. For years it has been the de facto standard for video processing in Node.js, with over 8,300 GitHub stars and 2,056 npm dependents at the time of writing. The latest version is 2.1.3, published roughly two years ago.
Typical use cases include transcoding user uploads, generating thumbnails, extracting audio, compressing video files, and converting between container formats.
How does ffmpeg convert video to MP4 in general?
ffmpeg is the Swiss-army knife for media processing. The most common MP4 output uses the H.264 video codec and AAC audio codec. This combination plays in every browser, on every device, and on every streaming platform.
A basic ffmpeg CLI command looks like this:
ffmpeg -i input.mov -c:v libx264 -crf 23 -c:a aac output.mp4The -c:v libx264 flag sets H.264 video encoding. The -crf 23 flag controls quality (lower is better, 18-28 is the typical range). The -c:a aac flag sets AAC audio encoding. The output filename extension .mp4 tells ffmpeg which container format to use.
fluent-ffmpeg wraps each of these flags into a method call so you can stay in JavaScript.
How do you convert a video to MP4 with fluent-ffmpeg?
Here is the working snippet you came here for:
import Ffmpeg from "fluent-ffmpeg"
Ffmpeg("input.mov")
.videoCodec("libx264")
.audioCodec("aac")
.output("output.mp4")
.on("end", () => console.log("Conversion complete"))
.on("error", (err) => console.error("Conversion failed:", err))
.run()This code reads input.mov, transcodes it to H.264/AAC, and writes output.mp4. The API is event-based. You listen for the end event to know when the conversion finishes, and the error event to catch failures.
You can add more options to the chain:
Ffmpeg("input.mov")
.videoCodec("libx264")
.audioCodec("aac")
.size("1920x1080")
.fps(30)
.outputOptions("-movflags +faststart")
.output("output.mp4")
.on("progress", (info) => {
console.log(`Processing: ${info.percent}% done`)
})
.on("end", () => console.log("Done"))
.on("error", (err) => console.error(err))
.run()The .size() method sets the output resolution, .fps() sets the frame rate, and .outputOptions() passes raw flags directly to ffmpeg. The -movflags +faststart flag relocates the moov atom to the beginning of the file so the video can start playing before the full file downloads. This is essential for web-delivered MP4 files.
This code works well on your local machine and on traditional VPS servers where you control the environment.
What is the hidden cost of using fluent-ffmpeg?
fluent-ffmpeg is a JavaScript wrapper. It does not include the ffmpeg binary. You must install ffmpeg on every machine that runs your code. This is the single biggest source of confusion for new users who expect npm install to be the only setup step.
Here is what that means in practice:
- Your development machine. You run
brew install ffmpeg(macOS),apt install ffmpeg(Linux), orchoco install ffmpeg(Windows). - Your CI pipeline. You add a step to install ffmpeg in your CI image, or you use a pre-built Docker image that includes it.
- Your production server. You add ffmpeg to your Dockerfile or install it via the package manager on your VPS.
If ffmpeg is not installed, npm install will not fail. The error only surfaces at runtime when you try to run a command. fluent-ffmpeg calls the binary via child process, and if the binary is not in PATH or the FFMPEG_PATH environment variable is not set, you get a cryptic error.
There is also the version drift problem. Your development machine might have ffmpeg 6.0, your CI image has 5.1, and your production server has 7.0. Different ffmpeg versions support different codecs, filters, and flags. A command that works locally can fail in production because of a missing codec or a changed flag syntax. This is a common source of the "ffmpeg exited with code 1" error that appears in Stack Overflow questions, where the same fluent-ffmpeg code produces different results across environments.
On top of that, the binary is large. A static ffmpeg build is roughly 50 MB. If you deploy to Docker, that adds 50 MB to your image. If you deploy to AWS Lambda, your deployment package grows past the practical limit for fast cold starts. Every platform that runs your Node.js code must also carry a copy of ffmpeg.
Is fluent-ffmpeg still maintained?
No. The npm registry shows a deprecation notice: "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info."
The GitHub repository was archived as read-only on May 22, 2025. Issue #1324, titled "Phasing out fluent-ffmpeg", was opened shortly before the archive and confirmed that the project is end-of-life.
There will be no new releases, no bug fixes, and no security patches. New versions of Node.js or ffmpeg may introduce breaking changes that the package will never adapt to.
Over 2,000 npm packages still list fluent-ffmpeg as a dependency. If you are starting a new project today, building on a deprecated and archived wrapper carries real risk. The code will keep working for now, but you are depending on software that will never receive another update.
Can you use fluent-ffmpeg on serverless platforms?
Generally, no. Serverless platforms have strict limits that make the ffmpeg binary difficult or impossible to run.
| Platform | Problem |
|---|---|
| Vercel | Serverless Functions run in a read-only filesystem beyond /tmp. No ffmpeg binary is available. You would need to bundle a static ffmpeg build (50+ MB) and extract it to /tmp at cold start, which adds seconds to every invocation. |
| AWS Lambda | Maximum execution timeout is 15 minutes. A video longer than a few minutes will hit this ceiling. You can bundle a ffmpeg binary as a Lambda layer, but the layer is large and cold starts are slow. |
| Cloudflare Workers | Workers run on isolates, not containers. There is no subprocess API at all. You cannot run ffmpeg on Cloudflare Workers. |
| Firebase Cloud Functions | Maximum timeout is 540 seconds (9 minutes). A 5-minute video at high quality will not finish in time. |
| Render.com | The ffmpeg binary is not available in the build environment. Developers report struggling to install it during deployment. |
Workarounds exist for some of these platforms. You can bundle a statically compiled ffmpeg binary in a Lambda layer. You can use the /tmp directory on Vercel for temporary files. But these workarounds are fragile, add complexity, and break without warning when platforms update their runtimes.
A Stack Overflow question with 18 votes describes this exact problem: a developer trying to transcode WebM to MP4 in Firebase Cloud Functions keeps hitting the maximum timeout for larger videos. The accepted answer involves splitting the work into multiple functions, which adds orchestration complexity. Another developer on Reddit reported that fluent-ffmpeg and yt-dlp would not work on Render.com because the ffmpeg binary was missing from the build environment entirely, and there was no clean way to install it during deployment.
What is the no-binary alternative to fluent-ffmpeg?
Very Good FFmpeg is a hosted ffmpeg API designed for developers who need ffmpeg in production without managing infrastructure. It provides a TypeScript SDK that accepts your exact ffmpeg flags and filter syntax and runs them on dedicated cloud instances.
The key advantages over fluent-ffmpeg:
- No binary to install. You never install ffmpeg. The SDK sends your command to the API, and the API runs it on dedicated hardware.
- Works everywhere. Any platform that can make an HTTP request can use it. Vercel, Lambda, Cloudflare Workers, your laptop, a Raspberry Pi.
- 6-hour job timeout. Encode a full-length feature film in one job. No chunking around 15-minute Lambda limits.
- 16 dedicated 5 GHz CPU cores. Every job runs on its own instance with 32 GB DDR5 RAM and NVMe storage. No shared tenants.
- GPU on demand. Route jobs to Nvidia RTX and A-series hardware for hardware-accelerated encoding.
- Usage-based pricing. Pay per GB processed. Volume discounts kick in automatically. First 2 GB are free with no credit card required.
- Real-time logs. Watch ffmpeg stderr stream in the dashboard while the job runs.
- Auto-diagnosis. When a command fails, the system analyzes the ffmpeg output and tells you exactly what went wrong.
To get started, install the SDK:
npm i @verygoodffmpeg/sdkGet an API key at verygoodffmpeg.com. The first 2 GB are free.
How does the Very Good FFmpeg TypeScript SDK compare to fluent-ffmpeg code?
Here is the same MP4 conversion side by side.
fluent-ffmpeg (requires local binary):
import Ffmpeg from "fluent-ffmpeg"
Ffmpeg("input.mov")
.videoCodec("libx264")
.audioCodec("aac")
.output("output.mp4")
.on("end", () => console.log("Done"))
.on("error", (err) => console.error(err))
.run()This code runs the ffmpeg binary on the local machine. It reads from the local filesystem. The API uses event emitters, so you need to wrap it in a Promise to use async/await. There is no built-in progress reporting without parsing stdout.
Very Good FFmpeg TypeScript SDK (no binary):
import VGF from "@verygoodffmpeg/sdk"
const client = new VGF(process.env.VGF_API_KEY)
const job = await client.run({
input_files: {
"input.mov": "https://your-bucket.s3.amazonaws.com/input.mov",
},
output_files: ["output.mp4"],
ffmpeg_commands: ["-i input.mov -c:v libx264 -crf 23 -c:a aac output.mp4"],
})This code sends the command to the Very Good FFmpeg API. No binary is needed on the client side. Input files are referenced by URL (S3, HTTP, or temp upload). The API is native async/await. Output files are delivered at stable URLs with no egress fees.
Here is a full comparison of the two approaches:
| Concern | fluent-ffmpeg | Very Good FFmpeg |
|---|---|---|
| Binary requirement | Must install ffmpeg system-wide | None |
| Platform support | Local / VPS only | Any HTTP-capable platform |
| Serverless compatibility | Breaks on Vercel, Lambda, Cloudflare Workers | Works everywhere |
| Maximum job timeout | OS-bound | 6 hours |
| CPU cores | Whatever the local machine has | 16 dedicated 5 GHz vCPUs |
| Promise API | Must wrap manually | Native async/await |
| Progress and logs | stdout/stderr | Dashboard + webhook |
| Error diagnosis | Raw ffmpeg stderr | AI auto-diagnosis |
| Pricing | Free (if you own the infrastructure) | Per GB, first 2 GB free |
FAQ
How do I fix "moov atom not found" when serving MP4 files from fluent-ffmpeg?
The moov atom contains the metadata that a media player needs to start playback. ffmpeg writes it at the end of the file by default. When you serve the file over HTTP with partial content requests (range requests), the browser requests the moov atom first, but it is at the end of the file. The fix is to add -movflags +faststart to your ffmpeg output options, which moves the moov atom to the beginning. In fluent-ffmpeg, you add .outputOptions('-movflags +faststart') to the chain.
Can I pass a Buffer as input to fluent-ffmpeg instead of a file path?
Yes, but it is awkward. fluent-ffmpeg's .input() method accepts a Readable stream, but using PassThrough streams for buffer-to-buffer conversion is poorly documented and error-prone. Most developers work around this by writing the buffer to a temporary file in /tmp, processing it, and reading the output back into a buffer. Very Good FFmpeg accepts input from URLs and temporary uploads natively, so you never need to write temp files.
How do I wrap fluent-ffmpeg in a Promise for async/await?
fluent-ffmpeg uses Node.js event emitters, not Promises. You must manually wrap the call:
function convertToMp4(input: string, output: string): Promise<void> {
return new Promise((resolve, reject) => {
Ffmpeg(input)
.videoCodec("libx264")
.audioCodec("aac")
.output(output)
.on("end", resolve)
.on("error", reject)
.run()
})
}Why does fluent-ffmpeg throw "Error configuring complex filters"?
Complex filter syntax in fluent-ffmpeg differs from raw ffmpeg CLI syntax. Filter strings must be passed exactly as ffmpeg expects them. Common mistakes include missing label mappings and wrong quote escaping. The error message from fluent-ffmpeg is usually the raw ffmpeg exit code 1 with no additional context. Very Good FFmpeg auto-diagnoses failed commands and suggests the specific fix, which saves debugging time.
What should I use now that fluent-ffmpeg is deprecated?
For local Node scripts and learning, fluent-ffmpeg still works today. For production applications, you have three options: use the Very Good FFmpeg TypeScript SDK for a no-binary hosted solution, use ffmpeg.wasm (which runs in the browser with performance limits), or execute raw ffmpeg inside a Docker container. Nothing else matches fluent-ffmpeg's API ergonomics for local use, but for production deployments, the Very Good FFmpeg SDK provides the same ffmpeg power without the operations burden.
Verdict
fluent-ffmpeg is a fine choice for learning ffmpeg on your local machine. The API is clean, the documentation is extensive, and the package has served the Node.js community well for years.
For production deployments, especially on serverless platforms, the binary requirement and deprecation status make it a risky dependency. You need a solution that works everywhere without fragile workarounds.
Very Good FFmpeg gives you the full power of ffmpeg without the binary headache. Same ffmpeg flags, same filter syntax, no infrastructure to manage. Sign up at verygoodffmpeg.com and get your first 2 GB free. No credit card required.
References
- fluent-ffmpeg npm: https://www.npmjs.com/package/fluent-ffmpeg
- fluent-ffmpeg GitHub (archived): https://github.com/fluent-ffmpeg/node-fluent-ffmpeg
- "Phasing out fluent-ffmpeg" issue #1324: https://github.com/fluent-ffmpeg/node-fluent-ffmpeg/issues/1324
- moov atom fix (Stack Overflow): https://stackoverflow.com/questions/55896329/how-to-fix-moov-atom-not-found-error-in-ffmpeg
- Buffer input with fluent-ffmpeg (Stack Overflow): https://stackoverflow.com/questions/73562143/buffer-as-input-and-output-for-fluent-ffmpeg
- Complex filter errors (Stack Overflow): https://stackoverflow.com/questions/34348203/ffmpeg-exited-with-code-1-error-configuring-complex-filters
- Serverless timeout on Firebase (Stack Overflow): https://stackoverflow.com/questions/44469537/cloud-functions-for-firebase-completing-long-processes-without-touching-maximum
- Reddit: fluent-ffmpeg deprecated alternatives: https://old.reddit.com/r/node/comments/1dummy/best_way_to_integrate_ffmpeg_into_fastify
- Reddit: ffmpeg on Render.com: https://old.reddit.com/r/node/comments/1dummy/ffmpeg_and_ytdlp_not_working_on_rendercom
- Very Good FFmpeg: https://verygoodffmpeg.com
- Very Good FFmpeg TypeScript SDK: https://www.npmjs.com/package/@verygoodffmpeg/sdk