Repository navigation
fix(video): every video path is broken on ffmpeg 8+ because -vsync was removed, and the tests hide it by passing when ffmpeg is absent #1172
Description
Summary
Every video input path in mlxcel is broken on ffmpeg 8 and later. src/multimodal/video.rs:1123 passes -vsync vfr to the ffmpeg subprocess; ffmpeg removed the long-deprecated -vsync option, so the process exits immediately with
Unrecognized option 'vsync'.
Error splitting the argument list: Option not found
and every load_video / load_video_source call fails. This affects the shipped Gemma 4, Gemma 4 Unified, and Kimi-VL video paths, which are the only video-capable families today.
The reason nobody noticed: the video tests pass by silently skipping when ffmpeg is not on PATH, and CI hosts apparently have no ffmpeg. So the suite has been green against a code path that cannot work on a current ffmpeg.
Evidence (2026-08-16)
Host: Apple Silicon, ffmpeg 9.0.1 installed from Homebrew this session. Branch under test carried an unrelated change and touches 0 lines of src/multimodal/video.rs.
A PATH-controlled experiment on the same compiled test binary, same commit, varying only whether ffmpeg is visible:
| arm | ffmpeg on PATH |
cargo test --release -p mlxcel --lib ... multimodal::video::tests |
|---|---|---|
| A | hidden (PATH without Homebrew) |
34 passed, 0 failed |
| B | ffmpeg 9.0.1 | 31 passed, 3 failed |
The three failures:
multimodal::video::tests::load_video_single_pass_produces_correct_frame_count(src/multimodal/video_tests.rs:365)multimodal::video::tests::load_video_source_fd_variant_decodes_via_dev_fd_n(video_tests.rs:611)multimodal::video::tests::load_video_source_fd_variant_matches_path_variant_frame_count(video_tests.rs:655)
All three carry the identical error payload:
Extract { path: "...mlxcel-test-video-64x64-10fps-10s.mp4",
message: "Unrecognized option 'vsync'.\nError splitting the argument list: Option not found\n" }
Direct confirmation against the binary, independent of mlxcel:
$ ffmpeg -hide_banner -loglevel error -f lavfi -i "color=c=red:s=32x32:d=1" -vsync 0 -f null -
Unrecognized option 'vsync'.
Error splitting the argument list: Option not found
The offending call
src/multimodal/video.rs:1117-1130:
let mut cmd = Command::new("ffmpeg");
cmd.args(["-loglevel", "error", "-i"])
.arg(source.ffmpeg_input())
.args([
"-vf",
&format!("select='{select_expr}',setpts=N/FRAME_RATE/TB"),
"-vsync",
"vfr",
"-f",
"image2pipe",
"-vcodec",
"png",
"-",
])grep -rn "fps_mode" src returns nothing, so there is no modern-ffmpeg branch anywhere.
Fix
-vsync vfr became -fps_mode vfr. -fps_mode has been supported since ffmpeg 5.0 (2022), and -vsync was deprecated from that release and removed later, so a straight substitution covers every ffmpeg a user is plausibly running. Prefer the straight substitution over version-sniffing:
"-fps_mode", "vfr",If the project wants to keep working on pre-5.0 ffmpeg, the fallback should be an explicit capability probe (run ffmpeg -hide_banner -h once and cache whether fps_mode is accepted), not a version-string parse. State the chosen minimum in the docs either way.
The larger problem: the tests pass by skipping
The three tests self-skip when ffmpeg is absent and are counted as passed, not ignored. That is why arm A reports 34 passed rather than 31 passed and 3 ignored. A capability the suite cannot exercise should not report as satisfied.
This is the same class of gap as #1168, where no test invoked mlxcel generate --draft-model at all and a permanently-broken path stayed green. Whatever shape the fix takes, the skip must become visible:
- Mark the skip as
ignoredwith a reason, so the count reflects that the capability was not exercised, or - Gate the whole video test module behind an env var (
MLXCEL_TEST_VIDEO=1) that CI sets on a host with ffmpeg, and fail loudly if the var is set and ffmpeg is missing.
Silently-passing skips are worse than a skipped test, because they make an unexercised path indistinguishable from a verified one.
Scope and relationship to other issues
- Pre-existing. Unrelated to any current branch; the failure is a flag rejection by the external ffmpeg binary.
- Blocks feat(models): video input for the Qwen VL families (Qwen3.5-VL / Qwen3.8, then the rest) #1166 (video input for the Qwen VL families). feat(models): video input for the Qwen VL families (Qwen3.5-VL / Qwen3.8, then the rest) #1166's stated validation matrix cannot be executed on a modern-ffmpeg host until this is fixed, and its issue body already records that no video behavior of any kind had been measured. This should land before feat(models): video input for the Qwen VL families (Qwen3.5-VL / Qwen3.8, then the rest) #1166.
- Video decoding is invoked from
src/commands/generate_vlm.rs(compute_gemma4_video_embeddings,compute_gemma4_unified_video_embeddings,compute_kimi_vl_video_embeddings) and the server's equivalent paths, all of which funnel throughmlxcel::video::load_videos.
Acceptance criteria
-
-vsyncreplaced with-fps_mode(or an explicit, cached capability probe), with the supported ffmpeg range stated in the docs -
multimodal::video::testspass on a host with ffmpeg 9.x - The absent-ffmpeg case reports as skipped/ignored rather than passed, so an unexercised video path is visible in the test summary
- End-to-end confirmation:
mlxcel generate --video <clip>produces a correct description on a Gemma 4 or Kimi-VL checkpoint with ffmpeg 9.x on PATH - Closing comment records the ffmpeg version the fix was validated against
Activity
inureyes commentedon Aug 15, 2026
This blocks #1166 (video input for the Qwen VL families) and should land before it, since #1166 cannot run its validation matrix on a modern-ffmpeg host until the flag is fixed. Found while running the verification suite for #1168 after ffmpeg was installed on the development box.
fix(cli): reject a DFlash drafter before the offline standalone load (#…
inureyes commentedon Aug 16, 2026
Fixed in #1183, now at
status:review.Validated against ffmpeg 9.0.1 (Homebrew, Apple Silicon, macOS 26). The 5.0 lower bound in the docs comes from upstream release history, not from measurement: only 9.0.1 is installed on this box.
Chose the straight
-fps_mode vfrsubstitution over a cached capability probe.-fps_modelanded in 5.0 in the same release that deprecated-vsync, so one spelling covers every ffmpeg a user is plausibly running; a probe would buy 4.x support at the cost of a branch that only executes on machines nobody here has, which is the shape of the problem that produced this bug.For the skip, both shapes were needed rather than one.
#[ignore]is the only skip stable libtest reports in the summary line, so the nine ffmpeg-dependent tests carry it and a plain run now reads28 passed; 0 failed; 9 ignoredinstead of an all-green34 passed. But#[ignore]alone would let the opt-in run degrade into a no-op on a host that lost its ffmpeg, soMLXCEL_TEST_VIDEO=1additionally turns a missing binary into a hard failure.make verify-test-videois the pair, andnightly-verify.ymlnow installs ffmpeg and runs it as its own reported step, which is the part that actually closes the hole on the gate protectingmain.Reproduced both arms on one pre-fix binary at
115f6089, varying onlyPATH: ffmpeg hidden gaveok. 34 passed; 0 failed; 1 ignored, ffmpeg visible gaveFAILED. 31 passed; 3 failed; 1 ignored. After the fix,make verify-test-videoreports36 passed; 0 failed; 0 ignored.One finding beyond the reported scope:
extract_frames_preserves_color_increment_per_frame,extract_frames_preserves_channel_order, andextract_frames_preserves_moving_square_positionmatched onload_video's error and skipped, so they swallowed the exactExtract { "Unrecognized option 'vsync'" }error this issue is about. They would otherwise have caught it. Those arms are hard failures now.End to end:
mlxcel generate --videoon a clip that is solid red for two seconds then solid blue, againstgemma-4-12b-it-4bit, returns "The video shows a solid red color that transitions to a solid blue color." Independently of the model, the argument list the runtime now builds splits the PNG pipe into 8 frames whose centre pixels readfe0000x4 then0000ffx4, confirming frame count, temporal order, and RGB channel order.Not verified: Kimi-VL's video path was not run against a checkpoint, though it funnels through the same
load_videosentry point.test(video): bring the tenth ffmpeg-backed test under the video gate
fix(video): replace the removed -vsync with -fps_mode and stop the te…
feat(server): send sampled video frames as ordered images (#1759)