"""拼接引擎单元测试 - 配置解析等纯逻辑.""" from __future__ import annotations import pytest from video_processing.concat_engine import ConcatConfig, ConcatSegment class TestConcatSegmentDefaults: """ConcatSegment 默认值测试.""" def test_default_values(self): """默认值正确.""" seg = ConcatSegment(video_path="/a.mp4") assert seg.video_path == "/a.mp4" assert seg.start_time == 0.0 assert seg.duration == 0.0 assert seg.has_audio is True class TestConcatSegmentFromDict: """ConcatSegment.from_dict 测试.""" def test_basic_path(self): """基本路径.""" seg = ConcatSegment.from_dict({"video_path": "/a.mp4"}) assert seg.video_path == "/a.mp4" assert seg.start_time == 0.0 assert seg.duration == 0.0 def test_custom_start_time(self): """自定义开始时间.""" seg = ConcatSegment.from_dict( { "video_path": "/a.mp4", "start_time": 5.0, } ) assert seg.start_time == 5.0 def test_custom_duration(self): """自定义时长.""" seg = ConcatSegment.from_dict( { "video_path": "/a.mp4", "duration": 10.0, } ) assert seg.duration == 10.0 def test_start_time_negative_clamped(self): """负开始时间钳制到0.""" seg = ConcatSegment.from_dict( { "video_path": "/a.mp4", "start_time": -5.0, } ) assert seg.start_time == 0.0 def test_duration_negative_clamped(self): """负时长钳制到0.""" seg = ConcatSegment.from_dict( { "video_path": "/a.mp4", "duration": -3.0, } ) assert seg.duration == 0.0 def test_invalid_start_time_falls_back(self): """无效start_time回退到0.""" seg = ConcatSegment.from_dict( { "video_path": "/a.mp4", "start_time": "invalid", } ) assert seg.start_time == 0.0 def test_invalid_duration_falls_back(self): """无效duration回退到0.""" seg = ConcatSegment.from_dict( { "video_path": "/a.mp4", "duration": "not_a_number", } ) assert seg.duration == 0.0 def test_no_audio(self): """无音频.""" seg = ConcatSegment.from_dict( { "video_path": "/a.mp4", "has_audio": False, } ) assert seg.has_audio is False def test_full_config(self): """完整配置.""" seg = ConcatSegment.from_dict( { "video_path": "/video.mp4", "start_time": 2.5, "duration": 15.0, "has_audio": False, } ) assert seg.video_path == "/video.mp4" assert seg.start_time == 2.5 assert seg.duration == 15.0 assert seg.has_audio is False class TestConcatConfigDefaults: """ConcatConfig 默认值测试.""" def test_default_values(self): """默认值正确.""" config = ConcatConfig() assert config.segments == [] assert config.output_width == 0 assert config.output_height == 0 assert config.output_fps == 0.0 assert config.force_reencode is False assert config.transition == "none" assert config.transition_duration == 0.3 class TestConcatConfigFromConfigDict: """ConcatConfig.from_config_dict 测试.""" def test_none_returns_default(self): """None返回默认配置.""" config = ConcatConfig.from_config_dict(None) assert config.segments == [] def test_empty_dict_returns_default(self): """空dict返回默认.""" config = ConcatConfig.from_config_dict({}) assert config.segments == [] def test_single_segment(self): """单片段.""" config = ConcatConfig.from_config_dict( { "segments": [{"video_path": "/a.mp4"}], } ) assert len(config.segments) == 1 assert config.segments[0].video_path == "/a.mp4" def test_multiple_segments(self): """多片段.""" config = ConcatConfig.from_config_dict( { "segments": [ {"video_path": "/a.mp4", "start_time": 1.0}, {"video_path": "/b.mp4", "duration": 5.0}, {"video_path": "/c.mp4"}, ], } ) assert len(config.segments) == 3 assert config.segments[0].start_time == 1.0 assert config.segments[1].duration == 5.0 def test_skips_no_path(self): """跳过无video_path的片段.""" config = ConcatConfig.from_config_dict( { "segments": [ {"video_path": "/a.mp4"}, {"other": "value"}, {"video_path": ""}, ], } ) assert len(config.segments) == 1 def test_segments_not_list_ignored(self): """segments不是列表忽略.""" config = ConcatConfig.from_config_dict( { "segments": "not_a_list", } ) assert config.segments == [] def test_output_size(self): """输出尺寸.""" config = ConcatConfig.from_config_dict( { "segments": [{"video_path": "/a.mp4"}], "output_width": 1920, "output_height": 1080, } ) assert config.output_width == 1920 assert config.output_height == 1080 def test_negative_output_size_clamped(self): """负输出尺寸钳制到0.""" config = ConcatConfig.from_config_dict( { "segments": [{"video_path": "/a.mp4"}], "output_width": -100, "output_height": -50, } ) assert config.output_width == 0 assert config.output_height == 0 def test_invalid_output_size_falls_back(self): """无效输出尺寸回退.""" config = ConcatConfig.from_config_dict( { "segments": [{"video_path": "/a.mp4"}], "output_width": "wide", "output_fps": "sixty", } ) assert config.output_width == 0 assert config.output_fps == 0.0 def test_output_fps(self): """输出帧率.""" config = ConcatConfig.from_config_dict( { "segments": [{"video_path": "/a.mp4"}], "output_fps": 60.0, } ) assert config.output_fps == 60.0 def test_force_reencode(self): """强制重新编码.""" config = ConcatConfig.from_config_dict( { "segments": [{"video_path": "/a.mp4"}], "force_reencode": True, } ) assert config.force_reencode is True def test_transition_config(self): """转场配置.""" config = ConcatConfig.from_config_dict( { "segments": [{"video_path": "/a.mp4"}, {"video_path": "/b.mp4"}], "transition": "crossfade", "transition_duration": 1.0, } ) assert config.transition == "crossfade" assert config.transition_duration == 1.0 def test_non_dict_config_returns_default(self): """非dict配置返回默认.""" config = ConcatConfig.from_config_dict("not_a_dict") assert config.segments == [] class TestHasEffect: """has_effect 属性测试.""" def test_no_segments_no_effect(self): """无片段无效果.""" config = ConcatConfig() assert config.has_effect is False def test_one_segment_no_effect(self): """单片段无效果(拼接至少需要2段).""" config = ConcatConfig( segments=[ ConcatSegment(video_path="/a.mp4"), ] ) assert config.has_effect is False def test_two_segments_has_effect(self): """两段及以上有效果.""" config = ConcatConfig( segments=[ ConcatSegment(video_path="/a.mp4"), ConcatSegment(video_path="/b.mp4"), ] ) assert config.has_effect is True class TestTotalSegments: """total_segments 属性测试.""" def test_no_segments(self): """零片段.""" config = ConcatConfig() assert config.total_segments == 0 def test_three_segments(self): """三个片段.""" config = ConcatConfig( segments=[ ConcatSegment(video_path="/a.mp4"), ConcatSegment(video_path="/b.mp4"), ConcatSegment(video_path="/c.mp4"), ] ) assert config.total_segments == 3