Video Editing

The FFmpeg Commands AI Gets Wrong (and How to Check Them)

A field guide to the ffmpeg mistakes chatbots and coding agents make. Every wrong command below was run on ffmpeg 9.0.2, the error is quoted as it printed, and every fix is a command we ran.

By Linkeddit·October 4, 2026·11 min read

Key takeaways

  • Invented filter options fail loudly with "Option not found"; check any filter with ffmpeg -h filter=NAME.
  • The quiet failures are worse: a copied cut that starts early, atempo without setpts, a dropped track. All exited with code 0.
  • -1 in scale can produce an odd size that libx264 refuses; -2 rounds to an even one.
  • Single-pass loudnorm resampled to 192 kHz and varied the gain; two-pass linear mode gave one constant gain at 48 kHz.
  • Test on one second of footage and read the output with ffprobe.

1Why do ChatGPT and other AI tools get ffmpeg commands wrong?

Because a model predicts what an ffmpeg command usually looks like, and ffmpeg checks only what it actually accepts. The gap shows up as options that do not exist, flags in the wrong place, and commands that run cleanly but produce the wrong file.

In an r/ffmpeg thread about stream mapping and filter labels, one reply admits:

“Most of my filter commands were 90% ChatGPT fails that I tinkered with until they worked.”
r/ffmpeg

Our overview of AI agents editing video covers why the ffmpeg route is the most error-prone. This page goes a level down: seven kinds of mistake, each with the error it produced, the documentation that explains it, and a fix.

How we tested: we made short test clips with ffmpeg's lavfi sources (a 1080p clip with a keyframe every 2 s, a portrait clip, a clip with two audio tracks) and ran every command on ffmpeg 9.0.2. The wrong commands are our examples of a typical assistant shape, not transcripts from a particular chatbot. Our build lacks libass, so burning in captions with the subtitles filter is not covered.

Our summary of what follows:

MistakeWhat happened in our runFix
Invented filter optionError: Option not foundffmpeg -h filter=NAME
-ss before -i with -c copyExit 0; clip starts at the previous keyframeRe-encode the cut
-to after an input -ssExit 0; 5 s clip instead of 2 sPut -to before -i too
-c copy into WebMError: Could not write headerEncode VP9 and Opus
atempo without setptsExit 0; audio 5 s, video 10 sChange both streams
scale=720:-1Error: height not divisible by 2scale=720:-2
Missing or wrong -mapExit 0; track dropped, or no videoMap every stream you want
Single-pass loudnormExit 0; 192 kHz audio, gain variesTwo passes, -ar 48000

2Why does ffmpeg say "Option not found"?

Because the command passes a filter an option it does not have. This is the classic invented flag, and it is the easy case: ffmpeg stops before writing anything. Two examples:

$ ffmpeg -i in.mp4 -vf "scale=1280:720:keep_aspect=1" out.mp4
[fc#-1 @ 0x798b040000] Error applying option 'keep_aspect' to filter 'scale': Option not found

$ ffmpeg -i in.mp4 -af "volume=gain=6dB" out.mp4
[fc#-1 @ 0x7635054500] Error applying option 'gain' to filter 'volume': Option not found

The scale filter has no keep_aspect; the option that keeps the aspect ratio is force_original_aspect_ratio. The volume filter takes its level as volume, which is also its first positional value, so volume=6dB works and gain does not. A poster asking about the lo option of mpdecimate on r/ffmpeg describes the same trap from the other side:

“chatgpt keeps making things up, adding to my confusion.”
r/ffmpeg

The check: ffmpeg -h filter=NAME prints every option the filter has on your build:

$ ffmpeg -hide_banner -h filter=scale | grep -i aspect
   force_original_aspect_ratio <int>  ..FV....... decrease or increase w/h if necessary
                                      to keep the original AR (from 0 to 2) (default disable)

The fixed commands, both of which ran:

# 1080x1350 into 1280x720 without stretching
ffmpeg -i portrait.mp4 -vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2" out.mp4

# Raise the volume by 6 dB
ffmpeg -i in.mp4 -af "volume=6dB" out.mp4

3Why does my ffmpeg cut start at the wrong time?

Usually because -ss is combined with -c copy, which cannot start between keyframes, or because -to is placed where it means something different. Neither prints an error.

The main options documentation says that as an input option, -ss seeks to the closest seek point before your position. When transcoding, the extra segment is decoded and discarded; “When doing stream copy or when -noaccurate_seek is used, it will be preserved.” Our clip has a keyframe every 2 seconds:

# Keyframes in in.mp4: 0, 2, 4, 6, 8 s
$ ffmpeg -ss 3.5 -i in.mp4 -t 2 -c copy clip.mp4    # exit 0, no warning
# ffprobe: 2.07 s.  ffprobe -ignore_editlist 1: 107 video packets, 3.57 s
$ ffmpeg -ss 3.5 -i in.mp4 -t 2 -c copy clip.mkv
# ffprobe: 3.63 s, and 107 frames decoded

The MP4 holds 107 video packets starting from the keyframe at 2.0 s. An edit list hides the first 1.5 s (3.5 minus 2.0), which ffmpeg honours; a player that ignores edit lists shows 1.5 s of extra picture. The MKV simply came out 3.63 s long.

The second trap is -to after an input seek:

$ ffmpeg -ss 3 -i in.mp4 -to 5 -c:v libx264 -c:a aac clip.mp4
# asked for 3 s to 5 s; ffprobe: 5.000000 s long

With -ss before -i, the output's timestamps start at zero, so an output -to 5 stopped five seconds into the clip, at 8 s of the source. The fixed commands:

# Output: 2.000 s
ffmpeg -ss 3.5 -i in.mp4 -t 2 -c:v libx264 -crf 18 -c:a aac clip.mp4

# Output: 2.000 s
ffmpeg -ss 3 -to 5 -i in.mp4 -c:v libx264 -crf 18 -c:a aac clip.mp4

Our rule: re-encode anything that must land on a frame. The trim recipe in our ffmpeg command generator puts both times before the input and lets you choose copy or re-encode.

4Why does ffmpeg fail with -c copy?

Because stream copy moves packets without decoding them, so the output must accept the codec as it is, and nothing can be filtered. The streamcopy section describes it as copying packets “without decoding, filtering, or encoding them.” Two commands that ignore this:

$ ffmpeg -i in.mp4 -c copy out.webm
[webm @ 0x79b2c27700] Only VP8 or VP9 or AV1 video and Vorbis or Opus audio and WebVTT subtitles are supported for WebM.
[out#0/webm @ 0x79b304c300] Could not write header (incorrect codec parameters ?): Invalid argument

$ ffmpeg -i in.mp4 -vf scale=1280:-2 -c copy out.mp4
[vost#0:0/copy @ 0x7c96cb8000] Filtergraph 'scale=1280:-2' was specified, but codec copy was selected. Filtering and streamcopy cannot be used together.

WebM accepts only the codecs the first error lists, so H.264 and AAC from an MP4 cannot be copied into it. And a -vf needs the video decoded, so it cannot sit next to -c copy. Copy the streams you are not changing and encode the rest:

ffmpeg -i in.mp4 -c:v libvpx-vp9 -crf 32 -b:v 0 -row-mt 1 -c:a libopus -b:a 128k out.webm

ffmpeg -i in.mp4 -vf scale=1280:-2 -c:v libx264 -crf 20 -c:a copy out.mp4

5Why is my sped-up video out of sync, or atempo out of range?

atempo changes only the audio. Speed the picture with setpts in the same command, and keep each atempo between 0.5 and 100.

$ ffmpeg -i in.mp4 -af atempo=2 fast.mp4
# exit 0. ffprobe: video 10.000000 s, audio 5.006500 s

$ ffmpeg -i in.mp4 -af atempo=0.25 slow.mp4
[Parsed_atempo_0 @ 0x7597061380] Value 0.250000 for parameter 'tempo' out of range [0.5 - 100]
[fc#-1 @ 0x7597068500] Error applying option 'tempo' to filter 'atempo': Result too large

The first command exits cleanly and leaves a 10 s picture over 5 s of sound. The second hits the range in the atempo documentation: “Tempo must be in the [0.5, 100.0] range.” The same page warns that above 2 the filter “will skip some samples rather than blend them in” and suggests chaining instances. Our build accepted 4, but chaining is the cleaner choice. The fixed commands:

# 2x: video 5.07 s, audio 5.01 s
ffmpeg -i in.mp4 -filter_complex "[0:v]setpts=PTS/2[v];[0:a]atempo=2[a]" \
  -map "[v]" -map "[a]" -c:v libx264 -crf 20 -c:a aac fast.mp4

# Quarter speed of the first 2 s: video 7.90 s, audio 7.95 s
ffmpeg -t 2 -i in.mp4 -filter_complex "[0:v]setpts=4*PTS[v];[0:a]atempo=0.5,atempo=0.5[a]" \
  -map "[v]" -map "[a]" -c:v libx264 -crf 20 -c:a aac slow.mp4

The quarter-speed clip should last 8 s (2 x 4). The video measured 7.90 s and the audio 7.95 s, both within one slowed frame (4/30 s, about 0.13 s) of that. Our generator's speed recipe chains atempo for factors outside 0.5 to 2.

6Why does ffmpeg say "height not divisible by 2"?

Because -1 in the scale filter keeps the aspect ratio exactly, which can give an odd number, and libx264 with 4:2:0 colour refuses odd sizes. Use -2.

$ ffmpeg -i in.mp4 -vf scale=720:-1 -c:v libx264 small.mp4
[libx264 @ 0x7b84c49180] height not divisible by 2 (720x405)
...
Conversion failed!

720 / 1920 x 1080 = 405, an odd height. The scale documentation says that with -n, the filter keeps the aspect ratio and then makes “sure that the calculated dimension is divisible by n”:

# -2 keeps the aspect ratio and rounds to an even number (output: 720x406)
ffmpeg -i in.mp4 -vf scale=720:-2 -c:v libx264 -crf 20 -c:a aac small.mp4

A dry run with -f null - missed this one; only a real one-second encode showed the error.

7Why is an audio track or the video missing from my output?

Without -map, ffmpeg keeps one stream of each type. With -map, it keeps only what you list. Both rules cause silent losses.

# two-audio.mp4 has one video and two audio streams
$ ffmpeg -i two-audio.mp4 -c copy out.mp4
Stream mapping:
  Stream #0:0 -> #0:0 (copy)
  Stream #0:1 -> #0:1 (copy)
# exit 0; the second audio track is gone

$ ffmpeg -i in.mp4 -i music.m4a -map 1:a -c copy -shortest out.mp4
# exit 0; ffprobe: one audio stream, 12 s, no video

$ ffmpeg -i in.mp4 -map 0:a:1 -c copy out.mp4
Stream map '' matches no streams.
To ignore this, add a trailing '?' to the map.

The automatic stream selection rules pick, for audio, “the stream with the most channels”, so a second commentary or language track is dropped without a word. The manual selection rule is the opposite: “When -map is used, only user-mapped streams are included”, so mapping only the music produced an audio-only file. The third command asks for a second audio stream that does not exist; on our build the error even printed the map as empty quotes. The fixed commands:

# Output: video + both audio tracks
ffmpeg -i two-audio.mp4 -map 0 -c copy out.mp4

# Output: 10.0 s, video + music
ffmpeg -i in.mp4 -i music.m4a -map 0:v -map 1:a -c copy -shortest out.mp4

8Why does loudnorm change my sample rate, and should it be one pass or two?

Single-pass loudnorm works in dynamic mode, which upsamples to 192 kHz and rides the gain over time. For files, measure first and apply one constant gain in a second pass, with -ar set.

The loudnorm documentation says “In dynamic mode, to accurately detect true peaks, the audio stream will be upsampled to 192 kHz” and tells you to set the output rate with -ar or aresample. The one-line form:

$ ffmpeg -i talk.mp4 -af loudnorm=I=-14:TP=-1:LRA=11 -c:v copy out.mp4
  Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 96000 Hz, mono, fltp, 69 kb/s (default)
# Same filter to WAV: 192000 Hz

Our talk.mp4 alternates two tones 20 dB apart and measured -24.3 LUFS. The single pass hit -13.8 LUFS, but the gain varied: the quiet part at 0.5 s rose from -43.0 to -28.6 dB mean volume (+14.4 dB), the one at 8.5 s only to -32.6 dB (+10.4 dB). Adding -ar 48000 fixes the rate, not the riding gain. The two-pass version:

# Pass 1: measure only
$ ffmpeg -i talk.mp4 -af loudnorm=I=-14:TP=-1:LRA=11:print_format=json -f null -
  "input_i" : "-24.32",
  "input_tp" : "-19.96",
  "input_lra" : "3.10",
  "input_thresh" : "-36.53",
  "target_offset" : "-0.12"
# Pass 2: one constant gain, at 48 kHz (output: -14.0 LUFS, 48000 Hz)
ffmpeg -i talk.mp4 -af loudnorm=I=-14:TP=-1:LRA=11:measured_I=-24.32:measured_TP=-19.96:measured_LRA=3.10:measured_thresh=-36.53:offset=-0.12:linear=true:print_format=json \
  -ar 48000 -c:v copy out.mp4
# its report ends with:  "normalization_type" : "linear"

Result: -14.0 LUFS at 48 kHz, and every section we sampled rose by the same 10.3 dB (-43.0 to -32.7, -23.0 to -12.7). One catch: the docs say linear mode reverts to dynamic if the target loudness range is below the source's or the gain would push true peaks over your TP limit. A creator who made a parody ad with AI agents wrote in the skill file they shared:

“apply a limiter BEFORE loudnorm (ffmpeg's loudnorm silently changes mode on peaky mixes)”
r/StartupSoloFounder

We reproduced it with a clicky test signal:

# A quiet tone with a loud click every 2 s (input true peak -0.63 dBTP),
# pass 2 with linear=true and the measured values
  "output_i" : "-14.17",
  "normalization_type" : "dynamic",

Without print_format, nothing about the switch was printed, so always read normalization_type. Our free loudness checker gives a second opinion on the result.

9How do you check an AI-written ffmpeg command before running it?

Check every filter option against its help text, run the command on one second of footage with the real encoder, and read the output with ffprobe. Five of the eight failures in the table exited with code 0, so “it ran” proves little.

ffmpeg -hide_banner -h filter=atempo          # every option the filter really has
ffmpeg -v error -t 1 -i in.mp4 -vf "..." -f null -     # option errors, no file written
ffmpeg -v error -t 1 -i in.mp4 -vf "..." -c:v libx264 test.mp4   # encoder limits too
ffprobe -v error -show_entries stream=codec_type,duration,width,height,sample_rate -of csv=p=0 out.mp4

Or skip generation for common jobs: our ffmpeg command generator builds twelve recipes from a closed set of flags, each linked to its documentation. Our Claude Code guide walks through a full ffmpeg pipeline.

Disclosure: we make Linkeddit Studio, a desktop video editor for Mac and Windows that takes a different route. The agent in Studio sends named edit commands to Studio's own editing tools instead of command lines, and a batch of edits is one undo step. Other editors with agent tools are in our video editing MCP servers comparison.

Let your agent edit without inventing flags

Linkeddit Studio is a desktop editor for Mac and Windows where Claude Code or Codex edits a real timeline through named edit commands, looks at rendered frames and measures loudness, with every edit undoable. Files stay on your disk. One-time $99, no credits.
See Linkeddit Studio

Frequently asked questions

Why does ChatGPT make up ffmpeg options?+

A language model predicts what a command usually looks like, and ffmpeg has hundreds of filters with their own option names. A plausible option that does not exist is an easy mistake; ffmpeg rejects it with "Option not found".

How do I check whether an ffmpeg filter option exists?+

Run ffmpeg -h filter=NAME, for example ffmpeg -h filter=scale. It prints every option that filter accepts on your build, with ranges and defaults.

Why is my ffmpeg -ss cut with -c copy not accurate?+

Stream copy cannot start between keyframes. The ffmpeg docs say that with stream copy, the segment between the earlier seek point and your position is preserved. Re-encode the cut for frame accuracy.

What is the atempo range in ffmpeg?+

0.5 to 100 in the current documentation and on our ffmpeg 9.0.2 build. Above 2 it skips samples, so chain instances (atempo=2,atempo=2 for 4x). It changes audio only; use setpts for the picture.

Why does ffmpeg loudnorm output 192 kHz audio?+

In dynamic mode loudnorm upsamples to 192 kHz to detect true peaks, and the docs tell you to set the output rate with -ar or aresample. In our test the MP4 came out as 96 kHz AAC and a WAV as 192 kHz until we added -ar 48000.