Skip to content

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

Closed

Description

@inureyes

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 ignored with 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

Acceptance criteria

  • -vsync replaced with -fps_mode (or an explicit, cached capability probe), with the supported ffmpeg range stated in the docs
  • multimodal::video::tests pass 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

  1. added
    type:bugBug fixes, error corrections, or issue resolutions
    area:modelsModel architectures, weights, loading, metadata
    on Aug 15, 2026
  2. inureyes commented on Aug 15, 2026

    @inureyes
    MemberAuthor

    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.

  3. inureyes commented on Aug 16, 2026

    @inureyes
    MemberAuthor

    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 vfr substitution over a cached capability probe. -fps_mode landed 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 reads 28 passed; 0 failed; 9 ignored instead of an all-green 34 passed. But #[ignore] alone would let the opt-in run degrade into a no-op on a host that lost its ffmpeg, so MLXCEL_TEST_VIDEO=1 additionally turns a missing binary into a hard failure. make verify-test-video is the pair, and nightly-verify.yml now installs ffmpeg and runs it as its own reported step, which is the part that actually closes the hole on the gate protecting main.

    Reproduced both arms on one pre-fix binary at 115f6089, varying only PATH: ffmpeg hidden gave ok. 34 passed; 0 failed; 1 ignored, ffmpeg visible gave FAILED. 31 passed; 3 failed; 1 ignored. After the fix, make verify-test-video reports 36 passed; 0 failed; 0 ignored.

    One finding beyond the reported scope: extract_frames_preserves_color_increment_per_frame, extract_frames_preserves_channel_order, and extract_frames_preserves_moving_square_position matched on load_video's error and skipped, so they swallowed the exact Extract { "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 --video on a clip that is solid red for two seconds then solid blue, against gemma-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 read fe0000 x4 then 0000ff x4, 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_videos entry point.

  4. self-assigned this
    on Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:modelsModel architectures, weights, loading, metadatapriority:highHigh prioritystatus:doneCompletedtype:bugBug fixes, error corrections, or issue resolutions

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions