"""Integration tests for scene detection with real videos.""" from pathlib import Path import pytest from fansifter_clipper.models import VideoSegment from fansifter_clipper.reframing import Reframer class TestSceneIntegration: """Integration tests for scene detection.""" @pytest.mark.skipif( not Path("harry.mp4").exists(), reason="Test video harry.mp4 not available" ) def test_scene_detection_on_harry_video(self): """Test scene detection on Harry Styles video.""" reframer = Reframer(debug=False) video_path = Path("harry.mp4") # Test segment segment = VideoSegment( start=10.0, end=25.0, score=0.9, description="Test segment with scene changes" ) crop_path = reframer.generate_crop_path(video_path, segment) # Check that scene changes were detected scene_changes = [kf for kf in crop_path.keyframes if kf.scene_change] # For 15-second music video, expect 2-15 scene changes # (Music videos can have rapid cuts, especially in energetic sections) assert len(scene_changes) >= 2, "Should detect at least 2 scene changes" assert len(scene_changes) <= 15, "Should not over-detect scene changes" # Verify that scene changes are properly spaced if len(scene_changes) >= 2: times = [kf.time for kf in scene_changes] intervals = [times[i + 1] - times[i] for i in range(len(times) - 1)] # Scene changes should be at least 0.5s apart (debounce interval) assert all( interval >= 0.5 for interval in intervals ), "Scene changes should respect debounce interval" @pytest.mark.skipif( not Path("flowers.mp4").exists(), reason="Test video flowers.mp4 not available" ) def test_scene_detection_on_flowers_video(self): """Test scene detection on flowers video (likely more static).""" reframer = Reframer(debug=False) video_path = Path("flowers.mp4") # Test segment segment = VideoSegment( start=5.0, end=20.0, score=0.85, description="Test segment" ) crop_path = reframer.generate_crop_path(video_path, segment) # Verify keyframes have scene_change field assert all( hasattr(kf, "scene_change") for kf in crop_path.keyframes ), "All keyframes should have scene_change field" def test_smoothing_respects_scene_boundaries(self): """Test that smoothing is applied within scenes, not across boundaries.""" from fansifter_clipper.config import settings from fansifter_clipper.models import CropKeyframe # Temporarily enable scene detection original_setting = settings.enable_scene_detection settings.enable_scene_detection = True try: reframer = Reframer() # Create keyframes with a scene change in the middle keyframes = [ CropKeyframe( time=0.0, x=100, y=100, width=720, height=1280, scene_change=False ), CropKeyframe( time=1.0, x=110, y=110, width=720, height=1280, scene_change=False ), CropKeyframe( time=2.0, x=120, y=120, width=720, height=1280, scene_change=False ), # Scene change - position jumps CropKeyframe( time=3.0, x=800, y=600, width=720, height=1280, scene_change=True ), CropKeyframe( time=4.0, x=810, y=610, width=720, height=1280, scene_change=False ), CropKeyframe( time=5.0, x=820, y=620, width=720, height=1280, scene_change=False ), ] smoothed = reframer._smooth_keyframes(keyframes) # Verify we got smoothed keyframes assert len(smoothed) > 0 # Find keyframes near the scene boundary scene_change_kf = next(kf for kf in smoothed if kf.scene_change) # The discontinuity at the scene change should be preserved # (not smoothed away to intermediate values) # Verify by checking that x-coordinates remain in their respective ranges before_scene = [kf for kf in smoothed if kf.time < scene_change_kf.time] after_scene = [kf for kf in smoothed if kf.time >= scene_change_kf.time] if before_scene and after_scene: # Before scene should have x < 200 assert all( kf.x < 200 for kf in before_scene ), "Before scene should stay in first position range" # After scene should have x > 700 assert all( kf.x > 700 for kf in after_scene ), "After scene should stay in second position range" finally: # Restore original setting settings.enable_scene_detection = original_setting def test_scene_detection_disabled(self): """Test that smoothing works normally when scene detection is disabled.""" from fansifter_clipper.config import settings from fansifter_clipper.models import CropKeyframe # Temporarily disable scene detection original_setting = settings.enable_scene_detection settings.enable_scene_detection = False try: reframer = Reframer() keyframes = [ CropKeyframe( time=0.0, x=100, y=100, width=720, height=1280, scene_change=False ), CropKeyframe( time=1.0, x=110, y=110, width=720, height=1280, scene_change=False ), CropKeyframe( time=2.0, x=800, y=600, width=720, height=1280, scene_change=True ), CropKeyframe( time=3.0, x=810, y=610, width=720, height=1280, scene_change=False ), ] smoothed = reframer._smooth_keyframes(keyframes) # When scene detection is disabled, smoothing should blend across all keyframes # This means intermediate keyframes should have x-values between 110 and 800 assert len(smoothed) > 0 finally: # Restore original setting settings.enable_scene_detection = original_setting