Files
xiaoxia-saas/tests/unit/test_multi_track_mixer.py
CI Bot 7a72cfd709
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m9s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m19s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m10s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m7s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m7s
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m17s
CI/CD Pipeline / Frontend Lint (push) Successful in 3m37s
CI/CD Pipeline / Integration Tests (push) Successful in 2m18s
CI/CD Pipeline / Unit Tests (push) Failing after 4m59s
CI/CD Pipeline / Build Staging API Image (push) Successful in 8m39s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 3m25s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 42s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 27m2s
CI/CD Pipeline / Staging API Integration Tests (push) Failing after 30m15s
style: auto-format with black + isort + prettier
2026-07-25 00:16:10 +00:00

383 lines
11 KiB
Python
Executable File

"""多轨道混音单元测试 - 配置解析等纯逻辑."""
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