Common FFmpeg errors and the fix for each
Find the fix for common FFmpeg errors, from moov atom not found to Invalid argument, and learn why most failed video jobs never reached FFmpeg.
Most FFmpeg errors are one of about a dozen messages, and the table below gives each one with its meaning and its fix. One rule covers nearly all of them. The last line FFmpeg prints is a generic summary, and the cause is named a line or two above it. And if your FFmpeg runs behind an API, check the input URL before the command, because in failed jobs on ours a download that never happened was the more common cause.
The errors, what they mean, and the fix
Every message here is FFmpeg’s own text, reproduced on FFmpeg 8.0 with a known clip. Where FFmpeg prints two lines, the first one names the cause and the second is the summary most people copy into a search box.
| FFmpeg prints | What it means | Fix |
|---|---|---|
moov atom not found, then Invalid data found when processing input | Either the MP4 is truncated, so its index (the moov box) was never written or never fully downloaded, or the file is not an MP4 at all and FFmpeg guessed the format from the .mp4 extension. The second case also prints detected only with low score of 1, misdetection possible! above it. | For a truncated file, get the complete one. No input flag recovers an MP4 whose recording stopped before the index was written. For a misnamed one, see the next row. |
Invalid data found when processing input | The bytes are not a format FFmpeg recognises. A renamed PDF and an HTML error page saved as .mp4 both end here. | Check what the file really is with ffprobe. If it came from a URL, look at what the URL returned. |
No such file or directory | The input path does not exist from where FFmpeg is running. | Use an absolute path, or check the working directory. |
Server returned 404 Not Found (or 403 Forbidden) | FFmpeg reached the URL and the server refused it. | Fix the URL, or sign it. A presigned link that has expired returns 403. |
Unrecognized option 'crff'., then Error splitting the argument list: Option not found | An option name is misspelled, or it only exists in a newer FFmpeg. | Correct the name. ffmpeg -h full lists what your build accepts. |
No such filter: 'ovrlay', then Error opening output files: Filter not found | A filter name is misspelled or not compiled into this build. The output file is fine. | Fix the name. ffmpeg -filters lists what your build has. |
Unknown encoder 'libfdk_aac', then Error opening output files: Encoder not found | The encoder is not in this build. libfdk_aac is missing from most distributed builds because of its licence. | Use an encoder that is present, such as aac. ffmpeg -encoders lists them. |
Simple filtergraph 'overlay=10:10' was expected to have exactly 1 input and 1 output. However, it had 2 input(s), then Invalid argument | A two-input filter was passed to -vf, which only sees one input. | Use -filter_complex with labels (next section). |
Invalid file index 2 in filtergraph description, then Error binding filtergraph inputs/outputs: Invalid argument | A label such as [2:v] points at an input you did not pass. Indexes start at 0. | Count your -i flags and fix the label. |
Filter 'split' has output 0 (b) unconnected | A filter graph produced a stream that nothing consumes or maps. | Feed every labelled output into another filter or -map it. |
Stream map '' matches no streams. with To ignore this, add a trailing '?' to the map. | A -map asked for a stream the input does not have, for example subtitles. | Add ? to make it optional: -map 0:s?. |
Output file does not contain any stream | Every stream was excluded, for example by -vn -an or maps that select nothing. | Keep at least one stream in the output. |
width not divisible by 2 (259x194), then Conversion failed! | libx264 with 4:2:0 chroma needs even width and height. | Scale with -2, for example scale=640:-2, which rounds to an even number. |
invalid preset 'fastest', with Possible presets: ultrafast superfast ... | The preset name does not exist. | Pick one from the list FFmpeg printed. |
Conversion failed! is never the cause. It is the line FFmpeg prints after something above it went wrong. The same goes for a bare Invalid argument, which FFmpeg attaches to at least six of the failures in this table. Scroll up.
Which messages are Rendobar’s and which are FFmpeg’s
This post was first written from Rendobar’s failed jobs, and one message dominated them: Invalid argument. Check your FFmpeg flags and values. That sentence is ours, not FFmpeg’s, and the first version of this post presented it as FFmpeg’s text. It was wrong to.
Rendobar’s FFmpeg API rewrites a few FFmpeg messages into a shorter error.message. When stderr contains one of these, the job’s message is replaced:
| FFmpeg’s stderr contains | Rendobar’s error.message says |
|---|---|
Invalid argument, anywhere | Invalid argument. Check your FFmpeg flags and values. |
Unknown encoder 'X' | Codec X is not available. Try libx264 (H.264), libvpx-vp9 (VP9), or libx265 (H.265). |
No such file or directory | Input file not found. Check that the URL is accessible. |
Option X not found | The same text, followed by Check that the option name is correct. |
Anything else comes through as FFmpeg’s own last error line, prefixed with FFmpeg execution failed after Ns:. Error opening output files: Filter not found is one of those, so it is FFmpeg’s text.
The first row is the weak one. FFmpeg says Invalid argument for a -vf overlay, a bad label, an unconnected output, a missing stream, odd dimensions and a bad preset, so the rewritten message cannot tell you which. The specific line is still there. A failed job carries the tail of FFmpeg’s stderr in error.detail, and that is where to read the cause.
Why overlay fails so often
In our failed jobs, more commands used overlay than used subtitles and drawtext combined. About two thirds of those overlay failures came back as the generic Invalid argument message, and every one that reached FFmpeg ended within 35 ms of the step starting. Nothing gets decoded that fast, so the command never parsed.
The cause is structural. overlay takes two inputs, and -vf only ever sees the first one, so this cannot work however the coordinates are written:
# Fails. -vf has no access to the second input.ffmpeg -i base.mp4 -i logo.png -vf "overlay=10:10" out.mp4FFmpeg says so directly, on the line above the summary:
[vf#0:0 @ 0000024cc52df1c0] Simple filtergraph 'overlay=10:10' was expected to have exactly 1 input and 1 output. However, it had 2 input(s) and 1 output(s). Please adjust, or use a complex filtergraph (-filter_complex) instead.Error opening output file out.mp4.Error opening output files: Invalid argumentTwo inputs means -filter_complex and explicit labels:
# Works.ffmpeg -i base.mp4 -i logo.png \ -filter_complex "[0:v][1:v]overlay=W-w-20:H-h-20" \ -c:a copy out.mp4[0:v] is the video of the first input and [1:v] the video of the second. W and H are the base dimensions, w and h the overlay’s, so W-w-20 pins it 20 pixels from the right edge. The same rule covers concat, amix, hstack, blend and every transition: more than one input needs -filter_complex. The full set of corner positions, logo sizing and text watermarks is in how to add a watermark with FFmpeg.
Most failed jobs never reached FFmpeg
When I grouped failed jobs on the Rendobar API by the stage that failed, FFmpeg rejecting the command was not the largest group:
| What failed | Share of failed jobs |
|---|---|
| The input could not be downloaded | about 30% |
| FFmpeg rejected the command | about 20% |
| No message, or no clear category | about 20% |
| Infrastructure, where retrying is reasonable | about 13% |
| Rejected before it ran, or the input was missing or not media | about 7% |
| Something was too large | about 4% |
| The output stage failed | about 4% |
These are all failed jobs in the job archive as of 2026-08-20, across job types, with shares rounded. The traffic is ours, much of it exploratory, so read the order and not the exact proportions. A pipeline running one known-good command against reliable storage will fail less and in different ways.
The download failures each point to a different fix. A 404 means the URL is wrong or the object is gone. A 401 or 403 means the URL needs credentials, which is the usual result of passing a private storage link without signing it. A 429 means the origin rate-limited the fetch, common with sites that were never meant to serve media. A plain fetch failure means DNS or TLS. A presigned URL that worked when you wrote the job and fails on a retry three hours later has expired, and presigned URLs can expire earlier than the expiry you set.
Debugging in the order failures happen
- Did the input arrive? Fetch the URL yourself, unauthenticated, from somewhere other than your machine. On our data this settles the largest group.
- Did it fail at once? A failure before any frame was written is a parse or setup error, so the media content is irrelevant. Read the line above the summary.
- Does the filter graph parse? Build it one filter at a time, check every label, and use
-filter_complexfor anything with two inputs. - Does your build have the pieces?
ffmpeg -filtersandffmpeg -encodersanswer in one command.
Only then look at flag values, which is where most people start.
Reading the failure from the API
A failed Rendobar job returns the stage it died in (failedPhase), the wrapped message, and FFmpeg’s stderr tail. Both tabs below run the same command against an input URL that does not exist, which fails in preparing, before FFmpeg starts.
import { createClient } from "@rendobar/sdk";
const rb = createClient({ apiKey: process.env.RENDOBAR_API_KEY });
// throwOnFailure: false returns the failed job instead of throwing.const job = await rb.jobs.run({ type: "ffmpeg", params: { command: "ffmpeg -i https://cdn.rendobar.com/assets/examples/does-not-exist.mp4 -c:v libx264 -t 5 out.mp4", },},{ throwOnFailure: false },);
if (job.status === "failed") {// failedPhase is "preparing" when the input never downloaded and// "processing" when FFmpeg itself rejected the command.console.log(job.error.failedPhase, job.error.code);console.log(job.error.message);// FFmpeg's own stderr tail, or null when FFmpeg never ran.console.log(job.error.detail);}Install with npm i @rendobar/sdk. jobs.run() submits and waits, so it returns the finished job in one call.
curl -X POST https://api.rendobar.com/jobs \-H "Authorization: Bearer $RENDOBAR_API_KEY" \-H "Content-Type: application/json" \-d '{ "type": "ffmpeg", "params": { "command": "ffmpeg -i https://cdn.rendobar.com/assets/examples/does-not-exist.mp4 -c:v libx264 -t 5 out.mp4" }}'
# Then read error.failedPhase, error.message and error.detailcurl https://api.rendobar.com/jobs/JOB_ID \-H "Authorization: Bearer $RENDOBAR_API_KEY"Returns immediately with a job id. Poll GET /jobs/{id} or register a webhook rather than blocking on the request.
A probe catches the largest failure group at the price of a probe instead of an encode, and validating uploads with ffprobe covers using one as a gate.
My advice is to stop reading FFmpeg errors from the bottom. The last line tells you that something failed, the line above it tells you what, and on an API the URL fails before either.
Frequently asked questions
How do I see the full FFmpeg error log instead of the last line?
Run the same command with -report. FFmpeg writes the command line and the full log at debug level to a file named ffmpeg-YYYYMMDD-HHMMSS.log in the working directory, while the console output stays as it was.
Why does my FFmpeg command work locally but fail in Docker?
Usually a different build or a missing file. Container images ship different FFmpeg builds, so a filter or encoder on your laptop can be absent. Compare ffmpeg -filters and ffmpeg -encoders on both machines, and check that relative input paths resolve from the container's working directory.
