Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def decode_for_motion( # noqa: C901
stream = input_container.streams.video[0]
ctx = stream.codec_context
# Set this flag to return motion vectors
ctx.flags2 |= av.codec.context.Flags2.EXPORT_MVS
ctx.flags2 |= av.codec.context.Flags2.export_mvs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Motion Flag Lookup Can Fail

Real decode_for_motion() calls reach this line before any frames are decoded, but PyAV exposes export_mvs as the codec-context flag attribute while the Flags2 enum member is EXPORT_MVS. If PyAV 17.1.0 does not define a lowercase class member, this raises AttributeError at runtime and the current tests miss it because they mock the AV stack.

ctx.thread_type = av.codec.context.ThreadType.AUTO
ctx.thread_count = thread_count
mv_data = []
Expand Down
14 changes: 9 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,8 @@ text_cuda12 = [

# Video Curation Dependencies
video_cpu = [
"av==13.1.0",
"opencv-python",
"av==17.1.0", # ffmpeg 8.1.1 (CVE: was 7.0.2)
"opencv-python-headless", # dedupe with vllm's headless
"torchvision",
"einops",
"easydict",
Expand All @@ -230,11 +230,16 @@ video_cuda12 = [
"nemo_curator[vllm]",
"cvcuda_cu12",
"pycuda",
"PyNvVideoCodec==2.0.2; (platform_machine == 'x86_64' and platform_system != 'Darwin')",
"torch<=2.10.0",
"torchaudio",
]
Comment on lines 230 to 235

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 CUDA Extra Drops NVDEC

Existing x86_64 Linux users who install nemo_curator[video_cuda12] no longer get PyNvVideoCodec, but direct NV codec paths such as the stitching decoder still instantiate VideoBatchDecoder and raise when that package is missing. This changes an established CUDA video install from working hardware decode to a runtime crash unless users discover and install the new video_media extra.


# NVDEC HW decode; opt-in, kept off `all` (wheel vendors CVE ffmpeg). CPU-decode fallback exists.
video_media = [
"nemo_curator[video_cuda12]",
"PyNvVideoCodec==2.0.2; (platform_machine == 'x86_64' and platform_system != 'Darwin')",
]

# Math Curation Dependencies
math_cpu = [
"nemo_curator[text_cpu]", # Math examples use text processing utilities
Expand All @@ -250,10 +255,9 @@ math_cuda12 = [

# Multimodal / Interleaved Dependencies
interleaved_cpu = [
"albumentations",
"matplotlib",
"open_clip_torch",
"opencv-python",
"opencv-python-headless", # dedupe with vllm's headless
"Pillow",
"pypdfium2",
"s3fs>=2024.12.0",
Expand Down
24 changes: 11 additions & 13 deletions tests/stages/video/filtering/test_motion_vector_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def test_video_resolution_too_small_error(self):
"""Test that VideoResolutionTooSmallError is raised for small resolution."""
mock_video = io.BytesIO(b"mock_video_data")

with patch("av.open") as mock_open, patch("av.codec.context.Flags2.EXPORT_MVS", create=True):
with patch("av.open") as mock_open:
# Mock frame with small resolution
mock_frame = Mock()
mock_frame.height = 100 # Less than 256
Expand Down Expand Up @@ -211,7 +211,7 @@ def test_successful_decode(self):
"""Test successful decode operation."""
mock_video = io.BytesIO(b"mock_video_data")

with patch("av.open") as mock_open, patch("av.codec.context.Flags2.EXPORT_MVS", create=True):
with patch("av.open") as mock_open:
# Mock motion vector side data
mock_side_data = Mock()
mock_side_data.type = Mock()
Expand Down Expand Up @@ -260,18 +260,17 @@ def test_successful_decode(self):

mock_open.return_value = mock_container

with patch("av.sidedata.sidedata.Type.MOTION_VECTORS", create=True):
result = decode_for_motion(mock_video)
result = decode_for_motion(mock_video)

assert isinstance(result, DecodedData)
assert len(result.frames) == 1
assert result.frame_size == torch.Size([480, 640, 3])
assert isinstance(result, DecodedData)
assert len(result.frames) == 1
assert result.frame_size == torch.Size([480, 640, 3])

def test_no_motion_vectors(self):
"""Test decode with no motion vectors."""
mock_video = io.BytesIO(b"mock_video_data")

with patch("av.open") as mock_open, patch("av.codec.context.Flags2.EXPORT_MVS", create=True):
with patch("av.open") as mock_open:
# Mock frame with no motion vector side data
mock_frame = Mock()
mock_frame.height = 480
Expand Down Expand Up @@ -307,7 +306,7 @@ def test_custom_parameters(self):
"""Test decode with custom parameters."""
mock_video = io.BytesIO(b"mock_video_data")

with patch("av.open") as mock_open, patch("av.codec.context.Flags2.EXPORT_MVS", create=True):
with patch("av.open") as mock_open:
# Mock motion vector side data
mock_side_data = Mock()
mock_side_data.type = Mock()
Expand Down Expand Up @@ -356,11 +355,10 @@ def test_custom_parameters(self):

mock_open.return_value = mock_container

with patch("av.sidedata.sidedata.Type.MOTION_VECTORS", create=True):
result = decode_for_motion(mock_video, thread_count=8, target_fps=5.0, target_duration_ratio=0.3)
result = decode_for_motion(mock_video, thread_count=8, target_fps=5.0, target_duration_ratio=0.3)

assert isinstance(result, DecodedData)
assert result.frame_size == torch.Size([480, 640, 3])
assert isinstance(result, DecodedData)
assert result.frame_size == torch.Size([480, 640, 3])


class TestCheckIfSmallMotion:
Expand Down
Loading
Loading