Fix "Output File Is Empty, Nothing Was Encoded" in FFmpeg

Your job ran, the exit code was 0, and the file the next step downloaded is a few hundred bytes of MP4 header with no frames. Buried in stderr is a warning-level line: Output file is empty, nothing was encoded (check -ss / -t / -frames parameters if used). Nothing about the run looked like a failure to your pipeline, which is why it reached production.
Quick answer: An ffmpeg output file empty result means FFmpeg opened the muxer and wrote a container header, but zero packets reached it, so it printsOutput file is empty, nothing was encodedas a warning and still exits 0. Find the input-selection option that matched nothing (-sspast the end of the file, a-tor-framesvalue that computed to zero, aselectexpression that never evaluated true, an empty concat list), then add the global flag-abort_on empty_outputso the run is fatal instead of silent. To skip that assertion work in every pipeline, send the job to the FFmpeg Micro API: one call, no encoder to host, and a render that produces nothing returns a failed job, not a 0-byte download.
Why FFmpeg treats an empty output as a warning
FFmpeg decides the run is empty at the very end, when it tallies bytes written across video, audio, subtitle, and data streams. If that total is zero it prints the "nothing was encoded" line and returns the same status as a clean run. The design assumes a person is watching the terminal. A worker in a queue is not.
The fix is a global option, so it goes before your inputs:
ffmpeg -hide_banner -abort_on empty_output \
-ss 00:12:30 -i input.mp4 -t 15 -c copy clip.mp4
# ... Empty output
# echo $? -> 1
A stricter sibling, -abort_on empty_output_stream, fires when any single output stream got no packets. Use it when a missing audio track is as bad as a missing video track, which covers most social-media renders. Check your build with ffmpeg -h full | grep -A 4 abort_on. Same problem as FFmpeg error messages, not exit codes, tell you what to retry: the exit status carries no signal.
`-ss` past the end of the input
Seeking past the last frame is the most common cause of an empty render in an automated clipper. FFmpeg seeks, hits end of file, decodes nothing, and closes the muxer. There's no "seek beyond duration" error, because seeking past the end isn't illegal.
The scenario is constant: a repurposing workflow reads a timestamp range from a transcript, asks for 00:12:30 to 00:12:45, and the source is nine minutes long because the upload was truncated. The clip list was right for the video someone meant to send.
Two habits kill this class:
- Read the real duration with
ffprobebefore you build the command, and drop any clip that starts past it. - Express the window as
-ss <start> -t <duration>rather than-ss <start> -to <end>. A-tosmaller than-ssis a zero-length window, and the two interact differently depending on where each sits relative to-i.
Copy-mode trims add a second trap: with -c copy the cut lands on keyframes, so a short window between them can come out empty or unplayable. FFmpeg cuts on keyframes covers when to re-encode.
`-t` and `-frames` computed from metadata that wasn't there
A duration of zero produces an empty file, and zero is what you get when ffprobe returns nothing and your shell math treats the blank as a number. Plenty of real files have no container duration: WebM and Matroska from a streaming encoder, fragmented MP4, and anything still being written report N/A for format=duration.
ffprobe -v error -show_entries format=duration -of csv=p=0 recording.webm
# N/A
Feed that into dur=$(...) and then -t $((dur - 5)) and you either crash on the arithmetic or, in an awk or JavaScript path, sail through with -t 0. FFmpeg accepts -t 0 and -frames:v 0 and writes an empty file. Same for a frame count from a bogus duration times fps.
When the header has no duration, count packets instead:
ffprobe -v error -select_streams v:0 -count_packets \
-show_entries stream=nb_read_packets -of csv=p=0 recording.webm
It reads the whole file, so it costs time on large inputs, but it returns a real number.
Inputs that matched nothing: globs, numbering, and concat lists
An image sequence that matches no files fails loudly: ffmpeg -pattern_type glob -i 'frames/*.png' on an empty directory returns Could find no file with path 'frames/*.png' and exits non-zero. A printf width mismatch (frame_%04d.png against frame_1.png) does the same.
The silent version is the concat demuxer. An empty or all-comments list file is a valid input, so FFmpeg opens it, finds zero entries, writes the header, and reports an empty output:
# list.txt written by a loop that iterated over zero items
ffmpeg -f concat -safe 0 -i list.txt -c copy out.mp4
If an n8n loop, a Make iterator, or a for loop in GitHub Actions built that list from an empty API response, you get a valid command over an empty set. Assert wc -l < list.txt first, and treat "zero clips" as a workflow branch, not an FFmpeg input.
Filters that never pass a frame
A filtergraph can drop every frame and still be correct. select is the usual culprit because its expression is data-dependent: -vf "select='gt(scene,0.4)'" returns nothing on a static screencast or a slow talking-head clip, where no two consecutive frames differ by 40%. Same with select='between(t,120,130)' on a 90-second video.
That's the filter telling you the content has no scene changes at your threshold. Use a lower threshold (0.2 to 0.3 for talking-head footage) or a fallback that grabs frames at a fixed interval when the scene pass returns zero. Extracting frames by scene change instead of 1 FPS covers picking the threshold.
Single-image output makes this worse. Writing one PNG to a fixed filename needs -update 1, or the image2 muxer wants a numbered pattern and dies at frame 2. But -update 1 creates the file as soon as the output opens, so a select that never fires leaves a genuine 0-byte PNG that any file-exists check passes.
The output went somewhere the container threw away
Writing to a directory that doesn't exist fails loudly: No such file or directory, exit 1, and FFmpeg won't create parent directories. What turns it silent is the layer above.
On AWS Lambda, Google Cloud Run, or any ephemeral container, /tmp belongs to one instance, so if the encode and upload steps land in different containers the download finds nothing and the orchestrator reports a missing file, not an encoder error. Same in n8n when a Docker-hosted FFmpeg writes inside the container and a later node reads from the host. Passing URLs instead of local paths removes the class, the argument in Google Drive video automation fails in n8n.
The guard: assert the output, don't trust the exit code
Never mark a render complete on exit code alone, and don't trust a file-size test either. An "empty" MP4 usually isn't 0 bytes: FFmpeg writes the ftyp and moov boxes when it opens the muxer, so test -s out.mp4 passes on a valid container with no samples. Only ffprobe sees inside.
#!/usr/bin/env bash
set -euo pipefail
out="clip.mp4"
ffmpeg -hide_banner -loglevel error -abort_on empty_output \
-ss 90 -i input.mp4 -t 15 -c:v libx264 -c:a aac -y "$out"
codec=$(ffprobe -v error -select_streams v:0 \
-show_entries stream=codec_name -of csv=p=0 "$out")
dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$out")
[ -n "$codec" ] || { echo "no video stream in $out"; exit 65; }
awk -v d="$dur" 'BEGIN { exit !(d+0 > 0.5) }' \
|| { echo "duration $dur is not a real clip"; exit 65; }
Three checks: FFmpeg aborts on an empty mux, ffprobe confirms a video stream, and awk confirms a duration above half a second (bash can't compare floats, so [ "$dur" -gt 0 ] fails on 15.033). Exit 65 is arbitrary; the point is a status your worker can route on.
Both paths have a price:
| Step | Running FFmpeg yourself | One API call |
|---|---|---|
| Empty render | `-abort_on empty_output` plus your own ffprobe assertion | Job returns failed with an error |
| Bad timestamps | Probe duration first, clamp the clip list | Rejected with a typed error, not a 0-byte file |
| Output storage | Your `/tmp`, your bucket, your lifecycle | Output URL you download |
| Long jobs | You babysit the timeout | Poll or take a webhook |
That's the honest trade. Keep FFmpeg in-house and budget for the assertion script above in every worker. If the failure should instead surface as a failed job, FFmpeg Micro runs the same operations as one REST call with no servers to run, and the playground takes the exact command shape before you wire it into n8n, Make, or Zapier.
Pitfalls that produce an empty file with no error
Four habits cause most silent empty renders in production pipelines:
- Testing for a 0-byte file. An empty MP4 has a header; test with ffprobe.
- Setting
-loglevel quietor-nostatsin a worker, which discards the only warning you get. - Assuming
ffprobeduration exists. WebM, fragmented MP4, and in-progress recordings returnN/A. - Retrying an empty render. The input didn't change, so it produces the same empty file and burns quota.
When an empty output is the right answer
Not every empty render is a bug. Scene detection over a static slideshow, silence removal over a silent track, and an empty clip list are correct results that need a workflow branch, not a retry. The real failure is a pipeline that marks the job done and ships the file downstream.
FAQ
Why does FFmpeg exit 0 when the output file is empty?
FFmpeg logs Output file is empty, nothing was encoded at warning level, not error level, so the process returns status 0. It checks the encoded byte total only at the end of the run, after the muxer has written a valid container header. -abort_on empty_output makes it fatal.
How do I make FFmpeg fail loudly when nothing was encoded?
Pass the global option -abort_on empty_output before your inputs, and -abort_on empty_output_stream if one missing stream (audio, usually) should fail the job too. Add an ffprobe check for a video stream and a duration above zero, since the flag catches an empty mux but not a one-frame render.
Why is my empty MP4 a few hundred bytes instead of 0 bytes?
An empty MP4 contains the ftyp and moov boxes FFmpeg writes when it opens the output, with no media samples, so it's small but not zero-length, and any size-based check passes it. Use ffprobe -select_streams v:0 -show_entries stream=codec_name and treat empty output as a failed render.
Does "nothing was encoded" mean my input file is corrupt?
The "nothing was encoded" warning almost never means a corrupt input. Every frame was filtered out or seeked past, usually by -ss, -t, -frames, or a select expression. Broken inputs produce a different message, covered in FFmpeg "Invalid data found" isn't a corrupt video.
Can I detect an empty output before the file reaches my users?
Run ffprobe -v error -show_entries format=duration -show_entries stream=codec_type -of json on every render, in the worker that produced the file, and reject anything without a video stream or with a duration near zero.
If you'd rather stop maintaining that guard in every worker, run the transcode, trim, caption, or frame-extraction step through the FFmpeg Micro API and let the job status tell you what happened. The free tier is enough to compare it on a real file: sign up free.
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

Your HEVC MP4 Won't Play on Apple: The FFmpeg hvc1 Tag Fix
Your HEVC MP4 plays in VLC but not in Safari? The FFmpeg hvc1 tag fix is a metadata-only remux: one stream copy, done in seconds, with zero quality loss.

MP4 Not Playing in Browser? Four Encoder Flags Fix It
MP4 not playing in browser? VLC decodes almost anything, hiding the four real breakers: pixel format, H.264 profile/level, moov placement, audio codec.

Too many packets buffered? max_muxing_queue_size is the last fix
FFmpeg's "Too many packets buffered for output stream" error is a stream mapping problem. Fix the cause first, then size max_muxing_queue_size without an OOM.
Ready to process videos at scale?
Start using FFmpeg Micro's simple API today. No infrastructure required.
Get Started Free