From 3ff14375ab926ee0b9aac1b9ea064b5cba4b1a27 Mon Sep 17 00:00:00 2001 From: Shreyas Misra Date: Wed, 7 Oct 2026 10:04:03 -0700 Subject: [PATCH] fix(frontend): use container duration for WebM decoding Fall back to container duration when the video stream duration is unknown. Cover the reported finalized VP9 WebM through the real decoder with all-frame, FPS-based, and fixed-frame sampling. Fixes #15811 Signed-off-by: Shreyas Misra (cherry picked from commit e46c79195aa3b138aa99e4a3e1cbddb0f4c54b14) --- .../src/preprocessor/media/decoders/video.rs | 62 +++++++++++++++++- lib/llm/tests/data/media/224p_6_vp9.webm | Bin 0 -> 846 bytes 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 lib/llm/tests/data/media/224p_6_vp9.webm diff --git a/lib/llm/src/preprocessor/media/decoders/video.rs b/lib/llm/src/preprocessor/media/decoders/video.rs index 3a5d7f26d860..4d3dcfc3a629 100644 --- a/lib/llm/src/preprocessor/media/decoders/video.rs +++ b/lib/llm/src/preprocessor/media/decoders/video.rs @@ -239,10 +239,17 @@ fn decode_video(config: &VideoDecoder, bytes: Vec) -> Result 0, "Cannot determine the video frame rate" ); + let source_duration = if input_stream.duration() > 0 { + Time::new(Some(input_stream.duration()), stream_time_base).as_secs() as f64 + } else { + // WebM may expose duration only at container level, in AV_TIME_BASE units. + // Clamp unknown (AV_NOPTS_VALUE) or nonpositive durations to zero. + input.duration().max(0) as f64 / f64::from(ffmpeg_next::ffi::AV_TIME_BASE) + }; ( input_stream.index(), stream_time_base, - Time::new(Some(input_stream.duration()), stream_time_base).as_secs() as f64, + source_duration, (frame_rate.numerator() as f32 / frame_rate.denominator() as f32) as f64, input_stream.frames().max(0) as u64, input_stream.parameters(), @@ -537,6 +544,59 @@ mod tests { assert!(msg.contains("libvpx-vp9"), "no re-encode hint in: {msg}"); } + #[rstest] + #[case(None, None, 6)] + #[case(Some(1.0), None, 3)] + #[case(None, Some(2), 2)] + fn test_decode_webm_with_container_duration( + #[case] fps: Option, + #[case] num_frames: Option, + #[case] expected_frames: usize, + ) { + // Finalized six-frame VP9 WebM from the reported data-URL request. + let path = concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/data/media/224p_6_vp9.webm" + ); + let input = ffmpeg_next::format::input(&path).unwrap(); + let stream = input + .streams() + .best(ffmpeg_next::media::Type::Video) + .unwrap(); + assert_eq!(stream.duration(), ffmpeg_next::ffi::AV_NOPTS_VALUE); + assert_eq!(stream.frames(), 0); + assert_eq!( + input.duration(), + 3 * i64::from(ffmpeg_next::ffi::AV_TIME_BASE) + ); + + let decoder = VideoDecoder { + fps, + num_frames, + strict: true, + ..Default::default() + }; + let decoded = decoder + .decode(EncodedMediaData::from_bytes(std::fs::read(path).unwrap())) + .unwrap(); + assert_eq!( + decoded.tensor_info.shape, + vec![expected_frames, 224, 224, 3] + ); + let Some(DecodedMediaMetadata::Video(metadata)) = decoded.tensor_info.metadata else { + panic!("missing video metadata"); + }; + assert_eq!(metadata.source_duration, 3.0); + assert_eq!(metadata.source_fps, 2.0); + assert_eq!(metadata.sampled_timestamps.len(), expected_frames); + assert!( + metadata + .sampled_timestamps + .iter() + .all(|t| (0.0..3.0).contains(t)) + ); + } + #[test] fn test_decode_video_num_frames() { let (encoded_data, width, height, _total_frames) = load_test_video("240p_10.mp4"); diff --git a/lib/llm/tests/data/media/224p_6_vp9.webm b/lib/llm/tests/data/media/224p_6_vp9.webm new file mode 100644 index 0000000000000000000000000000000000000000..180e668f6be2be0a2ac3295d0cf103e381d5ea6a GIT binary patch literal 846 zcmb1gy}x+AQ(GgW({~{L)X3uWxsk)EsiizMDc7mJk;$pGkx3%BA)S!{1eoOneP@^K z^4;AXyt+lyb7flan#P3?o><7bY#{HN@9rL;oKVQ&^x!p3j0zC0Oz>`?WD_&T8;Cg! zkznI!L4aJfLF@X)W_~9J--aHa#IiIqLp^gnLj!|wM-;A0XNSY`0NcC} zjf}-dJN_ML&B~Ce&7IT8ptN;Pb7@{mTWeT+Sb$}7Bct-;<`&trsSFP$Y-oJ2tMS3C zMyAjWEulLb89^R*6e}+NmVBTg{lrw)#ofM+Uj9MxPC<@-E-vjYnZ^0J=_jVSF7Ea9 z^aF~AxdsJ$`un-GH=7z7rJq>rx_F|qzi)tJh^Lc}D@0eYOM4GU8xR}i7MK{OpP1>o zxZTyy+26%A2q=f_#o}UznaKyblM&8OKQYgBagPVkb|2TEct1y9SD==#%#_r8kHox` zoYbQ96SG|xcZRw8xd5Hx33Ovmv_UK!rJq=eY}pK;Wy!$sG_(W-s6tL=QdvQTZdrk4 z`iX_Ei#uFGgMcmvd1tbLffWcD>lwfRgS_*$Qio&D8yOaFY-C_)X!2}!V&MJ2`;Eau zMMlC4D3bWiAVap=fkDVNy@64@aMrTSJw2_3Vyp9`7#2@&WcQA;q8F5+C+_aw+{m!K3z&00HZj?J HXk-QeKkyUm literal 0 HcmV?d00001