"""多轨道混音单元测试 - 配置解析等纯逻辑.""" from __future__ import annotations import pytest from video_processing.multi_track_mixer import ( DEFAULT_VOLUMES, MAX_AUDIO_TRACKS, TRACK_TYPE_AMBIENT, TRACK_TYPE_BGM, TRACK_TYPE_MAIN, TRACK_TYPE_SFX, TRACK_TYPE_VOICEOVER, AudioTrack, MultiTrackMixConfig, ) class TestConstants: """常量测试.""" def test_track_types(self): """5种轨道类型.""" assert TRACK_TYPE_MAIN == "main" assert TRACK_TYPE_BGM == "bgm" assert TRACK_TYPE_VOICEOVER == "voiceover" assert TRACK_TYPE_SFX == "sfx" assert TRACK_TYPE_AMBIENT == "ambient" def test_default_volumes(self): """5种默认音量.""" assert len(DEFAULT_VOLUMES) == 5 assert DEFAULT_VOLUMES[TRACK_TYPE_MAIN] == 1.0 assert DEFAULT_VOLUMES[TRACK_TYPE_BGM] == 0.3 assert DEFAULT_VOLUMES[TRACK_TYPE_VOICEOVER] == 1.0 assert DEFAULT_VOLUMES[TRACK_TYPE_SFX] == 0.7 assert DEFAULT_VOLUMES[TRACK_TYPE_AMBIENT] == 0.2 def test_max_tracks(self): """最大轨道数.""" assert MAX_AUDIO_TRACKS == 8 class TestAudioTrackDefaults: """AudioTrack 默认值测试.""" def test_default_values(self): """默认值正确.""" track = AudioTrack(track_id="t1", track_type="bgm", audio_path="/a.mp3") assert track.track_id == "t1" assert track.track_type == "bgm" assert track.audio_path == "/a.mp3" assert track.volume == 1.0 assert track.fade_in == 0.0 assert track.fade_out == 0.0 assert track.start_time == 0.0 assert track.duration == 0.0 assert track.enabled is True class TestAudioTrackFromDict: """AudioTrack.from_dict 解析测试.""" def test_basic_parsing(self): """基本解析.""" track = AudioTrack.from_dict( { "track_id": "t1", "track_type": "bgm", "audio_path": "/bgm.mp3", } ) assert track.track_id == "t1" assert track.track_type == "bgm" assert track.audio_path == "/bgm.mp3" def test_default_volume_by_type_bgm(self): """bgm默认音量0.3.""" track = AudioTrack.from_dict( { "track_id": "t1", "track_type": "bgm", "audio_path": "/a.mp3", } ) assert track.volume == 0.3 def test_default_volume_by_type_sfx(self): """sfx默认音量0.7.""" track = AudioTrack.from_dict( { "track_id": "t1", "track_type": "sfx", "audio_path": "/a.mp3", } ) assert track.volume == 0.7 def test_default_volume_unknown_type(self): """未知类型默认音量1.0.""" track = AudioTrack.from_dict( { "track_id": "t1", "track_type": "unknown_type", "audio_path": "/a.mp3", } ) assert track.volume == 1.0 def test_custom_volume(self): """自定义音量.""" track = AudioTrack.from_dict( { "track_id": "t1", "track_type": "bgm", "audio_path": "/a.mp3", "volume": 0.5, } ) assert track.volume == 0.5 def test_volume_clamped_high(self): """音量上限钳制.""" track = AudioTrack.from_dict( { "track_id": "t1", "track_type": "bgm", "audio_path": "/a.mp3", "volume": 3.0, } ) assert track.volume == 2.0 def test_volume_clamped_low(self): """音量下限钳制.""" track = AudioTrack.from_dict( { "track_id": "t1", "audio_path": "/a.mp3", "volume": -1.0, } ) assert track.volume == 0.0 def test_volume_invalid_falls_back(self): """无效音量回退到类型默认值.""" track = AudioTrack.from_dict( { "track_id": "t1", "track_type": "bgm", "audio_path": "/a.mp3", "volume": "not_a_number", } ) assert track.volume == 0.3 def test_fade_in(self): """淡入时长.""" track = AudioTrack.from_dict( { "track_id": "t1", "audio_path": "/a.mp3", "fade_in": 2.5, } ) assert track.fade_in == 2.5 def test_fade_negative_clamped(self): """负淡入钳制到0.""" track = AudioTrack.from_dict( { "track_id": "t1", "audio_path": "/a.mp3", "fade_in": -1.0, "fade_out": -2.0, } ) assert track.fade_in == 0.0 assert track.fade_out == 0.0 def test_start_time(self): """开始时间.""" track = AudioTrack.from_dict( { "track_id": "t1", "audio_path": "/a.mp3", "start_time": 5.5, } ) assert track.start_time == 5.5 def test_start_time_negative_clamped(self): """负开始时间钳制到0.""" track = AudioTrack.from_dict( { "track_id": "t1", "audio_path": "/a.mp3", "start_time": -3.0, } ) assert track.start_time == 0.0 def test_disabled_track(self): """禁用轨道.""" track = AudioTrack.from_dict( { "track_id": "t1", "audio_path": "/a.mp3", "enabled": False, } ) assert track.enabled is False def test_invalid_fade_in_falls_back(self): """无效淡入值回退到0.""" track = AudioTrack.from_dict( { "track_id": "t1", "audio_path": "/a.mp3", "fade_in": "fast", } ) assert track.fade_in == 0.0 class TestMultiTrackMixConfigDefaults: """MultiTrackMixConfig 默认值测试.""" def test_default_values(self): """默认值正确.""" config = MultiTrackMixConfig() assert config.tracks == [] assert config.master_volume == 1.0 assert config.normalize is True assert config.max_output_volume == 1.5 class TestMultiTrackMixConfigFromConfigDict: """MultiTrackMixConfig.from_config_dict 测试.""" def test_none_returns_default(self): """None返回默认配置.""" config = MultiTrackMixConfig.from_config_dict(None) assert config.tracks == [] assert config.master_volume == 1.0 def test_empty_dict_returns_default(self): """空dict返回默认.""" config = MultiTrackMixConfig.from_config_dict({}) assert config.tracks == [] def test_single_track(self): """单轨道.""" config = MultiTrackMixConfig.from_config_dict( { "tracks": [ { "track_id": "bgm1", "track_type": "bgm", "audio_path": "/bgm.mp3", }, ], } ) assert len(config.tracks) == 1 assert config.tracks[0].track_id == "bgm1" def test_multiple_tracks(self): """多轨道.""" config = MultiTrackMixConfig.from_config_dict( { "tracks": [ {"track_id": "t1", "track_type": "bgm", "audio_path": "/a.mp3"}, {"track_id": "t2", "track_type": "sfx", "audio_path": "/b.mp3"}, ], } ) assert len(config.tracks) == 2 def test_skips_disabled_tracks(self): """跳过禁用轨道.""" config = MultiTrackMixConfig.from_config_dict( { "tracks": [ {"track_id": "t1", "audio_path": "/a.mp3", "enabled": True}, {"track_id": "t2", "audio_path": "/b.mp3", "enabled": False}, ], } ) assert len(config.tracks) == 1 assert config.tracks[0].track_id == "t1" def test_skips_no_audio_path(self): """跳过无audio_path的轨道.""" config = MultiTrackMixConfig.from_config_dict( { "tracks": [ {"track_id": "t1", "audio_path": "/a.mp3"}, {"track_id": "t2", "audio_path": ""}, {"track_id": "t3"}, ], } ) assert len(config.tracks) == 1 def test_master_volume(self): """主音量.""" config = MultiTrackMixConfig.from_config_dict( { "master_volume": 0.8, "tracks": [{"track_id": "t1", "audio_path": "/a.mp3"}], } ) assert config.master_volume == 0.8 def test_master_volume_clamped(self): """主音量边界钳制.""" config = MultiTrackMixConfig.from_config_dict( { "master_volume": 5.0, "tracks": [{"track_id": "t1", "audio_path": "/a.mp3"}], } ) assert config.master_volume == 2.0 def test_normalize_disabled(self): """禁用归一化.""" config = MultiTrackMixConfig.from_config_dict( { "normalize": False, "tracks": [{"track_id": "t1", "audio_path": "/a.mp3"}], } ) assert config.normalize is False def test_tracks_not_list_ignored(self): """tracks不是列表时忽略.""" config = MultiTrackMixConfig.from_config_dict( { "tracks": "not_a_list", } ) assert config.tracks == [] def test_non_dict_track_skipped(self): """非dict轨道跳过.""" config = MultiTrackMixConfig.from_config_dict( { "tracks": [ {"track_id": "t1", "audio_path": "/a.mp3"}, "not_a_dict", ], } ) assert len(config.tracks) == 1 class TestHasEffect: """has_effect 属性测试.""" def test_no_tracks_no_effect(self): """无轨道无效果.""" config = MultiTrackMixConfig() assert config.has_effect is False def test_with_tracks_has_effect(self): """有轨道有效果.""" config = MultiTrackMixConfig( tracks=[ AudioTrack(track_id="t1", track_type="bgm", audio_path="/a.mp3"), ] ) assert config.has_effect is True def test_disabled_tracks_no_effect(self): """所有轨道都禁用无效果.""" config = MultiTrackMixConfig( tracks=[ AudioTrack(track_id="t1", track_type="bgm", audio_path="/a.mp3", enabled=False), ] ) assert config.has_effect is False