Common FFmpeg errors and what they mean

Read the failure distribution from 208 failed video jobs. More died on an unreachable input URL than on a bad FFmpeg command, and one message caused 25 of them.

Share

Short version

We went through every job in Rendobar’s history that did not complete. 208 failures out of 1,978 jobs, a 10.5% failure rate, and for FFmpeg jobs specifically 129 out of 1,306, or 9.9%.

Then we grouped them by what actually went wrong. The result is not what people expect.

What failedCount
The input could not be downloaded61
FFmpeg rejected the command45
Something else, unclassified23
No message was recorded19
The job never reached a runner17
An upstream provider failed10
Something was too large9
The output stage failed9
An input reference was missing6
Rejected before it ran6
The input was not media3

More jobs died on a URL than on a command. Expired links, 401s, 404s and one origin returning 429 outnumbered FFmpeg’s own complaints by 61 to 45. If you are debugging a pipeline that fails intermittently, check the input fetch before you touch the filter graph.

The one message you will see most

Of the 37 failures that carried FFmpeg’s own error text, 25 returned exactly this:

Invalid argument. Check your FFmpeg flags and values.

That message is honest about being unhelpful. It almost never means a flag value is out of range. In every case we traced, it meant the filter graph did not parse.

The tell is the timing. All 25 failed at 0 seconds. Nothing was decoded, nothing was encoded, no input was read. FFmpeg parsed the command line, could not build the graph, and stopped. A failure at 0 seconds is a syntax problem, so the input file, the codec and the encoder settings are all irrelevant to it.

Overlay is the worst offender

21 of 40 overlay jobs failed, and 14 of those returned the generic Invalid argument message. That is more than half the overlay attempts in the entire archive.

The reason is structural rather than careless. overlay takes two inputs. -vf only ever sees the first one. So this cannot work no matter how the coordinates are written:

Terminal window
# Fails. -vf has no access to the second input.
ffmpeg -i base.mp4 -i logo.png -vf "overlay=10:10" out.mp4

Two inputs means -filter_complex and explicit labels:

Terminal window
# 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, [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 rule generalises past overlay. Any filter with more than one input needs -filter_complex and labels. That covers concat, amix, hstack, blend and every transition.

Filter not found points at the wrong place

Five failures returned this:

Error opening output files: Filter not found

The phrase “output files” sends people to check their output path and their container. The output is fine. A filter name in the graph is misspelled, or is not compiled into the build you are running.

FFmpeg builds genuinely differ. A filter that works on your laptop can be absent from a container image, and the message is the same either way. ffmpeg -filters lists what your build actually has, which settles it in one command.

The same applies to encoders, and we hit both. One job asked for libfdk_aac, which is not in most distributed builds because of its licence. Another asked for libx264__nvec, which is a typo. Both came back as the codec not being available, with a suggestion to use libx264 instead.

Failures that were never about FFmpeg

61 of 208 could not download the input. The messages name the cause directly and they are worth reading as a category, because each has a different fix.

A 404 means the URL is wrong or the object is gone. A 401 means the URL needs credentials the fetch did not have, which is the usual outcome of passing a private storage link without signing it. A 429 means the origin rate-limited us, which happened when a job pulled an asset from a public wiki. A plain fetch failure means DNS or TLS.

Presigned URLs deserve their own warning. They expire. A URL that worked when you wrote the job and fails when you retry it three hours later is not an intermittent bug, it is a signature that timed out.

9 failures were about size. Payloads over the accepted limit, and one request for an output of 2730x4096, which is 11.2 megapixels, against a GPU that could hold 9.9. Those are limits, not errors, and they fail fast rather than half-rendering.

Reading a failure in the right order

The distribution above suggests a debugging order that is not the instinctive one.

First, did the job run at all. If it failed at 0 seconds, stop looking at the media. It is a parse error. If it never started, it is infrastructure and retrying is reasonable.

Second, did the input arrive. This is the single largest category and it is invisible from the command. Fetch the URL yourself, unauthenticated, from somewhere other than your machine.

Third, does the filter graph parse. Build it up one filter at a time. Every label you reference must be one you defined, every multi-input filter needs -filter_complex, and every branch must be consumed.

Fourth, does your build have the pieces. ffmpeg -filters and ffmpeg -encoders answer this immediately and rule out an entire class of confusing messages.

Only after all four is it worth looking at flag values, which is where most people start.

Running it where the error comes back structured

Rendobar’s FFmpeg API returns the failure reason on the job rather than making you scrape stderr, and it records the stage at which the job died, so “the input never downloaded” and “FFmpeg rejected the command” are distinguishable without reading logs.

job.ts
import { createClient } from "@rendobar/sdk";
const rb = createClient({ apiKey: process.env.RENDOBAR_API_KEY });
// throwOnFailure:false returns the failed job instead of raising, so you can
// read WHICH stage died rather than scraping stderr.
const job = await rb.jobs.run(
{
type: "ffmpeg",
params: { command: "ffmpeg -i https://example.com/missing.mp4 -c:v libx264 out.mp4" },
},
{ throwOnFailure: false },
);
if (job.status === "failed") {
// "could not fetch input" and "FFmpeg execution failed" are different
// problems with different fixes. 61 of our 208 failures were the first.
console.error(job.error.message);
}

Install with npm i @rendobar/sdk. jobs.run() submits and waits, so it returns the finished job in one call.

terminal
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/sample.mp4 -i https://cdn.rendobar.com/assets/examples/photo.jpg -filter_complex "[0:v][1:v]overlay=W-w-20:H-h-20" -c:a copy -t 5 out.mp4" }
}'

Returns immediately with a job id. Poll GET /jobs/{id} or register a webhook rather than blocking on the request.

Probing the input first is the cheapest way to remove the largest failure category. A probe bills a flat $0.0010 against a median FFmpeg job of $0.0025, and it answers “is this URL reachable and is it media” before you pay to encode.

Where this stops

This is our traffic, not yours. It is heavily weighted toward testing and content production, which means an unusual concentration of deliberately broken commands and deliberately dead URLs. A production pipeline running one known-good command against reliable storage will not see a 10.5% failure rate, and the shape of its failures will differ.

19 failures recorded no message at all and 23 more did not match any family, so about a fifth of the sample is unclassified and could belong to any category above. The counts should be read as a ranking rather than as precise proportions.

We also cannot tell you FFmpeg’s own stderr for most of these, because the API surfaces a normalised message rather than the raw output. That is a real limitation when the raw text would name the exact filter that failed.

For the input side specifically, see validating video uploads with ffprobe, which covers using a probe as a gate. For what a job costs when it does work, see FFmpeg API pricing compared. For the settings themselves, see FFmpeg encoding settings measured.

Frequently asked questions

What does 'Invalid argument' mean in FFmpeg?

Almost always that the filter graph did not parse, not that a flag value is out of range. It was the single most common FFmpeg error in our data, 25 of 37, and in the overlay cases it came from unlabelled or mismatched stream labels in filter_complex rather than from any argument.

Why does FFmpeg say 'Filter not found'?

The filter name is misspelled or is not compiled into your build. The message mentions output files, which sends people to check their output path, but the failure is in the -vf or -filter_complex string. It caused 5 of our failures.

What is the most common cause of a failed video processing job?

The input URL, not the command. In 208 failed jobs, 61 could not download the input at all while 45 were FFmpeg rejecting the command. Expired links, 401s, 404s and rate limits from the origin outnumbered every syntax problem.

Why does my FFmpeg overlay filter fail?

An overlay takes two inputs, so it needs explicit stream labels. Writing -vf overlay with two -i flags cannot work, because -vf only sees the first input. It has to be -filter_complex with labels like [0:v][1:v]overlay=x:y.

How do I debug an FFmpeg command that fails immediately?

A failure at 0 seconds is a parse error, not a processing error, so the input and the encoder are irrelevant. Build the filter graph up one filter at a time against a short local clip, and check that every label you reference is one you defined.

Sources

Tags #ffmpeg#errors#debugging#troubleshooting
All posts
Share
  1. Opus vs AAC vs MP3 vs FLAC Engineering blog
  2. AV1 vs H.264 VMAF compared Engineering blog
  3. AV1 vs VP9 vs HEVC vs H.264 Engineering blog