Skip to content

Commit 65241a9

Browse files
authored
Finish porting tests for MultiStreamEncoder (#1403)
1 parent 8946648 commit 65241a9

1 file changed

Lines changed: 271 additions & 0 deletions

File tree

test/test_encoders.py

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2761,6 +2761,277 @@ def test_add_audio_output_sample_rate(self, tmp_path, method):
27612761
decoded_2 = AudioDecoder(source, stream_index=2).get_all_samples()
27622762
assert decoded_2.sample_rate == 44_100
27632763

2764+
@pytest.mark.parametrize("method", ("to_file", "to_file_like"))
2765+
def test_pixel_format_errors(self, method, tmp_path):
2766+
enc, _, open_kwargs = self._create_encoder(method, tmp_path, "mp4")
2767+
enc.add_video(
2768+
height=64,
2769+
width=64,
2770+
frame_rate=30.0,
2771+
pixel_format="invalid_pix_fmt",
2772+
)
2773+
with pytest.raises(
2774+
RuntimeError,
2775+
match=r"Unknown pixel format: invalid_pix_fmt[\s\S]*Supported pixel formats.*yuv420p",
2776+
):
2777+
enc.open(**open_kwargs)
2778+
2779+
enc2, _, open_kwargs2 = self._create_encoder(method, tmp_path, "mp4")
2780+
enc2.add_video(
2781+
height=64,
2782+
width=64,
2783+
frame_rate=30.0,
2784+
pixel_format="rgb24",
2785+
)
2786+
with pytest.raises(
2787+
RuntimeError,
2788+
match=r"Specified pixel format rgb24 is not supported[\s\S]*Supported pixel formats.*yuv420p",
2789+
):
2790+
enc2.open(**open_kwargs2)
2791+
2792+
@pytest.mark.needs_cuda
2793+
@pytest.mark.skipif(in_fbcode(), reason="NVENC not available in fbcode")
2794+
@pytest.mark.parametrize("method", ("to_file", "to_file_like"))
2795+
def test_pixel_format_gpu_override_errors(self, method, tmp_path):
2796+
enc, _, open_kwargs = self._create_encoder(method, tmp_path, "mp4")
2797+
enc.add_video(
2798+
height=64,
2799+
width=64,
2800+
frame_rate=30.0,
2801+
device="cuda",
2802+
pixel_format="yuv444p",
2803+
)
2804+
with pytest.raises(
2805+
RuntimeError,
2806+
match="Video encoding on GPU currently only supports the nv12 pixel format",
2807+
):
2808+
enc.open(**open_kwargs)
2809+
2810+
@pytest.mark.parametrize(
2811+
"extra_options,error",
2812+
[
2813+
({"qp": -10}, "qp=-10 is out of valid range"),
2814+
({"qp": ""}, "Option qp expects a numeric value but got"),
2815+
(
2816+
{"direct-pred": "a"},
2817+
"Option direct-pred expects a numeric value but got 'a'",
2818+
),
2819+
({"tune": "not_a_real_tune"}, "avcodec_open2 failed: Invalid argument"),
2820+
],
2821+
)
2822+
@pytest.mark.parametrize("method", ("to_file", "to_file_like"))
2823+
def test_extra_options_errors(self, method, tmp_path, extra_options, error):
2824+
enc, _, open_kwargs = self._create_encoder(method, tmp_path, "mp4")
2825+
enc.add_video(
2826+
height=64,
2827+
width=64,
2828+
frame_rate=30.0,
2829+
extra_options=extra_options,
2830+
)
2831+
with pytest.raises(RuntimeError, match=error):
2832+
enc.open(**open_kwargs)
2833+
2834+
@pytest.mark.parametrize("method", ("to_file", "to_file_like"))
2835+
def test_write_frames_wrong_dtype_errors(self, method, tmp_path):
2836+
enc, _, open_kwargs = self._create_encoder(method, tmp_path, "mp4")
2837+
video = enc.add_video(height=64, width=64, frame_rate=30.0)
2838+
enc.open(**open_kwargs)
2839+
float_frames = torch.rand(2, 3, 64, 64, dtype=torch.float32)
2840+
with pytest.raises(RuntimeError, match="must have uint8 dtype"):
2841+
video.write(float_frames)
2842+
2843+
@pytest.mark.parametrize("method", ("to_file", "to_file_like"))
2844+
def test_write_frames_wrong_ndim_errors(self, method, tmp_path):
2845+
enc, _, open_kwargs = self._create_encoder(method, tmp_path, "mp4")
2846+
video = enc.add_video(height=64, width=64, frame_rate=30.0)
2847+
enc.open(**open_kwargs)
2848+
frames_1d = torch.randint(0, 256, (100,), dtype=torch.uint8)
2849+
with pytest.raises(RuntimeError):
2850+
video.write(frames_1d)
2851+
2852+
@pytest.mark.parametrize("method", ("to_file", "to_file_like"))
2853+
def test_write_frames_wrong_num_channels_errors(self, method, tmp_path):
2854+
enc, _, open_kwargs = self._create_encoder(method, tmp_path, "mp4")
2855+
video = enc.add_video(height=64, width=64, frame_rate=30.0)
2856+
enc.open(**open_kwargs)
2857+
frames_2ch = torch.randint(0, 256, (2, 2, 64, 64), dtype=torch.uint8)
2858+
with pytest.raises(RuntimeError):
2859+
video.write(frames_2ch)
2860+
2861+
@pytest.mark.parametrize("method", ("to_file", "to_file_like"))
2862+
def test_add_audio_invalid_sample_rate_errors(self, method, tmp_path):
2863+
enc, _, open_kwargs = self._create_encoder(method, tmp_path, "wav")
2864+
with pytest.raises(RuntimeError, match="sample_rate must be > 0"):
2865+
enc.add_audio(sample_rate=0, num_channels=1)
2866+
2867+
enc2, _, open_kwargs2 = self._create_encoder(method, tmp_path, "wav")
2868+
with pytest.raises(RuntimeError, match="sample_rate must be > 0"):
2869+
enc2.add_audio(sample_rate=-1, num_channels=1)
2870+
2871+
@pytest.mark.parametrize("method", ("to_file", "to_file_like"))
2872+
def test_add_audio_invalid_num_channels_errors(self, method, tmp_path):
2873+
enc, _, open_kwargs = self._create_encoder(method, tmp_path, "wav")
2874+
with pytest.raises(RuntimeError, match="num_channels must be > 0"):
2875+
enc.add_audio(sample_rate=44100, num_channels=0)
2876+
2877+
def test_to_file_like_custom_file_object(self, tmp_path):
2878+
class CustomFileObject:
2879+
def __init__(self):
2880+
self._file = io.BytesIO()
2881+
2882+
def write(self, data):
2883+
return self._file.write(data)
2884+
2885+
def seek(self, offset, whence=0):
2886+
return self._file.seek(offset, whence)
2887+
2888+
def get_encoded_data(self):
2889+
return self._file.getvalue()
2890+
2891+
source_decoder = VideoDecoder(str(TEST_SRC_2_720P.path))
2892+
source_frames = source_decoder.get_frames_in_range(start=0, stop=10).data
2893+
frame_rate = source_decoder.metadata.average_fps
2894+
2895+
enc = StreamingEncoder()
2896+
file_like = CustomFileObject()
2897+
video = enc.add_video(
2898+
height=source_frames.shape[2],
2899+
width=source_frames.shape[3],
2900+
frame_rate=frame_rate,
2901+
pixel_format="yuv444p",
2902+
crf=0,
2903+
)
2904+
enc.open(file_like, format="mp4")
2905+
video.write(source_frames)
2906+
enc.close()
2907+
2908+
decoded_frames = (
2909+
VideoDecoder(file_like.get_encoded_data())
2910+
.get_frames_in_range(start=0, stop=10)
2911+
.data
2912+
)
2913+
assert_tensor_close_on_at_least(
2914+
decoded_frames, source_frames, percentage=99, atol=2
2915+
)
2916+
2917+
def test_to_file_like_custom_file_object_audio(self, tmp_path):
2918+
class CustomFileObject:
2919+
def __init__(self):
2920+
self._file = io.BytesIO()
2921+
2922+
def write(self, data):
2923+
return self._file.write(data)
2924+
2925+
def seek(self, offset, whence=0):
2926+
return self._file.seek(offset, whence)
2927+
2928+
def get_encoded_data(self):
2929+
return self._file.getvalue()
2930+
2931+
asset = NASA_AUDIO_MP3
2932+
source_samples = AudioDecoder(str(asset.path)).get_all_samples().data
2933+
sample_rate = asset.sample_rate
2934+
num_channels = source_samples.shape[0]
2935+
2936+
enc = StreamingEncoder()
2937+
file_like = CustomFileObject()
2938+
audio = enc.add_audio(sample_rate=sample_rate, num_channels=num_channels)
2939+
enc.open(file_like, format="flac")
2940+
audio.write(source_samples)
2941+
enc.close()
2942+
2943+
decoded = AudioDecoder(file_like.get_encoded_data()).get_all_samples()
2944+
torch.testing.assert_close(decoded.data, source_samples, rtol=0, atol=1e-4)
2945+
2946+
def test_to_file_like_real_file_video(self, tmp_path):
2947+
source_decoder = VideoDecoder(str(TEST_SRC_2_720P.path))
2948+
source_frames = source_decoder.get_frames_in_range(start=0, stop=10).data
2949+
frame_rate = source_decoder.metadata.average_fps
2950+
2951+
file_path = tmp_path / "test_real_file.mp4"
2952+
enc = StreamingEncoder()
2953+
video = enc.add_video(
2954+
height=source_frames.shape[2],
2955+
width=source_frames.shape[3],
2956+
frame_rate=frame_rate,
2957+
pixel_format="yuv444p",
2958+
crf=0,
2959+
)
2960+
with open(file_path, "wb") as f:
2961+
enc.open(f, format="mp4")
2962+
video.write(source_frames)
2963+
enc.close()
2964+
2965+
decoded_frames = (
2966+
VideoDecoder(str(file_path)).get_frames_in_range(start=0, stop=10).data
2967+
)
2968+
assert_tensor_close_on_at_least(
2969+
decoded_frames, source_frames, percentage=99, atol=2
2970+
)
2971+
2972+
def test_to_file_like_real_file_audio(self, tmp_path):
2973+
asset = NASA_AUDIO_MP3
2974+
source_samples = AudioDecoder(str(asset.path)).get_all_samples().data
2975+
sample_rate = asset.sample_rate
2976+
num_channels = source_samples.shape[0]
2977+
2978+
file_path = tmp_path / "test_real_file.flac"
2979+
enc = StreamingEncoder()
2980+
audio = enc.add_audio(sample_rate=sample_rate, num_channels=num_channels)
2981+
with open(file_path, "wb") as f:
2982+
enc.open(f, format="flac")
2983+
audio.write(source_samples)
2984+
enc.close()
2985+
2986+
decoded = AudioDecoder(str(file_path)).get_all_samples()
2987+
torch.testing.assert_close(decoded.data, source_samples, rtol=0, atol=1e-4)
2988+
2989+
def test_to_file_like_bad_methods_video(self):
2990+
class NoWriteMethod:
2991+
def seek(self, offset, whence=0):
2992+
return 0
2993+
2994+
enc = StreamingEncoder()
2995+
enc.add_video(height=64, width=64, frame_rate=30.0)
2996+
with pytest.raises(
2997+
RuntimeError, match="File like object must implement a write method"
2998+
):
2999+
enc.open(NoWriteMethod(), format="mp4")
3000+
3001+
class NoSeekMethod:
3002+
def write(self, data):
3003+
return len(data)
3004+
3005+
enc2 = StreamingEncoder()
3006+
enc2.add_video(height=64, width=64, frame_rate=30.0)
3007+
with pytest.raises(
3008+
RuntimeError, match="File like object must implement a seek method"
3009+
):
3010+
enc2.open(NoSeekMethod(), format="mp4")
3011+
3012+
def test_to_file_like_bad_methods_audio(self):
3013+
class NoWriteMethod:
3014+
def seek(self, offset, whence=0):
3015+
return 0
3016+
3017+
enc = StreamingEncoder()
3018+
enc.add_audio(sample_rate=44100, num_channels=1)
3019+
with pytest.raises(
3020+
RuntimeError, match="File like object must implement a write method"
3021+
):
3022+
enc.open(NoWriteMethod(), format="wav")
3023+
3024+
class NoSeekMethod:
3025+
def write(self, data):
3026+
return len(data)
3027+
3028+
enc2 = StreamingEncoder()
3029+
enc2.add_audio(sample_rate=44100, num_channels=1)
3030+
with pytest.raises(
3031+
RuntimeError, match="File like object must implement a seek method"
3032+
):
3033+
enc2.open(NoSeekMethod(), format="wav")
3034+
27643035
@needs_ffmpeg_cli
27653036
@pytest.mark.parametrize("method", ("to_file", "to_file_like"))
27663037
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)