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
306 lines
9.8 KiB
Python
Executable File
306 lines
9.8 KiB
Python
Executable File
"""视频调速引擎单元测试 - 配置解析 + 滤镜生成等纯逻辑."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from video_processing.speed_engine import (
|
|
MAX_SPEED,
|
|
MIN_SPEED,
|
|
SpeedConfig,
|
|
SpeedEngine,
|
|
)
|
|
|
|
# ── 常量测试 ──────────────────────────────────────────────────
|
|
|
|
|
|
class TestConstants:
|
|
"""常量值测试."""
|
|
|
|
def test_speed_ranges(self):
|
|
"""速度范围合理."""
|
|
assert MIN_SPEED == 0.25
|
|
assert MAX_SPEED == 4.0
|
|
assert MIN_SPEED < MAX_SPEED
|
|
|
|
|
|
# ── SpeedConfig 测试 ────────────────────────────────────────
|
|
|
|
|
|
class TestSpeedConfigDefaults:
|
|
"""默认配置测试."""
|
|
|
|
def test_default_values(self):
|
|
"""默认值正确."""
|
|
config = SpeedConfig()
|
|
assert config.speed == 1.0
|
|
assert config.pitch_correct is True
|
|
|
|
def test_is_original_default(self):
|
|
"""默认配置是原速."""
|
|
config = SpeedConfig()
|
|
assert config.is_original is True
|
|
|
|
|
|
class TestSpeedConfigParse:
|
|
"""parse 配置解析测试."""
|
|
|
|
def test_none_returns_default(self):
|
|
"""None 返回默认配置."""
|
|
config = SpeedConfig.parse(None)
|
|
assert config.speed == 1.0
|
|
assert config.pitch_correct is True
|
|
|
|
def test_empty_dict_returns_default(self):
|
|
"""空 dict 返回默认配置."""
|
|
config = SpeedConfig.parse({})
|
|
assert config.is_original is True
|
|
|
|
def test_custom_speed(self):
|
|
"""自定义速度."""
|
|
config = SpeedConfig.parse({"speed": 2.0})
|
|
assert config.speed == 2.0
|
|
assert config.is_original is False
|
|
|
|
def test_pitch_correct_disabled(self):
|
|
"""禁用音调修正."""
|
|
config = SpeedConfig.parse({"speed": 1.5, "pitch_correct": False})
|
|
assert config.pitch_correct is False
|
|
|
|
def test_invalid_speed_type_falls_back(self):
|
|
"""无效速度类型回退到默认."""
|
|
config = SpeedConfig.parse({"speed": "fast"})
|
|
assert config.speed == 1.0
|
|
|
|
def test_invalid_pitch_correct_type_falls_back(self):
|
|
"""无效pitch_correct类型回退到默认."""
|
|
config = SpeedConfig.parse({"speed": 2.0, "pitch_correct": "yes"})
|
|
assert config.pitch_correct is True
|
|
|
|
def test_non_dict_input_returns_default(self):
|
|
"""非dict输入返回默认."""
|
|
config = SpeedConfig.parse("not_a_dict")
|
|
assert config.is_original is True
|
|
|
|
|
|
class TestSpeedConfigClamp:
|
|
"""clamp 边界钳制测试."""
|
|
|
|
def test_speed_below_min_clamped(self):
|
|
"""低于最小值钳制."""
|
|
config = SpeedConfig(speed=0.1)
|
|
config.clamp()
|
|
assert config.speed == MIN_SPEED
|
|
|
|
def test_speed_above_max_clamped(self):
|
|
"""高于最大值钳制."""
|
|
config = SpeedConfig(speed=10.0)
|
|
config.clamp()
|
|
assert config.speed == MAX_SPEED
|
|
|
|
def test_zero_speed_falls_back_to_default(self):
|
|
"""速度为0回退到默认."""
|
|
config = SpeedConfig(speed=0)
|
|
config.clamp()
|
|
assert config.speed == 1.0
|
|
|
|
def test_negative_speed_falls_back_to_default(self):
|
|
"""负速度回退到默认."""
|
|
config = SpeedConfig(speed=-2.0)
|
|
config.clamp()
|
|
assert config.speed == 1.0
|
|
|
|
def test_speed_at_min_ok(self):
|
|
"""最小值边界."""
|
|
config = SpeedConfig(speed=MIN_SPEED)
|
|
config.clamp()
|
|
assert config.speed == MIN_SPEED
|
|
|
|
def test_speed_at_max_ok(self):
|
|
"""最大值边界."""
|
|
config = SpeedConfig(speed=MAX_SPEED)
|
|
config.clamp()
|
|
assert config.speed == MAX_SPEED
|
|
|
|
def test_speed_in_range_unchanged(self):
|
|
"""合法范围内不修改."""
|
|
config = SpeedConfig(speed=1.5)
|
|
config.clamp()
|
|
assert config.speed == 1.5
|
|
|
|
def test_parse_auto_clamps(self):
|
|
"""parse 自动钳制."""
|
|
config = SpeedConfig.parse({"speed": 100.0})
|
|
assert config.speed == MAX_SPEED
|
|
|
|
|
|
class TestIsOriginal:
|
|
"""is_original 属性测试."""
|
|
|
|
def test_exactly_one(self):
|
|
"""速度恰好为1."""
|
|
assert SpeedConfig(speed=1.0).is_original is True
|
|
|
|
def test_very_close_to_one(self):
|
|
"""非常接近1也算原速."""
|
|
assert SpeedConfig(speed=1.0000001).is_original is True
|
|
|
|
def test_not_one(self):
|
|
"""不是1."""
|
|
assert SpeedConfig(speed=1.1).is_original is False
|
|
assert SpeedConfig(speed=0.9).is_original is False
|
|
|
|
|
|
# ── SpeedEngine 测试 ────────────────────────────────────────
|
|
|
|
|
|
class TestBuildVideoFilter:
|
|
"""build_video_filter 测试."""
|
|
|
|
def setup_method(self):
|
|
self.engine = SpeedEngine()
|
|
|
|
def test_original_speed_empty_filter(self):
|
|
"""原速返回空字符串(跳过滤镜)."""
|
|
config = SpeedConfig(speed=1.0)
|
|
assert self.engine.build_video_filter(config) == ""
|
|
|
|
def test_speed_up_2x(self):
|
|
"""2倍速."""
|
|
config = SpeedConfig(speed=2.0)
|
|
result = self.engine.build_video_filter(config)
|
|
assert "setpts=PTS/2.0000" == result
|
|
|
|
def test_slow_down_half(self):
|
|
"""0.5倍速."""
|
|
config = SpeedConfig(speed=0.5)
|
|
result = self.engine.build_video_filter(config)
|
|
assert "setpts=PTS/0.5000" == result
|
|
|
|
def test_contains_setpts(self):
|
|
"""包含setpts滤镜."""
|
|
config = SpeedConfig(speed=1.5)
|
|
result = self.engine.build_video_filter(config)
|
|
assert "setpts=PTS/" in result
|
|
|
|
|
|
class TestBuildAudioFilter:
|
|
"""build_audio_filter 测试."""
|
|
|
|
def setup_method(self):
|
|
self.engine = SpeedEngine()
|
|
|
|
def test_original_speed_empty_filter(self):
|
|
"""原速返回空字符串."""
|
|
config = SpeedConfig(speed=1.0)
|
|
assert self.engine.build_audio_filter(config) == ""
|
|
|
|
def test_single_stage_2x(self):
|
|
"""2倍速单级atempo."""
|
|
config = SpeedConfig(speed=2.0)
|
|
result = self.engine.build_audio_filter(config)
|
|
assert result == "atempo=2.0000"
|
|
|
|
def test_single_stage_half(self):
|
|
"""0.5倍速单级atempo."""
|
|
config = SpeedConfig(speed=0.5)
|
|
result = self.engine.build_audio_filter(config)
|
|
assert result == "atempo=0.5000"
|
|
|
|
def test_multi_stage_4x(self):
|
|
"""4倍速需要两级 atempo=2.0,atempo=2.0."""
|
|
config = SpeedConfig(speed=4.0)
|
|
result = self.engine.build_audio_filter(config)
|
|
assert result == "atempo=2.0000,atempo=2.0000"
|
|
|
|
def test_multi_stage_quarter(self):
|
|
"""0.25倍速需要两级 atempo=0.5,atempo=0.5."""
|
|
config = SpeedConfig(speed=0.25)
|
|
result = self.engine.build_audio_filter(config)
|
|
assert result == "atempo=0.5000,atempo=0.5000"
|
|
|
|
def test_multi_stage_3x(self):
|
|
"""3倍速: 2.0 * 1.5."""
|
|
config = SpeedConfig(speed=3.0)
|
|
result = self.engine.build_audio_filter(config)
|
|
stages = result.split(",")
|
|
assert len(stages) == 2
|
|
# 验证两级相乘等于3
|
|
values = [float(s.split("=")[1]) for s in stages]
|
|
assert abs(values[0] * values[1] - 3.0) < 0.01
|
|
|
|
|
|
class TestSplitAtempoStages:
|
|
"""_split_atempo_stages 测试."""
|
|
|
|
def test_single_stage_within_range(self):
|
|
"""范围内单级."""
|
|
stages = SpeedEngine._split_atempo_stages(1.5)
|
|
assert len(stages) == 1
|
|
assert stages[0] == 1.5
|
|
|
|
def test_single_stage_at_max(self):
|
|
"""最大值边界单级."""
|
|
stages = SpeedEngine._split_atempo_stages(2.0)
|
|
assert len(stages) == 1
|
|
|
|
def test_single_stage_at_min(self):
|
|
"""最小值边界单级."""
|
|
stages = SpeedEngine._split_atempo_stages(0.5)
|
|
assert len(stages) == 1
|
|
|
|
def test_multi_stage_double_speed(self):
|
|
"""4x 需要两级."""
|
|
stages = SpeedEngine._split_atempo_stages(4.0)
|
|
assert len(stages) == 2
|
|
assert abs(stages[0] * stages[1] - 4.0) < 0.01
|
|
|
|
def test_multi_stage_half_speed(self):
|
|
"""0.25x 需要两级."""
|
|
stages = SpeedEngine._split_atempo_stages(0.25)
|
|
assert len(stages) == 2
|
|
assert abs(stages[0] * stages[1] - 0.25) < 0.01
|
|
|
|
def test_all_stages_within_valid_range(self):
|
|
"""所有分级都在有效范围内."""
|
|
for speed in [0.25, 0.3, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0]:
|
|
stages = SpeedEngine._split_atempo_stages(speed)
|
|
for s in stages:
|
|
assert 0.5 <= s <= 2.0, f"speed={speed}, stage={s} out of range"
|
|
|
|
|
|
class TestAdjustDuration:
|
|
"""adjust_duration 时长计算测试."""
|
|
|
|
def setup_method(self):
|
|
self.engine = SpeedEngine()
|
|
|
|
def test_original_speed_unchanged(self):
|
|
"""原速时长不变."""
|
|
result = self.engine.adjust_duration(100.0, SpeedConfig(speed=1.0))
|
|
assert result == 100.0
|
|
|
|
def test_double_speed_half_duration(self):
|
|
"""2倍速时长减半."""
|
|
result = self.engine.adjust_duration(100.0, SpeedConfig(speed=2.0))
|
|
assert result == 50.0
|
|
|
|
def test_half_speed_double_duration(self):
|
|
"""0.5倍速时长翻倍."""
|
|
result = self.engine.adjust_duration(100.0, SpeedConfig(speed=0.5))
|
|
assert result == 200.0
|
|
|
|
def test_zero_duration_unchanged(self):
|
|
"""零时长不变."""
|
|
result = self.engine.adjust_duration(0.0, SpeedConfig(speed=2.0))
|
|
assert result == 0.0
|
|
|
|
def test_negative_duration_unchanged(self):
|
|
"""负时长不变(异常值保护)."""
|
|
result = self.engine.adjust_duration(-10.0, SpeedConfig(speed=2.0))
|
|
assert result == -10.0
|
|
|
|
def test_quarter_speed(self):
|
|
"""0.25倍速时长4倍."""
|
|
result = self.engine.adjust_duration(60.0, SpeedConfig(speed=0.25))
|
|
assert abs(result - 240.0) < 0.01
|