-
Notifications
You must be signed in to change notification settings - Fork 64
Add custom_frame_mappings to VideoDecoder init #799
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f802b38
e4226ad
3e26ca7
17e7fc8
4cd6fd8
ae369e3
4857628
b00ba7a
be9466b
40d120d
c985db3
90241a0
0b0037c
a88eab9
3046797
bbae18a
2a5f622
7acca92
ea442e5
81806dd
c634909
263637c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
import contextlib | ||
import gc | ||
import json | ||
from functools import partial | ||
from unittest.mock import patch | ||
|
||
import numpy | ||
|
@@ -1279,6 +1280,112 @@ def test_10bit_videos_cpu(self, asset): | |
decoder = VideoDecoder(asset.path) | ||
decoder.get_frame_at(10) | ||
|
||
def setup_frame_mappings(tmp_path, file, stream_index): | ||
json_path = tmp_path / "custom_frame_mappings.json" | ||
custom_frame_mappings = NASA_VIDEO.generate_custom_frame_mappings(stream_index) | ||
if file: | ||
# Write the custom frame mappings to a JSON file | ||
with open(json_path, "w") as f: | ||
f.write(custom_frame_mappings) | ||
return json_path | ||
else: | ||
# Return the custom frame mappings as a JSON string | ||
return custom_frame_mappings | ||
|
||
@pytest.mark.parametrize("device", all_supported_devices()) | ||
@pytest.mark.parametrize("stream_index", [0, 3]) | ||
@pytest.mark.parametrize( | ||
"method", | ||
( | ||
partial(setup_frame_mappings, file=True), | ||
partial(setup_frame_mappings, file=False), | ||
), | ||
) | ||
def test_custom_frame_mappings_json_and_bytes( | ||
self, tmp_path, device, stream_index, method | ||
): | ||
custom_frame_mappings = method(tmp_path=tmp_path, stream_index=stream_index) | ||
# Optionally open the custom frame mappings file if it is a file path | ||
# or use a null context if it is a string. | ||
with ( | ||
open(custom_frame_mappings, "r") | ||
if hasattr(custom_frame_mappings, "read") | ||
else contextlib.nullcontext() | ||
) as custom_frame_mappings: | ||
decoder = VideoDecoder( | ||
NASA_VIDEO.path, | ||
stream_index=stream_index, | ||
device=device, | ||
custom_frame_mappings=custom_frame_mappings, | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: everything below can probably be out of the context manager. It's preferable to end the CM's scope as soon as it's not needed. |
||
frame_0 = decoder.get_frame_at(0) | ||
frame_5 = decoder.get_frame_at(5) | ||
assert_frames_equal( | ||
frame_0.data, | ||
NASA_VIDEO.get_frame_data_by_index(0, stream_index=stream_index).to(device), | ||
) | ||
assert_frames_equal( | ||
frame_5.data, | ||
NASA_VIDEO.get_frame_data_by_index(5, stream_index=stream_index).to(device), | ||
) | ||
frames0_5 = decoder.get_frames_played_in_range( | ||
frame_0.pts_seconds, frame_5.pts_seconds | ||
) | ||
assert_frames_equal( | ||
frames0_5.data, | ||
NASA_VIDEO.get_frame_data_by_range(0, 5, stream_index=stream_index).to( | ||
device | ||
), | ||
) | ||
|
||
@pytest.mark.parametrize("device", all_supported_devices()) | ||
@pytest.mark.parametrize( | ||
"custom_frame_mappings,expected_match", | ||
[ | ||
(NASA_VIDEO.generate_custom_frame_mappings(0), "seek_mode"), | ||
("{}", "The input is empty or missing the required 'frames' key."), | ||
( | ||
'{"valid": "json"}', | ||
"The input is empty or missing the required 'frames' key.", | ||
), | ||
( | ||
'{"frames": [{"missing": "keys"}]}', | ||
"keys are required in the frame metadata.", | ||
), | ||
], | ||
) | ||
def test_custom_frame_mappings_init_fails( | ||
self, device, custom_frame_mappings, expected_match | ||
): | ||
with pytest.raises(ValueError, match=expected_match): | ||
VideoDecoder( | ||
NASA_VIDEO.path, | ||
stream_index=0, | ||
device=device, | ||
custom_frame_mappings=custom_frame_mappings, | ||
seek_mode=("approximate" if expected_match == "seek_mode" else "exact"), | ||
) | ||
|
||
@pytest.mark.parametrize("device", all_supported_devices()) | ||
def test_custom_frame_mappings_init_fails_invalid_json(self, tmp_path, device): | ||
invalid_json_path = tmp_path / "invalid_json" | ||
with open(invalid_json_path, "w+") as f: | ||
f.write("invalid input") | ||
|
||
# Test both file object and string | ||
with open(invalid_json_path, "r") as file_obj: | ||
for custom_frame_mappings in [ | ||
file_obj, | ||
file_obj.read(), | ||
]: | ||
with pytest.raises(ValueError, match="Invalid custom frame mappings"): | ||
VideoDecoder( | ||
NASA_VIDEO.path, | ||
stream_index=0, | ||
device=device, | ||
custom_frame_mappings=custom_frame_mappings, | ||
) | ||
|
||
|
||
class TestAudioDecoder: | ||
@pytest.mark.parametrize("asset", (NASA_AUDIO, NASA_AUDIO_MP3, SINE_MONO_S32)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,7 +44,6 @@ | |
from .utils import ( | ||
all_supported_devices, | ||
assert_frames_equal, | ||
get_ffmpeg_major_version, | ||
NASA_AUDIO, | ||
NASA_AUDIO_MP3, | ||
NASA_VIDEO, | ||
|
@@ -485,7 +484,7 @@ def test_seek_mode_custom_frame_mappings_fails(self): | |
) | ||
with pytest.raises( | ||
RuntimeError, | ||
match="Please provide frame mappings when using custom_frame_mappings seek mode.", | ||
match="Missing frame mappings when custom_frame_mappings seek mode is set.", | ||
): | ||
add_video_stream(decoder, stream_index=0, custom_frame_mappings=None) | ||
|
||
|
@@ -505,10 +504,6 @@ def test_seek_mode_custom_frame_mappings_fails(self): | |
decoder, stream_index=0, custom_frame_mappings=different_lengths | ||
) | ||
|
||
@pytest.mark.skipif( | ||
get_ffmpeg_major_version() in (4, 5), | ||
reason="ffprobe isn't accurate on ffmpeg 4 and 5", | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ffprobe does include the fields |
||
@pytest.mark.parametrize("device", all_supported_devices()) | ||
def test_seek_mode_custom_frame_mappings(self, device): | ||
stream_index = 3 # custom_frame_index seek mode requires a stream index | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also say something about the relationship with
seek_mode
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can also add that in order to accomodate for different FFmpeg versions we also allow
pkt_pts
andpkt_duration
?