|
42 | 42 | H265_10BITS, |
43 | 43 | H265_VIDEO, |
44 | 44 | in_fbcode, |
| 45 | + IS_WINDOWS, |
45 | 46 | make_video_decoder, |
46 | 47 | NASA_AUDIO, |
47 | 48 | NASA_AUDIO_MP3, |
48 | 49 | NASA_AUDIO_MP3_44100, |
49 | 50 | NASA_VIDEO, |
| 51 | + NASA_VIDEO_HDR, |
50 | 52 | NASA_VIDEO_ROTATED, |
51 | 53 | needs_cuda, |
52 | 54 | needs_ffmpeg_cli, |
|
57 | 59 | SINE_MONO_S32_44100, |
58 | 60 | SINE_MONO_S32_8000, |
59 | 61 | TEST_NON_ZERO_START, |
| 62 | + TEST_SRC_2_12BIT_HDR, |
60 | 63 | TEST_SRC_2_720P, |
61 | 64 | TEST_SRC_2_720P_H265, |
| 65 | + TEST_SRC_2_720P_HDR, |
62 | 66 | TEST_SRC_2_720P_MPEG4, |
63 | 67 | TEST_SRC_2_720P_VP8, |
64 | 68 | TEST_SRC_2_720P_VP9, |
@@ -2360,6 +2364,145 @@ def test_beta_backend_still_supported_for_bc(self): |
2360 | 2364 | dec[0] |
2361 | 2365 | assert dec.cpu_fallback._backend == "CUDA" |
2362 | 2366 |
|
| 2367 | + @staticmethod |
| 2368 | + def _assert_float32_frame_matches_rgb48_ref(frame_data, asset, frame_index): |
| 2369 | + frame_as_uint16 = (frame_data * 65535).round().to(torch.uint16) |
| 2370 | + ref = asset.get_frame_data_by_index_rgb48(frame_index) |
| 2371 | + torch.testing.assert_close(frame_as_uint16, ref, rtol=0, atol=0) |
| 2372 | + |
| 2373 | + @pytest.mark.xfail( |
| 2374 | + IS_WINDOWS and ffmpeg_major_version < 5, |
| 2375 | + reason="swscale YUV->RGB differs on Windows + FFmpeg 4", |
| 2376 | + ) |
| 2377 | + @pytest.mark.parametrize( |
| 2378 | + "asset", |
| 2379 | + (NASA_VIDEO, NASA_VIDEO_HDR, TEST_SRC_2_720P_HDR, TEST_SRC_2_12BIT_HDR), |
| 2380 | + ) |
| 2381 | + def test_output_dtype_uint8(self, asset): |
| 2382 | + decoder = VideoDecoder(asset.path, output_dtype=torch.uint8) |
| 2383 | + frame_indices = [0, 5, 10] |
| 2384 | + for frame_index in frame_indices: |
| 2385 | + frame = decoder[frame_index] |
| 2386 | + assert frame.dtype == torch.uint8 |
| 2387 | + assert_frames_equal(frame.data, asset.get_frame_data_by_index(frame_index)) |
| 2388 | + |
| 2389 | + @pytest.mark.xfail( |
| 2390 | + IS_WINDOWS and ffmpeg_major_version < 5, |
| 2391 | + reason="swscale YUV->RGB48 differs on Windows + FFmpeg 4", |
| 2392 | + ) |
| 2393 | + @pytest.mark.parametrize( |
| 2394 | + "asset", |
| 2395 | + (NASA_VIDEO, NASA_VIDEO_HDR, TEST_SRC_2_720P_HDR, TEST_SRC_2_12BIT_HDR), |
| 2396 | + ) |
| 2397 | + def test_output_dtype_float32(self, asset): |
| 2398 | + decoder = VideoDecoder(asset.path, output_dtype=torch.float32) |
| 2399 | + frame_indices = [0, 5, 10] |
| 2400 | + for frame_index in frame_indices: |
| 2401 | + frame = decoder[frame_index] |
| 2402 | + assert frame.dtype == torch.float32 |
| 2403 | + |
| 2404 | + self._assert_float32_frame_matches_rgb48_ref(frame.data, asset, frame_index) |
| 2405 | + |
| 2406 | + @pytest.mark.xfail( |
| 2407 | + IS_WINDOWS and ffmpeg_major_version < 5, |
| 2408 | + reason="swscale YUV->RGB48 differs on Windows + FFmpeg 4", |
| 2409 | + ) |
| 2410 | + @pytest.mark.parametrize( |
| 2411 | + "asset, is_hdr", |
| 2412 | + ( |
| 2413 | + (NASA_VIDEO, False), |
| 2414 | + (NASA_VIDEO_HDR, True), |
| 2415 | + (TEST_SRC_2_720P_HDR, True), |
| 2416 | + (TEST_SRC_2_12BIT_HDR, True), |
| 2417 | + ), |
| 2418 | + ) |
| 2419 | + def test_output_dtype_auto(self, asset, is_hdr): |
| 2420 | + decoder = VideoDecoder(asset.path, output_dtype="auto") |
| 2421 | + frame_indices = [0, 5, 10] |
| 2422 | + for frame_index in frame_indices: |
| 2423 | + frame = decoder[frame_index] |
| 2424 | + |
| 2425 | + if is_hdr: |
| 2426 | + assert frame.dtype == torch.float32 |
| 2427 | + frame_as_uint16 = (frame.data * 65535).round().to(torch.uint16) |
| 2428 | + ref = asset.get_frame_data_by_index_rgb48(frame_index) |
| 2429 | + torch.testing.assert_close(frame_as_uint16, ref, rtol=0, atol=0) |
| 2430 | + else: |
| 2431 | + assert frame.dtype == torch.uint8 |
| 2432 | + ref = asset.get_frame_data_by_index(frame_index) |
| 2433 | + assert_frames_equal(frame.data, ref) |
| 2434 | + |
| 2435 | + @pytest.mark.xfail( |
| 2436 | + IS_WINDOWS and ffmpeg_major_version < 5, |
| 2437 | + reason="swscale YUV->RGB48 differs on Windows + FFmpeg 4", |
| 2438 | + ) |
| 2439 | + @pytest.mark.parametrize( |
| 2440 | + "asset", |
| 2441 | + (NASA_VIDEO, NASA_VIDEO_HDR, TEST_SRC_2_720P_HDR, TEST_SRC_2_12BIT_HDR), |
| 2442 | + ) |
| 2443 | + def test_output_dtype_float32_batch_apis(self, asset): |
| 2444 | + decoder = VideoDecoder(asset.path, output_dtype=torch.float32) |
| 2445 | + indices = [0, 5, 10] |
| 2446 | + |
| 2447 | + # get_frame_at |
| 2448 | + self._assert_float32_frame_matches_rgb48_ref( |
| 2449 | + decoder.get_frame_at(0).data, asset, 0 |
| 2450 | + ) |
| 2451 | + |
| 2452 | + # get_frames_at |
| 2453 | + frames = decoder.get_frames_at(indices) |
| 2454 | + for i, idx in enumerate(indices): |
| 2455 | + self._assert_float32_frame_matches_rgb48_ref(frames.data[i], asset, idx) |
| 2456 | + |
| 2457 | + # get_frames_in_range |
| 2458 | + frames_range = decoder.get_frames_in_range(start=5, stop=11) |
| 2459 | + self._assert_float32_frame_matches_rgb48_ref(frames_range.data[0], asset, 5) |
| 2460 | + self._assert_float32_frame_matches_rgb48_ref(frames_range.data[5], asset, 10) |
| 2461 | + |
| 2462 | + @pytest.mark.xfail( |
| 2463 | + IS_WINDOWS and ffmpeg_major_version < 5, |
| 2464 | + reason="swscale YUV->RGB48 differs on Windows + FFmpeg 4", |
| 2465 | + ) |
| 2466 | + @pytest.mark.parametrize( |
| 2467 | + "asset", |
| 2468 | + (NASA_VIDEO, NASA_VIDEO_HDR, TEST_SRC_2_720P_HDR, TEST_SRC_2_12BIT_HDR), |
| 2469 | + ) |
| 2470 | + def test_output_dtype_float32_pts_apis(self, asset): |
| 2471 | + decoder = VideoDecoder(asset.path, output_dtype=torch.float32) |
| 2472 | + indices = [0, 5, 10] |
| 2473 | + |
| 2474 | + pts_seconds_ref = [decoder.get_frame_at(i).pts_seconds for i in indices] |
| 2475 | + |
| 2476 | + # get_frame_played_at |
| 2477 | + for pts, idx in zip(pts_seconds_ref, indices): |
| 2478 | + frame = decoder.get_frame_played_at(pts) |
| 2479 | + self._assert_float32_frame_matches_rgb48_ref(frame.data, asset, idx) |
| 2480 | + |
| 2481 | + # get_frames_played_in_range (full range) |
| 2482 | + frames = decoder.get_frames_played_in_range( |
| 2483 | + start_seconds=0, |
| 2484 | + stop_seconds=pts_seconds_ref[-1] + 1e-4, |
| 2485 | + ) |
| 2486 | + for idx in indices: |
| 2487 | + self._assert_float32_frame_matches_rgb48_ref(frames.data[idx], asset, idx) |
| 2488 | + |
| 2489 | + # get_frames_played_in_range (single-frame ranges) |
| 2490 | + for pts, idx in zip(pts_seconds_ref, indices): |
| 2491 | + frames = decoder.get_frames_played_in_range( |
| 2492 | + start_seconds=pts, stop_seconds=pts + 1e-4 |
| 2493 | + ) |
| 2494 | + self._assert_float32_frame_matches_rgb48_ref(frames.data[0], asset, idx) |
| 2495 | + |
| 2496 | + # get_frames_played_at |
| 2497 | + frames = decoder.get_frames_played_at(pts_seconds_ref) |
| 2498 | + for i, idx in enumerate(indices): |
| 2499 | + self._assert_float32_frame_matches_rgb48_ref(frames.data[i], asset, idx) |
| 2500 | + |
| 2501 | + @pytest.mark.parametrize("bad_dtype", (torch.float64, torch.int32, "not_a_dtype")) |
| 2502 | + def test_output_dtype_invalid(self, bad_dtype): |
| 2503 | + with pytest.raises(ValueError, match="Invalid output_dtype"): |
| 2504 | + VideoDecoder(NASA_VIDEO.path, output_dtype=bad_dtype) |
| 2505 | + |
2363 | 2506 |
|
2364 | 2507 | class TestAudioDecoder: |
2365 | 2508 | @pytest.mark.parametrize( |
|
0 commit comments