Repository: huggingface/transformers
Pull request: Qwen-VL video processor accepts min/max pixels

# What does this PR do?

Fixes https://github.com/huggingface/transformers/issues/43183 and makes sure we can pass `min/max pixels` to video processor's call.

Changed files:
filename: src/transformers/models/qwen2_vl/video_processing_qwen2_vl.py
patch: @@ -107,6 +107,29 @@ def __init__(self, **kwargs: Unpack[Qwen2VLVideoProcessorInitKwargs]):
 
         super().__init__(size=size, min_pixels=min_pixels, max_pixels=max_pixels, **kwargs)
 
+    def _further_process_kwargs(
+        self,
+        size: SizeDict | None = None,
+        min_pixels: int | None = None,
+        max_pixels: int | None = None,
+        **kwargs,
+    ) -> dict:
+        """
+        Update kwargs that need further processing before being validated
+        Can be overridden by subclasses to customize the processing of kwargs.
+        """
+        if min_pixels is not None and max_pixels is not None:
+            size = {"shortest_edge": min_pixels, "longest_edge": max_pixels}
+        elif size is not None:
+            if "shortest_edge" not in size or "longest_edge" not in size:
+                raise ValueError("dictionary `size` must contain 'shortest_edge' and 'longest_edge' keys.")
+            min_pixels = size["shortest_edge"]
+            max_pixels = size["longest_edge"]
+        else:
+            size = {**self.size}
+
+        return super()._further_process_kwargs(size=size, min_pixels=min_pixels, max_pixels=max_pixels, **kwargs)
+
     def sample_frames(
         self,
         metadata: VideoMetadata,

filename: src/transformers/models/video_llama_3/modular_video_llama_3.py
patch: @@ -1473,6 +1473,9 @@ class VideoLlama3VideoProcessor(Qwen2VLVideoProcessor):
     valid_kwargs = VideoLlama3VideoProcessorInitKwargs
     model_input_names = ["pixel_values_videos", "video_grid_thw", "video_merge_sizes", "video_compression_mask"]
 
+    def _further_process_kwargs(self):
+        raise AttributeError("VideoLlama3 never supported min/max pixels, no need to copy from Qwen")
+
     def _get_compression_mask(
         self,
         pixel_values_videos: torch.FloatTensor,

filename: tests/models/qwen2_vl/test_video_processing_qwen2_vl.py
patch: @@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import tempfile
 import unittest
 
 import numpy as np
@@ -361,3 +362,20 @@ def test_num_frames_equal_temporal_patch_size_plus_two(self):
 
             video_grid_thw = video_processed["video_grid_thw"]
             self.assertEqual(video_grid_thw.tolist(), [[2, 2, 2]])
+
+    def test_bc_min_max_pixels(self):
+        for video_processing_class in self.video_processor_list:
+            video_processing = video_processing_class(**self.video_processor_dict)
+            with tempfile.TemporaryDirectory() as tmpdirname:
+                video_processing.save_pretrained(tmpdirname)
+                video_processing_loaded = video_processing_class.from_pretrained(
+                    tmpdirname, max_pixels=56 * 56, min_pixels=28 * 28
+                )
+
+            video_inputs = self.video_processor_tester.prepare_video_inputs(
+                equal_resolution=True,
+                return_tensors="torch",
+            )
+            processed = video_processing_loaded(video_inputs, return_tensors="pt")
+            expected_output_video_shape = [320, 1176]
+            self.assertListEqual(list(processed.pixel_values_videos.shape), expected_output_video_shape)