"""speed_config 领域模型单测.""" from __future__ import annotations import pytest from packages.domain.speed_config import ( DEFAULT_SPEED, MAX_SPEED, MIN_SPEED, SpeedConfig, adjust_duration, build_audio_filter, build_clip_speed_filter, build_video_filter, resolve_clip_speed, ) # ── 常量测试 ──────────────────────────────────────────────────────────────── class TestConstants: def test_min_speed(self): assert MIN_SPEED == 0.25 def test_max_speed(self): assert MAX_SPEED == 4.0 def test_default_speed(self): assert DEFAULT_SPEED == 1.0 # ── SpeedConfig.parse 测试 ──────────────────────────────────────────────── class TestSpeedConfigParse: def test_none_returns_default(self): cfg = SpeedConfig.parse(None) assert cfg.speed == DEFAULT_SPEED assert cfg.pitch_correct is True def test_empty_dict_returns_default(self): cfg = SpeedConfig.parse({}) assert cfg.speed == DEFAULT_SPEED def test_invalid_type_returns_default(self): cfg = SpeedConfig.parse("not_a_dict") assert cfg.speed == DEFAULT_SPEED def test_valid_speed(self): cfg = SpeedConfig.parse({"speed": 2.0}) assert cfg.speed == 2.0 def test_speed_clamped_low(self): cfg = SpeedConfig.parse({"speed": 0.1}) assert cfg.speed == MIN_SPEED def test_speed_clamped_high(self): cfg = SpeedConfig.parse({"speed": 5.0}) assert cfg.speed == MAX_SPEED def test_zero_speed_returns_default(self): cfg = SpeedConfig.parse({"speed": 0}) assert cfg.speed == DEFAULT_SPEED def test_negative_speed_returns_default(self): cfg = SpeedConfig.parse({"speed": -1.0}) assert cfg.speed == DEFAULT_SPEED def test_pitch_correct_false(self): cfg = SpeedConfig.parse({"pitch_correct": False}) assert cfg.pitch_correct is False def test_pitch_correct_invalid_type_defaults_true(self): cfg = SpeedConfig.parse({"pitch_correct": "yes"}) assert cfg.pitch_correct is True def test_string_speed_invalid_uses_default(self): cfg = SpeedConfig.parse({"speed": "fast"}) assert cfg.speed == DEFAULT_SPEED # ── SpeedConfig.clamp 测试 ──────────────────────────────────────────────── class TestClamp: def test_already_valid_unchanged(self): cfg = SpeedConfig(speed=1.5) cfg.clamp() assert cfg.speed == 1.5 def test_below_min_clamped(self): cfg = SpeedConfig(speed=0.1) cfg.clamp() assert cfg.speed == MIN_SPEED def test_above_max_clamped(self): cfg = SpeedConfig(speed=10.0) cfg.clamp() assert cfg.speed == MAX_SPEED def test_zero_defaults(self): cfg = SpeedConfig(speed=0.0) cfg.clamp() assert cfg.speed == DEFAULT_SPEED def test_negative_defaults(self): cfg = SpeedConfig(speed=-2.0) cfg.clamp() assert cfg.speed == DEFAULT_SPEED def test_exact_min_stays(self): cfg = SpeedConfig(speed=MIN_SPEED) cfg.clamp() assert cfg.speed == MIN_SPEED def test_exact_max_stays(self): cfg = SpeedConfig(speed=MAX_SPEED) cfg.clamp() assert cfg.speed == MAX_SPEED # ── is_original / is_fast / is_slow 测试 ───────────────────────────────── class TestSpeedProperties: def test_is_original_true(self): cfg = SpeedConfig(speed=1.0) assert cfg.is_original is True def test_is_original_false_fast(self): cfg = SpeedConfig(speed=2.0) assert cfg.is_original is False def test_is_original_false_slow(self): cfg = SpeedConfig(speed=0.5) assert cfg.is_original is False def test_is_original_near_one(self): cfg = SpeedConfig(speed=1.0000001) assert cfg.is_original is True def test_is_fast_true(self): cfg = SpeedConfig(speed=2.0) assert cfg.is_fast is True def test_is_fast_false(self): cfg = SpeedConfig(speed=0.5) assert cfg.is_fast is False def test_is_false_for_original(self): cfg = SpeedConfig(speed=1.0) assert cfg.is_fast is False assert cfg.is_slow is False def test_is_slow_true(self): cfg = SpeedConfig(speed=0.5) assert cfg.is_slow is True def test_is_slow_false(self): cfg = SpeedConfig(speed=2.0) assert cfg.is_slow is False # ── build_video_filter 测试 ─────────────────────────────────────────────── class TestBuildVideoFilter: def test_original_speed_empty(self): cfg = SpeedConfig(speed=1.0) assert build_video_filter(cfg) == "" def test_fast_speed_setpts(self): cfg = SpeedConfig(speed=2.0) result = build_video_filter(cfg) assert "setpts=PTS/2.0000" in result def test_slow_speed_setpts(self): cfg = SpeedConfig(speed=0.5) result = build_video_filter(cfg) assert "setpts=PTS/0.5000" in result def test_format_four_decimals(self): cfg = SpeedConfig(speed=1.5) result = build_video_filter(cfg) assert "1.5000" in result # ── build_audio_filter 测试 ─────────────────────────────────────────────── class TestBuildAudioFilter: def test_original_speed_empty(self): cfg = SpeedConfig(speed=1.0) assert build_audio_filter(cfg) == "" def test_single_stage_within_range(self): cfg = SpeedConfig(speed=1.5) result = build_audio_filter(cfg) assert result == "atempo=1.5000" assert result.count("atempo") == 1 def test_fast_two_stages(self): cfg = SpeedConfig(speed=3.0) result = build_audio_filter(cfg) assert result.count("atempo") == 2 # 2.0 * 1.5 = 3.0 assert "atempo=2.0000" in result assert "atempo=1.5000" in result def test_max_speed_two_stages(self): cfg = SpeedConfig(speed=4.0) result = build_audio_filter(cfg) assert result.count("atempo") == 2 # 2.0 * 2.0 = 4.0 assert result == "atempo=2.0000,atempo=2.0000" def test_slow_two_stages(self): cfg = SpeedConfig(speed=0.25) result = build_audio_filter(cfg) assert result.count("atempo") == 2 # 0.5 * 0.5 = 0.25 assert result == "atempo=0.5000,atempo=0.5000" def test_slow_single_stage(self): cfg = SpeedConfig(speed=0.8) result = build_audio_filter(cfg) assert result == "atempo=0.8000" assert result.count("atempo") == 1 def test_exactly_two_point_zero_single(self): cfg = SpeedConfig(speed=2.0) result = build_audio_filter(cfg) assert result.count("atempo") == 1 assert "atempo=2.0000" in result def test_exactly_half_single(self): cfg = SpeedConfig(speed=0.5) result = build_audio_filter(cfg) assert result.count("atempo") == 1 assert "atempo=0.5000" in result # ── adjust_duration 测试 ────────────────────────────────────────────────── class TestAdjustDuration: def test_original_speed_unchanged(self): cfg = SpeedConfig(speed=1.0) assert adjust_duration(10.0, cfg) == 10.0 def test_double_speed_halved(self): cfg = SpeedConfig(speed=2.0) assert adjust_duration(10.0, cfg) == 5.0 def test_half_speed_doubled(self): cfg = SpeedConfig(speed=0.5) assert adjust_duration(10.0, cfg) == 20.0 def test_zero_duration_unchanged(self): cfg = SpeedConfig(speed=2.0) assert adjust_duration(0.0, cfg) == 0.0 def test_negative_duration_unchanged(self): cfg = SpeedConfig(speed=2.0) assert adjust_duration(-5.0, cfg) == -5.0 # ── build_clip_speed_filter 测试 ───────────────────────────────────────── class TestBuildClipSpeedFilter: def test_normal_speed(self): vf, af, cfg = build_clip_speed_filter(2.0) assert vf == "setpts=PTS/2.0000" assert "atempo=2.0000" in af assert cfg.speed == 2.0 def test_clamped_speed(self): vf, af, cfg = build_clip_speed_filter(10.0) assert cfg.speed == MAX_SPEED def test_pitch_correct_param(self): vf, af, cfg = build_clip_speed_filter(1.5, pitch_correct=False) assert cfg.pitch_correct is False def test_original_speed_empty_filters(self): vf, af, cfg = build_clip_speed_filter(1.0) assert vf == "" assert af == "" # ── resolve_clip_speed 测试 ────────────────────────────────────────────── class TestResolveClipSpeed: def test_none_config_uses_global(self): assert resolve_clip_speed(None, 1.5) == 1.5 def test_no_playback_speed_uses_global(self): assert resolve_clip_speed({}, 1.5) == 1.5 def test_zero_speed_uses_global(self): assert resolve_clip_speed({"playback_speed": 0}, 1.5) == 1.5 def test_valid_speed_returns_speed(self): assert resolve_clip_speed({"playback_speed": 2.0}, 1.0) == 2.0 def test_invalid_type_uses_global(self): assert resolve_clip_speed({"playback_speed": "fast"}, 1.0) == 1.0 def test_default_global_speed(self): assert resolve_clip_speed({}) == DEFAULT_SPEED