Add new functionality for image rectification and depth metadata handling

- Implemented a preview tool for rectifying images with interactive aspect controls in `preview.py`.
- Added tests for depth metadata loading to ensure consistent schema usage in `test_depth_metadata.py`.
- Developed comprehensive tests for cutout calibration processes in `test_cutout_calibration.py`, including stability checks and depth calibration.
- Created tests for frame compositing operations in `test_frame_compositing.py`, ensuring correct handling of depth and transparency.
- Introduced material slot inheritance functionality in `material_slots.py` to manage geometry material assignments.
- Built shared shader nodes for material processing in `shader.py`, including roughness and bump mapping setups.
This commit is contained in:
2026-09-12 19:20:06 +08:00
parent e8268504c2
commit 938d95db0f
175 changed files with 3812 additions and 6864 deletions

View File

@@ -1,5 +1,3 @@
from types import SimpleNamespace
from unittest.mock import Mock
import pytest
from PIL import Image
@@ -12,54 +10,19 @@ def media():
return load_server_module("media.input")
@pytest.mark.parametrize("suffix", [".PNG", ".MP4", ".webp", ".mts"])
@pytest.mark.parametrize("suffix", [".PNG", ".JPG", ".webp", ".jpeg"])
def test_supported_format_is_case_insensitive(media, tmp_path, suffix):
path = tmp_path / f"input{suffix}"
path.touch()
assert media.validate_input_path(path) == path
@pytest.mark.parametrize("suffix", [".tiff", ""])
def test_frontend_and_backend_share_format_reason(media, tmp_path, suffix):
path = tmp_path / f"input{suffix}"
path.touch()
with pytest.raises(RuntimeError) as preflight:
media.validate_input_path(path)
with pytest.raises(RuntimeError) as backend:
media.prepare_input(path, 16, tmp_path)
assert str(preflight.value) == str(backend.value)
assert f"Unsupported input format: {suffix or '(no extension)'}" in str(backend.value)
assert "Supported images: .jpeg, .jpg, .png, .webp." in str(backend.value)
assert "Supported videos:" in str(backend.value)
@pytest.mark.parametrize("suffix,kind", [(".png", "image"), (".mp4", "video")])
@pytest.mark.parametrize("suffix,kind", [(".png", "image"), (".mp4", "image")])
def test_missing_path_precedes_format_validation(media, tmp_path, suffix, kind):
with pytest.raises(RuntimeError, match=f"Source {kind} file does not exist"):
media.validate_input_path(tmp_path / f"missing{suffix}")
def test_directory_filtering_and_empty_reason(media, tmp_path):
(tmp_path / "note.txt").touch()
(tmp_path / "frame.exr").touch()
with pytest.raises(RuntimeError, match="No supported image frames found"):
media.validate_input_path(tmp_path)
for name in ["b.PNG", "a.jpg"]:
(tmp_path / name).touch()
frames, temporary = media.prepare_input(tmp_path, 1, tmp_path, start_frame=2)
assert frames == [tmp_path / "b.PNG"]
assert temporary is None
def test_image_subset_rejects_video(media, tmp_path):
path = tmp_path / "clip.mp4"
path.touch()
with pytest.raises(RuntimeError) as error:
media.validate_input_path(path, images_only=True)
assert "Supported images:" in str(error.value)
assert "Supported videos:" not in str(error.value)
def test_image_decode_error_keeps_cause(media, tmp_path):
path = tmp_path / "broken.png"
path.write_bytes(b"broken")
@@ -79,15 +42,33 @@ def test_processing_error_is_not_relabelled(media, tmp_path):
assert error.value is failure
def test_video_decode_error_closes_container(media, tmp_path):
failure = ValueError("broken packet")
def frames():
raise failure
yield
container = SimpleNamespace(streams=SimpleNamespace(video=[object()]),
decode=lambda _stream: frames(), close=Mock())
with pytest.raises(RuntimeError, match="Unable to read video") as error:
media.extract_video_frames("broken.mp4", tmp_path, 2,
SimpleNamespace(open=lambda _path: container))
assert error.value.__cause__ is failure
container.close.assert_called_once()
@pytest.mark.parametrize("suffix", [".mp4", ".mts", ".gif", ".tiff", ""])
def test_unsupported_formats_are_rejected(media, tmp_path, suffix):
path = tmp_path / f"input{suffix}"
path.touch()
with pytest.raises(RuntimeError, match="Unsupported input format"):
media.validate_input_path(path)
def test_frame_directory_is_rejected(media, tmp_path):
Image.new("RGB", (2, 2)).save(tmp_path / "frame.png")
with pytest.raises(RuntimeError, match="Source image file does not exist"):
media.validate_input_path(tmp_path)
def test_animated_png_is_rejected(media, tmp_path):
path = tmp_path / "animated.png"
Image.new("RGB", (2, 2), "red").save(
path, save_all=True, append_images=[Image.new("RGB", (2, 2), "blue")], duration=100,
)
with pytest.raises(RuntimeError, match="Only still images"):
with media.open_image(path):
pytest.fail("Animated images must not reach processing")
def test_ai_tasks_support_cooperative_cancellation(media):
with pytest.raises(media.TaskCancelled):
media.check_cancelled(lambda: True)
media.check_cancelled(lambda: False)