FFmpeg Error Messages, Not Exit Codes, Tell You What to Retry

Your worker ran ffmpeg, the process exited non-zero, and your queue put the job back in line to fail again. Conventional wisdom says check the exit code and retry the failures, which works for most command-line tools. FFmpeg isn't most tools: it exits with code 1 for a corrupt upload, a filter-graph typo, and a full disk alike, so your retry loop keeps re-running jobs that can never succeed.
Quick answer: FFmpeg error messages are diagnosed from stderr, not the exit code, because FFmpeg returns 1 for nearly every failure. Match the exact stderr string and sort it into three classes: bad input (moov atom not found,Invalid data found when processing input), rejected at ingest; bad command (height not divisible by 2,Unknown encoder,matches no streams), fixed and never retried; and resource exhaustion (No space left on device, exit 137 from the OOM killer, network timeouts), the only class worth retrying. If you'd rather not maintain that classifier, FFmpeg Micro runs the job and returns a structured status instead of a stderr dump.
Every FFmpeg error string, what causes it, and whether to retry
The table is keyed on the literal text FFmpeg prints, the only reliable identifier you get. Each row gives the cause behind that string in a pipeline and what your queue should do.
| stderr string | What causes it in a pipeline | Fix | Class |
|---|---|---|---|
| `moov atom not found` | Truncated upload (killed multipart, unfinalized stream), or a range request that missed the trailing moov box | Re-fetch the whole object; write `-movflags +faststart` on your outputs | Reject at ingest |
| `Invalid data found when processing input` | Not what the extension claims: an HTML error page saved as `.mp4`, a 0-byte object | `ffprobe` at ingest; fail with a message the uploader can act on | Reject |
| `pipe:0: Invalid data found when processing input` | Nothing reached stdin; the upstream step produced no bytes | Assert a byte count before spawning FFmpeg | Reject |
| `height not divisible by 2` | libx264 got an odd dimension after a scale or crop | `-vf "scale=trunc(iw/2)*2:trunc(ih/2)*2"` | Bad command |
| `Too many packets buffered for output stream 0:1` | One stream stalls the interleaver, usually one that goes long stretches with no packets | `-max_muxing_queue_size 4096` | Bad command |
| `Non-monotonic DTS in output stream 0:1` (older builds print "Non-monotonous") | Timestamps going backwards from a VFR or concatenated source | `-fflags +genpts` on the input, or re-encode with `-fps_mode cfr` | Warning |
| `Application provided invalid, non monotonically increasing dts to muxer` | Same timestamps, raised by the muxer during a stream copy | Drop `-c copy` and normalize first | Bad command |
| `Stream map '0:a' matches no streams` | No audio track, common with screen recordings and silent UGC | `-map 0:a?` | Bad command |
| `Could not find tag for codec pcm_s16le in stream #1` | The container can't hold that codec, PCM audio in MP4 being the usual case | `-c:a aac`, or write `.mov` | Bad command |
| `Unknown encoder 'libx264'` | A minimal or static build without that encoder compiled in | `ffmpeg -encoders`, then pick what the build has | Bad command |
| `Unable to find a suitable output format for 'out'` | Output path has no extension and no `-f` | Add `-f mp4` | Bad command |
| `No space left on device` | Temp volume filled up; AWS Lambda gives you 512 MB of `/tmp` by default | Clear temp between jobs, or raise ephemeral storage | Retry |
| Empty stderr, shell reports 137 | The Linux OOM killer sent SIGKILL | Lower resolution or thread count, or move the job elsewhere | Retry once |
| `Server returned 403 Forbidden` on an `http` input | The signed URL expired between enqueue and run: a 15-minute presign against a 20-minute backlog | Sign the URL at execution, not at enqueue | Retry after re-signing |
| `Conversion failed!` | Nothing. It's the tail line printed after the real error | Grep upward; never match on this line | Depends |
Four of these get their own walkthrough here: moov atom not found, height not divisible by 2, timestamp and sync drift, and the normalize-then-copy fix for concat failures.
FFmpeg exit codes carry almost no signal
FFmpeg exits 0 on success and 1 on nearly every failure: broken file, bad flag, full disk. Other values turn up in the wild (Transloadit's community has a thread about exit code 8), so the number is worse than useless: you can't enumerate it or map it to an action.
Codes above 128 aren't from FFmpeg. Your shell reports 137 for SIGKILL (128 + 9), the OOM killer on a memory-limited container, and 143 for SIGTERM (128 + 15), usually your orchestrator hitting a timeout. Special-case those two because FFmpeg never printed anything.
Treat the exit code as a boolean. Everything else lives in stderr.
Parse stderr and classify into three buckets
Run FFmpeg with -hide_banner -loglevel error so stderr carries diagnosis instead of build configuration and a progress counter, then match the tail against three ordered pattern sets:
import re
import subprocess
REJECT = (r"moov atom not found|Invalid data found when processing input|"
r"could not find codec parameters|Invalid NAL unit size")
BAD_COMMAND = (r"not divisible by 2|Unknown encoder|Unrecognized option|"
r"matches no streams|Could not find tag for codec|"
r"Unable to find a suitable output format|"
r"Automatic encoder selection failed|Error initializing")
TRANSIENT = (r"No space left on device|Cannot allocate memory|"
r"Connection timed out|Server returned 5\d\d|Input/output error")
def run(cmd):
p = subprocess.run(cmd, capture_output=True, text=True)
tail = "\n".join(p.stderr.strip().splitlines()[-40:])
if p.returncode == 0:
return "ok", tail
if p.returncode in (137, 143): # SIGKILL / SIGTERM
return "transient", tail
for label, pattern in (("transient", TRANSIENT),
("reject", REJECT),
("bad_command", BAD_COMMAND)):
if re.search(pattern, tail):
return label, tail
return "unknown", tail
Only transient goes back on the queue. reject fails the job immediately and tells the uploader what's wrong with their file. bad_command pages you, because a retry reproduces it exactly. unknown gets logged with the full tail and reviewed weekly; that's how the pattern lists grow.
Order matters. Check transient first: a job killed mid-write leaves a truncated output that a later probe reads as bad input, and you'd reject a file that was fine.
Exit code 0 is not proof the output is good
FFmpeg exits 0 while writing files that are wrong. A -map 0:a? that matched nothing yields a silent video. Broken source timestamps yield drifting audio. A filter that dropped frames prints a warning. All three exit 0.
Probe the output before marking the job complete:
ffprobe -v error -of json \
-show_entries format=duration,size \
-show_entries stream=codec_type,codec_name,nb_frames \
out.mp4
Assert what you expected: duration within a second of the input's, stream count matching, nb_frames not zero. That check catches more real breakage than stderr parsing does, because the failures that reach your users are the ones FFmpeg didn't consider failures.
Warnings your pipeline probably treats as failures
FFmpeg writes its progress counter to stderr, so non-empty stderr is not a failure signal, and a check shaped like if stderr: raise fails every successful job you run. That's the most common bug in homegrown FFmpeg wrappers. Messages that read like errors but aren't:
Past duration 0.799 too largeappears constantly on VFR sources and affects nothing.deprecated pixel format used, make sure you did set range correctlycomes from swscaler on yuvj420p input and is cosmetic.Non-monotonic DTS in output streamis a warning when the encode continues; only the muxer's "non monotonically increasing dts" form during a stream copy is fatal.Multiple frames in a packet from stream 1shows up on MP3 sources and is harmless.
To stop on conditions FFmpeg would otherwise survive, add -xerror. For a failure you can't reproduce, add -report, which writes a full debug-level log to ffmpeg-YYYYMMDD-HHMMSS.log in the working directory, including the exact command line and every decoder decision.
Common pitfalls when handling FFmpeg errors at scale
The patterns that bite hardest aren't in the table, they're in how the surrounding system reads it.
- Matching on
Conversion failed!. It's the last line, so a naive tail grabs it, and it names no cause. Take the last 40 lines. - Retrying bad input forever. A truncated upload fails identically on attempt one and attempt fifty. An
ffprobecheck at ingest keeps it out of the queue. - Assuming the local build matches production.
Unknown encoder 'libx264'on a server that works on your Mac is a build difference; on Windows it shows up as FFmpeg not being on PATH. - Reading whole files into the runtime. n8n and Lambda die by SIGKILL long before FFmpeg errors, which is why exit 137 with empty stderr is a memory problem, not an FFmpeg problem.
When to stop parsing stderr
If you run a handful of conversions a week, skip all of this and read the log when something breaks. The classifier earns its keep around a few hundred jobs a day, when nobody has time to read stderr by hand and the retry queue costs real money.
Past that you're maintaining a media microservice: the FFmpeg build, the temp volumes, the memory limits, the pattern lists that go stale on every upgrade ("Non-monotonous" became "Non-monotonic" in a recent release, and every regex written against the old spelling went quiet). That's the argument for sending the job somewhere else. FFmpeg Micro takes the job over its REST API, runs it with no servers for you to size, and returns a job status you can branch on instead of a stderr dump; the error semantics are in the docs, and the same jobs work from n8n, Make, Zapier, or an AI agent over MCP. If you'd rather keep FFmpeg in-house, the honest comparison is a self-hosted container, whose running cost is mostly your time.
FAQ
What does "Invalid data found when processing input" mean in FFmpeg?
"Invalid data found when processing input" means FFmpeg couldn't identify a container or codec in the bytes it was given. In a pipeline the file usually isn't video at all: an HTML or JSON error body saved with a .mp4 extension, a zero-byte object, or an empty pipe:0. Check the first bytes and the length before blaming the codec.
Which FFmpeg errors are safe to retry?
Only resource-exhaustion and network failures are safe to retry: No space left on device, Cannot allocate memory, exit 137 from the OOM killer, connection timeouts, and 5xx responses from a remote input URL. Bad input and bad commands fail identically every attempt, so retrying wastes compute and delays the alert you need.
Does FFmpeg exit code 1 mean the output file is unusable?
FFmpeg exit code 1 means the run didn't finish, so delete any output it left behind rather than inspecting it. The dangerous case runs the other way: FFmpeg exits 0 while producing a video with no audio track, a wrong duration, or dropped frames, which is why you should ffprobe every output before marking a job complete.
Why does my pipeline hit FFmpeg errors that never happen on my laptop?
Pipeline-only FFmpeg errors usually come from three differences: the server's build lacks encoders your local install has, the container has a memory ceiling your laptop doesn't, and production input is real user uploads, not your clean test clip. Run ffmpeg -version and ffmpeg -encoders on the actual worker before debugging anything else.
Point your next failing job at the API instead of a worker you babysit, and compare a structured job status to a stderr tail. Sign up free and run it on the file that's been breaking your queue.
About Javid Jamae
Founder & CEO at FFmpeg Micro
Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.
You might also like

The fal.ai FFmpeg API merges clips. It can't burn captions.
The fal ai ffmpeg api covers compose and merge-videos at $0.0002/second. Where that fits an AI video pipeline, and where a full FFmpeg surface wins out.

CloudConvert Alternative for Video: Your Job Isn't a Conversion
Looking for a CloudConvert alternative for video? Compare per-minute billing, filter chains, webhooks, and composition steps before you move your pipeline.
Your video thumbnail sprite sheet is fine. The VTT cues drift.
Build a thumbnail sprite sheet video preview with FFmpeg, then generate the WebVTT from the same interval so your scrub-bar cues never drift.
Ready to process videos at scale?
Start using FFmpeg Micro's simple API today. No infrastructure required.
Get Started Free