diff --git a/tests/multimodal/test_video.py b/tests/multimodal/test_video.py index 9c24b5f96932..d1d36dc53fa9 100644 --- a/tests/multimodal/test_video.py +++ b/tests/multimodal/test_video.py @@ -24,6 +24,7 @@ GLM46VVideoBackend, GLMGAVideoBackend, Molmo2VideoBackend, + OpenCVDynamicOpenPanguVideoBackend, Qwen2VLVideoBackend, Qwen3VLVideoBackend, VideoBackend, @@ -1437,6 +1438,27 @@ def test_glm46v_dynamic_fps_thresholds( assert indices == sorted(set(indices)), "Indices must be sorted and deduplicated" +@pytest.mark.parametrize("fps", [2, -1]) +def test_openpangu_num_frames_sentinel(fps): + """`num_frames=-1` (the default / "no cap" sentinel) must sample frames + instead of raising `ValueError` from `np.linspace(..., -1)`.""" + source = VideoSourceMetadata( + total_frames_num=100, original_fps=30, duration=100 / 30 + ) + target = VideoTargetMetadata(num_frames=-1, fps=fps, max_duration=300) + + indices = OpenCVDynamicOpenPanguVideoBackend.compute_frames_index_to_sample( + source, target + ) + + assert len(indices) > 0 + assert indices == sorted(indices) + assert all(0 <= idx < source.total_frames_num for idx in indices) + # fps=-1 means "no fps limit" -> sample every frame. + if fps == -1: + assert len(indices) == source.total_frames_num + + def test_glm46v_even_frame_count_enforcement(): """Test that GLM-4.6V always returns an even number of frames.""" target = VideoTargetMetadata(num_frames=-1, fps=-1, max_duration=-1) diff --git a/vllm/multimodal/video.py b/vllm/multimodal/video.py index 854b5572b3ef..59faf9998acd 100644 --- a/vllm/multimodal/video.py +++ b/vllm/multimodal/video.py @@ -1220,14 +1220,20 @@ def compute_frames_index_to_sample( # `fps` is the FPS parameter passed in for sampling, # -1 indicates that sampling can be performed directly without FPS limitation. if fps > 0: - # Num_frames is the maximum number of frames to sample. + # Num_frames is the maximum number of frames to sample; `num_frames < 0` + # means "no cap" and defers entirely to the fps-derived count. # If fewer frames are sampled at this sample_fps, the update duration will be longer. # noqa: E501 - if num_frames >= int(total_duration * fps) + 1: - num_frames = int(total_duration * fps) + 1 + max_num_frames = int(total_duration * fps) + 1 + if num_frames < 0 or num_frames >= max_num_frames: + num_frames = max_num_frames # Under the new maximum frame rate, the video duration of the rightmost frame, # noqa: E501 # cannot be calculated for frame 0. total_duration = min(total_duration, (num_frames - 1) / fps) - elif fps != -1: + elif fps == -1: + # No fps limit: `num_frames < 0` means sample every frame. + if num_frames < 0: + num_frames = total_frames_num + else: raise ValueError( f"requires dataset fps is -1 or greater than 0 but got {fps}" )