"""Unit tests for scene change detection.""" import numpy as np from fansifter_clipper.reframing import Reframer class TestSceneDetection: """Test suite for scene change detection.""" def test_scene_change_different_frames(self): """Test detection between completely different frames.""" reframer = Reframer() # Create black and white frames (extreme difference) frame1 = np.zeros((720, 1280, 3), dtype=np.uint8) # Black frame2 = np.ones((720, 1280, 3), dtype=np.uint8) * 255 # White is_change, confidence, reason = reframer._detect_scene_change( frame2, frame1, 0.9, 0.9 ) assert is_change is True assert confidence > 0.4 # Chi-square distance normalized to ~0.5 for extreme differences assert reason in ["histogram", "pixel_difference"] def test_no_scene_change_similar_frames(self): """Test no false positive on similar frames.""" reframer = Reframer() # Create similar frames with slight variation frame1 = np.random.randint(100, 150, (720, 1280, 3), dtype=np.uint8) frame2 = np.roll(frame1, 10, axis=1) # Camera motion simulation is_change, confidence, reason = reframer._detect_scene_change( frame2, frame1, 0.85, 0.87 ) assert is_change is False def test_scene_change_color_shift(self): """Test detection on major color changes.""" reframer = Reframer() # Create frames with different dominant colors frame1 = np.zeros((720, 1280, 3), dtype=np.uint8) frame1[:, :, 2] = 255 # Red frame frame2 = np.zeros((720, 1280, 3), dtype=np.uint8) frame2[:, :, 0] = 255 # Blue frame is_change, confidence, reason = reframer._detect_scene_change( frame2, frame1, 0.8, 0.8 ) assert is_change is True assert reason in ["histogram", "pixel_difference"] def test_no_scene_change_lighting_variation(self): """Test that gradual lighting changes don't trigger false positives.""" reframer = Reframer() # Create realistic scene with gradient frame1 = np.zeros((720, 1280, 3), dtype=np.uint8) y_gradient = np.linspace(50, 200, 720, dtype=np.uint8) frame1[:, :, :] = y_gradient[:, np.newaxis, np.newaxis] # Apply very slight brightening across entire frame frame2 = np.clip(frame1.astype(int) + 5, 0, 255).astype(np.uint8) is_change, confidence, reason = reframer._detect_scene_change( frame2, frame1, 0.85, 0.83 ) # Slight lighting change should not trigger scene detection assert is_change is False def test_scene_change_confidence_drop(self): """Test detection via significant confidence drop.""" reframer = Reframer() # Create moderately different frames frame1 = np.random.randint(80, 120, (720, 1280, 3), dtype=np.uint8) frame2 = np.random.randint(140, 180, (720, 1280, 3), dtype=np.uint8) # Significant confidence drop should help trigger detection is_change, confidence, reason = reframer._detect_scene_change( frame2, frame1, 0.3, 0.9 # Large confidence drop ) # Should detect scene change due to combined signals assert is_change is True class TestSceneSegmentation: """Test suite for scene segmentation.""" def test_segment_by_scene_changes(self): """Test keyframe segmentation by scene boundaries.""" from fansifter_clipper.models import CropKeyframe reframer = Reframer() # Create keyframes with scene changes keyframes = [ CropKeyframe(time=0.0, x=100, y=50, width=720, height=1280, scene_change=False), CropKeyframe(time=1.0, x=110, y=55, width=720, height=1280, scene_change=False), CropKeyframe(time=2.0, x=120, y=60, width=720, height=1280, scene_change=False), CropKeyframe(time=3.0, x=800, y=200, width=720, height=1280, scene_change=True), CropKeyframe(time=4.0, x=810, y=205, width=720, height=1280, scene_change=False), CropKeyframe(time=5.0, x=300, y=400, width=720, height=1280, scene_change=True), CropKeyframe(time=6.0, x=310, y=405, width=720, height=1280, scene_change=False), ] segments = reframer._segment_by_scene_changes(keyframes) # Should have 3 segments assert len(segments) == 3 assert len(segments[0]) == 3 # First scene (0-2s) assert len(segments[1]) == 2 # Second scene (3-4s) assert len(segments[2]) == 2 # Third scene (5-6s) def test_segment_no_scene_changes(self): """Test segmentation with no scene changes.""" from fansifter_clipper.models import CropKeyframe reframer = Reframer() # Create keyframes without scene changes keyframes = [ CropKeyframe(time=0.0, x=100, y=50, width=720, height=1280, scene_change=False), CropKeyframe(time=1.0, x=110, y=55, width=720, height=1280, scene_change=False), CropKeyframe(time=2.0, x=120, y=60, width=720, height=1280, scene_change=False), ] segments = reframer._segment_by_scene_changes(keyframes) # Should have 1 segment (entire sequence) assert len(segments) == 1 assert len(segments[0]) == 3 def test_segment_empty_keyframes(self): """Test segmentation with empty keyframe list.""" reframer = Reframer() segments = reframer._segment_by_scene_changes([]) assert len(segments) == 0