e2ddc679bb
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
640 lines
23 KiB
Python
Executable File
640 lines
23 KiB
Python
Executable File
"""trim_config 裁剪配置领域模型单元测试."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import pytest
|
||
|
||
from packages.domain.trim_config import (
|
||
MIN_TRIM_DURATION,
|
||
TrimConfig,
|
||
TrimSegment,
|
||
build_audio_trim_filter,
|
||
build_video_trim_filter,
|
||
extract_trim_from_clip_config,
|
||
parse_segments_from_config,
|
||
resolve_segments,
|
||
)
|
||
|
||
# ── TrimConfig.from_dict 测试 ────────────────────────────────────────────────
|
||
|
||
|
||
class TestTrimConfigFromDict:
|
||
"""TrimConfig.from_dict 测试."""
|
||
|
||
def test_none_returns_none(self):
|
||
"""None返回None."""
|
||
assert TrimConfig.from_dict(None) is None
|
||
|
||
def test_empty_dict_returns_none(self):
|
||
"""空dict返回None."""
|
||
assert TrimConfig.from_dict({}) is None
|
||
|
||
def test_all_zero_returns_none(self):
|
||
"""全0返回None(不裁剪)."""
|
||
assert TrimConfig.from_dict({"start_time": 0, "end_time": 0, "duration": 0}) is None
|
||
|
||
def test_start_and_end(self):
|
||
"""start + end."""
|
||
result = TrimConfig.from_dict({"start_time": 5, "end_time": 10})
|
||
assert result is not None
|
||
assert result.start_time == 5.0
|
||
assert result.end_time == 10.0
|
||
|
||
def test_start_and_duration(self):
|
||
"""start + duration."""
|
||
result = TrimConfig.from_dict({"start_time": 2, "duration": 5})
|
||
assert result is not None
|
||
assert result.start_time == 2.0
|
||
assert result.duration == 5.0
|
||
|
||
def test_end_and_duration(self):
|
||
"""end + duration."""
|
||
result = TrimConfig.from_dict({"end_time": 10, "duration": 3})
|
||
assert result is not None
|
||
assert result.end_time == 10.0
|
||
assert result.duration == 3.0
|
||
|
||
def test_only_start_returns_config(self):
|
||
"""只有start_time也返回有效配置(从start取到末尾语义)."""
|
||
result = TrimConfig.from_dict({"start_time": 3})
|
||
assert result is not None
|
||
assert result.start_time == 3.0
|
||
|
||
def test_only_duration_returns_config(self):
|
||
"""只有duration也返回(从开头取duration)."""
|
||
result = TrimConfig.from_dict({"duration": 5})
|
||
assert result is not None
|
||
assert result.duration == 5.0
|
||
|
||
def test_only_end_returns_config(self):
|
||
"""只有end_time也返回."""
|
||
result = TrimConfig.from_dict({"end_time": 8})
|
||
assert result is not None
|
||
assert result.end_time == 8.0
|
||
|
||
def test_string_values(self):
|
||
"""字符串值能正确解析."""
|
||
result = TrimConfig.from_dict({"start_time": "2.5", "duration": "3"})
|
||
assert result is not None
|
||
assert result.start_time == 2.5
|
||
assert result.duration == 3.0
|
||
|
||
def test_none_values_treated_as_zero(self):
|
||
"""None值当作0处理."""
|
||
result = TrimConfig.from_dict({"start_time": None, "duration": 5})
|
||
assert result is not None
|
||
assert result.start_time == 0.0
|
||
assert result.duration == 5.0
|
||
|
||
def test_false_values_treated_as_zero(self):
|
||
"""0/false值当作0处理."""
|
||
result = TrimConfig.from_dict({"start_time": 0, "duration": 0})
|
||
assert result is None
|
||
|
||
def test_all_three_params(self):
|
||
"""三个参数都给了."""
|
||
result = TrimConfig.from_dict({"start_time": 1, "end_time": 6, "duration": 5})
|
||
assert result is not None
|
||
assert result.start_time == 1.0
|
||
assert result.end_time == 6.0
|
||
assert result.duration == 5.0
|
||
|
||
|
||
# ── TrimConfig.validate_and_resolve 测试 ────────────────────────────────────
|
||
|
||
|
||
class TestTrimConfigValidateAndResolve:
|
||
"""validate_and_resolve 三选二推导 + 边界钳制测试."""
|
||
|
||
# 基础三选二推导
|
||
|
||
def test_start_and_end(self):
|
||
"""start + end → 推导duration."""
|
||
cfg = TrimConfig(start_time=5, end_time=15)
|
||
result = cfg.validate_and_resolve(asset_duration=30)
|
||
assert result.start_time == 5.0
|
||
assert result.end_time == 15.0
|
||
assert result.duration == 10.0
|
||
|
||
def test_start_and_duration(self):
|
||
"""start + duration → 推导end."""
|
||
cfg = TrimConfig(start_time=3, duration=7)
|
||
result = cfg.validate_and_resolve(asset_duration=30)
|
||
assert result.start_time == 3.0
|
||
assert result.duration == 7.0
|
||
assert result.end_time == 10.0
|
||
|
||
def test_end_and_duration(self):
|
||
"""end + duration → 推导start."""
|
||
cfg = TrimConfig(end_time=20, duration=5)
|
||
result = cfg.validate_and_resolve(asset_duration=30)
|
||
assert result.end_time == 20.0
|
||
assert result.duration == 5.0
|
||
assert result.start_time == 15.0
|
||
|
||
def test_end_minus_duration_negative(self):
|
||
"""end + duration 但算出start<0 → 钳制到0重新计算."""
|
||
cfg = TrimConfig(end_time=3, duration=10)
|
||
result = cfg.validate_and_resolve(asset_duration=30)
|
||
assert result.start_time == 0.0
|
||
assert result.end_time == 3.0
|
||
assert result.duration == 3.0
|
||
|
||
def test_only_start_takes_to_end(self):
|
||
"""只有start → 取到素材末尾."""
|
||
cfg = TrimConfig(start_time=5)
|
||
result = cfg.validate_and_resolve(asset_duration=30)
|
||
assert result.start_time == 5.0
|
||
assert result.end_time == 30.0
|
||
assert result.duration == 25.0
|
||
|
||
def test_only_end_takes_from_start(self):
|
||
"""只有end → 从开头取到end."""
|
||
cfg = TrimConfig(end_time=10)
|
||
result = cfg.validate_and_resolve(asset_duration=30)
|
||
assert result.start_time == 0.0
|
||
assert result.end_time == 10.0
|
||
assert result.duration == 10.0
|
||
|
||
def test_only_duration(self):
|
||
"""只有duration → 从开头取duration."""
|
||
cfg = TrimConfig(duration=8)
|
||
result = cfg.validate_and_resolve(asset_duration=30)
|
||
assert result.start_time == 0.0
|
||
assert result.end_time == 8.0
|
||
assert result.duration == 8.0
|
||
|
||
def test_all_zero_noop(self):
|
||
"""全0 → noop不裁剪."""
|
||
cfg = TrimConfig()
|
||
result = cfg.validate_and_resolve(asset_duration=30)
|
||
assert result.start_time == 0.0
|
||
assert result.duration == 0.0
|
||
assert result.is_noop
|
||
|
||
# 边界钳制
|
||
|
||
def test_start_negative_clamped(self):
|
||
"""start为负 → 钳制到0."""
|
||
cfg = TrimConfig(start_time=-5, duration=10)
|
||
result = cfg.validate_and_resolve(asset_duration=30)
|
||
assert result.start_time == 0.0
|
||
assert result.duration == 10.0
|
||
assert result.end_time == 10.0
|
||
|
||
def test_end_exceeds_asset_duration(self):
|
||
"""end超过素材时长 → 钳制."""
|
||
cfg = TrimConfig(start_time=5, end_time=50)
|
||
result = cfg.validate_and_resolve(asset_duration=30)
|
||
assert result.end_time == 30.0
|
||
assert result.duration == 25.0
|
||
|
||
def test_start_exceeds_asset_duration(self):
|
||
"""start超过素材时长 → 移到末尾取最小片段."""
|
||
cfg = TrimConfig(start_time=40, duration=5)
|
||
result = cfg.validate_and_resolve(asset_duration=30)
|
||
assert result.end_time == 30.0
|
||
assert result.start_time >= 0
|
||
assert result.duration >= 0
|
||
|
||
def test_start_equals_end_invalid(self):
|
||
"""start >= end → 无效(duration=0)."""
|
||
cfg = TrimConfig(start_time=10, end_time=10)
|
||
result = cfg.validate_and_resolve(asset_duration=30)
|
||
assert result.duration == 0.0
|
||
assert result.is_valid is False
|
||
|
||
def test_start_greater_than_end(self):
|
||
"""start > end → 无效."""
|
||
cfg = TrimConfig(start_time=15, end_time=10)
|
||
result = cfg.validate_and_resolve(asset_duration=30)
|
||
assert result.duration == 0.0
|
||
assert result.is_valid is False
|
||
|
||
def test_zero_asset_duration(self):
|
||
"""素材时长为0 → 返回noop."""
|
||
cfg = TrimConfig(start_time=5, duration=10)
|
||
result = cfg.validate_and_resolve(asset_duration=0)
|
||
assert result.start_time == 0.0
|
||
assert result.duration == 0.0
|
||
assert result.is_noop
|
||
|
||
def test_negative_asset_duration(self):
|
||
"""素材时长为负 → 返回noop."""
|
||
cfg = TrimConfig(start_time=1, duration=2)
|
||
result = cfg.validate_and_resolve(asset_duration=-5)
|
||
assert result.is_noop
|
||
|
||
# duration 边界
|
||
|
||
def test_duration_preserved_exactly(self):
|
||
"""精确时长保持."""
|
||
cfg = TrimConfig(start_time=1.234, duration=2.567)
|
||
result = cfg.validate_and_resolve(asset_duration=10)
|
||
assert abs(result.duration - 2.567) < 0.001
|
||
assert abs(result.start_time - 1.234) < 0.001
|
||
|
||
def test_duration_never_negative(self):
|
||
"""duration永远不为负."""
|
||
cfg = TrimConfig(start_time=10, end_time=5)
|
||
result = cfg.validate_and_resolve(asset_duration=30)
|
||
assert result.duration >= 0
|
||
|
||
|
||
# ── TrimConfig 属性测试 ─────────────────────────────────────────────────────
|
||
|
||
|
||
class TestTrimConfigProperties:
|
||
"""TrimConfig 属性测试."""
|
||
|
||
def test_is_valid_valid_trim(self):
|
||
"""有效裁剪."""
|
||
cfg = TrimConfig(start_time=0, end_time=0, duration=5)
|
||
assert cfg.is_valid is True
|
||
|
||
def test_is_valid_zero_duration(self):
|
||
"""duration=0无效."""
|
||
cfg = TrimConfig(duration=0)
|
||
assert cfg.is_valid is False
|
||
|
||
def test_is_valid_min_threshold(self):
|
||
"""刚好等于最小阈值也算有效."""
|
||
cfg = TrimConfig(duration=MIN_TRIM_DURATION)
|
||
assert cfg.is_valid is True
|
||
|
||
def test_is_valid_below_min(self):
|
||
"""低于最小阈值无效."""
|
||
cfg = TrimConfig(duration=MIN_TRIM_DURATION / 2)
|
||
assert cfg.is_valid is False
|
||
|
||
def test_is_noop_true(self):
|
||
"""从0开始且duration=0是noop."""
|
||
cfg = TrimConfig(start_time=0, duration=0)
|
||
assert cfg.is_noop is True
|
||
|
||
def test_is_noop_false_has_start(self):
|
||
"""有start不是noop."""
|
||
cfg = TrimConfig(start_time=5, duration=0)
|
||
assert cfg.is_noop is False
|
||
|
||
def test_is_noop_false_has_duration(self):
|
||
"""有duration不是noop."""
|
||
cfg = TrimConfig(start_time=0, duration=1)
|
||
assert cfg.is_noop is False
|
||
|
||
def test_trim_from_start_true(self):
|
||
"""start=0是从开头裁剪."""
|
||
cfg = TrimConfig(start_time=0, duration=5)
|
||
assert cfg.trim_from_start is True
|
||
|
||
def test_trim_from_start_false(self):
|
||
"""start>0不是从开头裁剪."""
|
||
cfg = TrimConfig(start_time=2, duration=5)
|
||
assert cfg.trim_from_start is False
|
||
|
||
def test_trim_from_start_negative_treated_as_zero(self):
|
||
"""start<0也认为从开头."""
|
||
cfg = TrimConfig(start_time=-1, duration=5)
|
||
assert cfg.trim_from_start is True
|
||
|
||
|
||
# ── TrimSegment 测试 ─────────────────────────────────────────────────────────
|
||
|
||
|
||
class TestTrimSegment:
|
||
"""TrimSegment 测试."""
|
||
|
||
def test_from_dict_basic(self):
|
||
"""基础构造."""
|
||
data = {"segment_id": "seg1", "start_time": 1, "end_time": 5, "order": 2}
|
||
seg = TrimSegment.from_dict(data)
|
||
assert seg.segment_id == "seg1"
|
||
assert seg.order == 2
|
||
assert seg.trim.start_time == 1.0
|
||
assert seg.trim.end_time == 5.0
|
||
|
||
def test_from_dict_default_order(self):
|
||
"""缺order使用默认值."""
|
||
data = {"segment_id": "s1", "start_time": 0, "duration": 3}
|
||
seg = TrimSegment.from_dict(data, default_order=5)
|
||
assert seg.order == 5
|
||
|
||
def test_from_dict_missing_segment_id(self):
|
||
"""缺segment_id用默认名."""
|
||
data = {"start_time": 0, "duration": 2}
|
||
seg = TrimSegment.from_dict(data, default_order=3)
|
||
assert seg.segment_id == "seg_3"
|
||
|
||
def test_from_dict_duration(self):
|
||
"""duration正确传递."""
|
||
data = {"segment_id": "s1", "duration": 10}
|
||
seg = TrimSegment.from_dict(data)
|
||
assert seg.trim.duration == 10.0
|
||
|
||
|
||
# ── build_video_trim_filter 测试 ────────────────────────────────────────────
|
||
|
||
|
||
class TestBuildVideoTrimFilter:
|
||
"""build_video_trim_filter 视频滤镜构建测试."""
|
||
|
||
def test_noop_filter(self):
|
||
"""noop时只有setpts."""
|
||
cfg = TrimConfig()
|
||
result = build_video_trim_filter("[0:v]", cfg, "[vout]")
|
||
assert "trim=" not in result
|
||
assert "setpts=PTS-STARTPTS" in result
|
||
assert result.startswith("[0:v]")
|
||
assert result.endswith("[vout]")
|
||
|
||
def test_start_and_duration(self):
|
||
"""start + duration 完整滤镜."""
|
||
cfg = TrimConfig(start_time=10, end_time=15, duration=5)
|
||
result = build_video_trim_filter("[0:v]", cfg, "[v0]")
|
||
assert "trim=start=10.000:duration=5.000" in result
|
||
assert "setpts=PTS-STARTPTS" in result
|
||
assert result.startswith("[0:v]")
|
||
assert result.endswith("[v0]")
|
||
|
||
def test_only_start(self):
|
||
"""只有start(取到末尾的情况resolve后也有duration)."""
|
||
cfg = TrimConfig(start_time=5, end_time=30, duration=25)
|
||
result = build_video_trim_filter("[1:v]", cfg, "[v1]")
|
||
assert "start=5.000" in result
|
||
assert "duration=25.000" in result
|
||
|
||
def test_only_duration_from_start(self):
|
||
"""从开头裁剪duration."""
|
||
cfg = TrimConfig(start_time=0, end_time=3, duration=3)
|
||
result = build_video_trim_filter("[0:v]", cfg, "[out]")
|
||
assert "trim=duration=3.000" in result or "trim=start=0" in result
|
||
# start=0 不写,只有duration
|
||
assert "start=0" not in result
|
||
|
||
def test_preserves_input_output_labels(self):
|
||
"""保持输入输出标签."""
|
||
cfg = TrimConfig(start_time=1, duration=2)
|
||
result = build_video_trim_filter("[in_label]", cfg, "[out_label]")
|
||
assert result.startswith("[in_label]")
|
||
assert result.endswith("[out_label]")
|
||
|
||
def test_three_decimal_precision(self):
|
||
"""三位小数精度."""
|
||
cfg = TrimConfig(start_time=1.234, duration=2.678)
|
||
result = build_video_trim_filter("[0:v]", cfg, "[v]")
|
||
assert "start=1.234" in result
|
||
assert "duration=2.678" in result
|
||
|
||
|
||
# ── build_audio_trim_filter 测试 ────────────────────────────────────────────
|
||
|
||
|
||
class TestBuildAudioTrimFilter:
|
||
"""build_audio_trim_filter 音频滤镜构建测试."""
|
||
|
||
def test_noop_filter(self):
|
||
"""noop时只有asetpts."""
|
||
cfg = TrimConfig()
|
||
result = build_audio_trim_filter("[0:a]", cfg, "[aout]")
|
||
assert "atrim=" not in result
|
||
assert "asetpts=PTS-STARTPTS" in result
|
||
assert result.startswith("[0:a]")
|
||
assert result.endswith("[aout]")
|
||
|
||
def test_start_and_duration(self):
|
||
"""start + duration 完整滤镜."""
|
||
cfg = TrimConfig(start_time=5, duration=3)
|
||
result = build_audio_trim_filter("[0:a]", cfg, "[a0]")
|
||
assert "atrim=start=5.000:duration=3.000" in result
|
||
assert "asetpts=PTS-STARTPTS" in result
|
||
|
||
def test_only_duration(self):
|
||
"""只有duration(start=0时不写start参数)."""
|
||
cfg = TrimConfig(start_time=0, duration=4)
|
||
result = build_audio_trim_filter("[0:a]", cfg, "[a0]")
|
||
assert "atrim=duration=4.000" in result
|
||
|
||
def test_uses_atrim_not_trim(self):
|
||
"""用atrim不是trim."""
|
||
cfg = TrimConfig(start_time=1, duration=2)
|
||
result = build_audio_trim_filter("[0:a]", cfg, "[a]")
|
||
assert "atrim=" in result
|
||
assert ",trim=" not in result
|
||
|
||
|
||
# ── resolve_segments 测试 ────────────────────────────────────────────────────
|
||
|
||
|
||
class TestResolveSegments:
|
||
"""resolve_segments 多段裁剪解析测试."""
|
||
|
||
def test_empty_list(self):
|
||
"""空列表返回空."""
|
||
result = resolve_segments([], asset_duration=30)
|
||
assert result == []
|
||
|
||
def test_single_segment(self):
|
||
"""单段解析."""
|
||
seg = TrimSegment(
|
||
segment_id="s1",
|
||
trim=TrimConfig(start_time=0, duration=5),
|
||
order=0,
|
||
)
|
||
result = resolve_segments([seg], asset_duration=30)
|
||
assert len(result) == 1
|
||
assert result[0].segment_id == "s1"
|
||
assert result[0].trim.duration == 5.0
|
||
|
||
def test_multiple_segments_sorted(self):
|
||
"""多段按order排序."""
|
||
segs = [
|
||
TrimSegment(segment_id="s1", trim=TrimConfig(duration=2), order=2),
|
||
TrimSegment(segment_id="s2", trim=TrimConfig(duration=3), order=0),
|
||
TrimSegment(segment_id="s3", trim=TrimConfig(duration=1), order=1),
|
||
]
|
||
result = resolve_segments(segs, asset_duration=30)
|
||
assert len(result) == 3
|
||
assert result[0].segment_id == "s2"
|
||
assert result[1].segment_id == "s3"
|
||
assert result[2].segment_id == "s1"
|
||
|
||
def test_filter_invalid_segments(self):
|
||
"""过滤无效段."""
|
||
segs = [
|
||
TrimSegment(segment_id="valid", trim=TrimConfig(duration=5), order=0),
|
||
TrimSegment(segment_id="invalid", trim=TrimConfig(duration=0), order=1),
|
||
]
|
||
result = resolve_segments(segs, asset_duration=30)
|
||
assert len(result) == 1
|
||
assert result[0].segment_id == "valid"
|
||
|
||
def test_negative_order_uses_index(self):
|
||
"""order为负时使用索引."""
|
||
segs = [
|
||
TrimSegment(segment_id="s1", trim=TrimConfig(duration=2), order=-1),
|
||
TrimSegment(segment_id="s2", trim=TrimConfig(duration=3), order=-1),
|
||
]
|
||
result = resolve_segments(segs, asset_duration=30)
|
||
assert len(result) == 2
|
||
# order用各自的index值(0, 1)
|
||
|
||
def test_resolves_with_asset_duration(self):
|
||
"""用素材时长做边界钳制."""
|
||
seg = TrimSegment(
|
||
segment_id="s1",
|
||
trim=TrimConfig(start_time=0, duration=50), # 超过素材时长
|
||
order=0,
|
||
)
|
||
result = resolve_segments([seg], asset_duration=30)
|
||
assert len(result) == 1
|
||
assert result[0].trim.end_time == 30.0
|
||
assert result[0].trim.duration == 30.0
|
||
|
||
|
||
# ── parse_segments_from_config 测试 ─────────────────────────────────────────
|
||
|
||
|
||
class TestParseSegmentsFromConfig:
|
||
"""parse_segments_from_config 测试."""
|
||
|
||
def test_none_config(self):
|
||
"""None返回空."""
|
||
assert parse_segments_from_config(None) == []
|
||
|
||
def test_empty_config(self):
|
||
"""空dict返回空."""
|
||
assert parse_segments_from_config({}) == []
|
||
|
||
def test_trim_segments_list(self):
|
||
"""多段配置解析."""
|
||
config = {
|
||
"trim_segments": [
|
||
{"segment_id": "s1", "start_time": 0, "duration": 3, "order": 0},
|
||
{"segment_id": "s2", "start_time": 5, "duration": 4, "order": 1},
|
||
]
|
||
}
|
||
result = parse_segments_from_config(config)
|
||
assert len(result) == 2
|
||
assert result[0].segment_id == "s1"
|
||
assert result[0].trim.duration == 3.0
|
||
assert result[1].segment_id == "s2"
|
||
assert result[1].trim.start_time == 5.0
|
||
|
||
def test_trim_segments_empty_list(self):
|
||
"""空segments列表 + 无单段 → 空."""
|
||
config = {"trim_segments": []}
|
||
assert parse_segments_from_config(config) == []
|
||
|
||
def test_trim_segments_not_list(self):
|
||
"""segments不是list → 回退到单段(如果有)."""
|
||
config = {"trim_segments": "not_a_list"}
|
||
assert parse_segments_from_config(config) == []
|
||
|
||
def test_single_trim_start(self):
|
||
"""单段:trim_start."""
|
||
config = {"trim_start": 2, "trim_duration": 5}
|
||
result = parse_segments_from_config(config)
|
||
assert len(result) == 1
|
||
assert result[0].segment_id == "main"
|
||
assert result[0].trim.start_time == 2.0
|
||
assert result[0].trim.duration == 5.0
|
||
|
||
def test_single_trim_end(self):
|
||
"""单段:trim_end."""
|
||
config = {"trim_end": 10}
|
||
result = parse_segments_from_config(config)
|
||
assert len(result) == 1
|
||
assert result[0].trim.end_time == 10.0
|
||
|
||
def test_segments_take_priority_over_single(self):
|
||
"""多段配置优先于单段."""
|
||
config = {
|
||
"trim_segments": [{"segment_id": "s1", "start_time": 0, "duration": 2}],
|
||
"trim_start": 5,
|
||
"trim_duration": 3,
|
||
}
|
||
result = parse_segments_from_config(config)
|
||
assert len(result) == 1
|
||
assert result[0].segment_id == "s1" # 多段优先
|
||
|
||
def test_segments_filter_non_dict(self):
|
||
"""过滤非dict元素."""
|
||
config = {
|
||
"trim_segments": [
|
||
{"segment_id": "s1", "duration": 2},
|
||
"not_a_dict",
|
||
None,
|
||
123,
|
||
]
|
||
}
|
||
result = parse_segments_from_config(config)
|
||
assert len(result) == 1
|
||
assert result[0].segment_id == "s1"
|
||
|
||
|
||
# ── extract_trim_from_clip_config 测试 ──────────────────────────────────────
|
||
|
||
|
||
class TestExtractTrimFromClipConfig:
|
||
"""extract_trim_from_clip_config 测试."""
|
||
|
||
def test_none_config(self):
|
||
"""None返回None."""
|
||
assert extract_trim_from_clip_config(None) is None
|
||
|
||
def test_empty_config(self):
|
||
"""空dict返回None."""
|
||
assert extract_trim_from_clip_config({}) is None
|
||
|
||
def test_trim_subdict(self):
|
||
"""trim子字典提取."""
|
||
config = {"trim": {"start_time": 2, "duration": 5}}
|
||
result = extract_trim_from_clip_config(config)
|
||
assert result is not None
|
||
assert result.start_time == 2.0
|
||
assert result.duration == 5.0
|
||
|
||
def test_trim_subdict_empty(self):
|
||
"""trim子字典为空 → None."""
|
||
config = {"trim": {}}
|
||
assert extract_trim_from_clip_config(config) is None
|
||
|
||
def test_flat_trim_fields(self):
|
||
"""扁平trim_字段."""
|
||
config = {"trim_start": 1, "trim_end": 6}
|
||
result = extract_trim_from_clip_config(config)
|
||
assert result is not None
|
||
assert result.start_time == 1.0
|
||
assert result.end_time == 6.0
|
||
|
||
def test_flat_trim_duration(self):
|
||
"""扁平trim_duration."""
|
||
config = {"trim_duration": 10}
|
||
result = extract_trim_from_clip_config(config)
|
||
assert result is not None
|
||
assert result.duration == 10.0
|
||
|
||
def test_trim_subdict_priority(self):
|
||
"""trim子字典优先于扁平字段."""
|
||
config = {
|
||
"trim": {"start_time": 1, "duration": 2},
|
||
"trim_start": 10,
|
||
"trim_duration": 20,
|
||
}
|
||
result = extract_trim_from_clip_config(config)
|
||
assert result is not None
|
||
assert result.start_time == 1.0
|
||
assert result.duration == 2.0
|
||
|
||
def test_trim_not_dict_ignored(self):
|
||
"""trim不是dict时忽略(回退到扁平字段)."""
|
||
config = {"trim": "not_a_dict", "trim_duration": 5}
|
||
result = extract_trim_from_clip_config(config)
|
||
assert result is not None
|
||
assert result.duration == 5.0
|
||
|
||
def test_no_trim_fields(self):
|
||
"""无裁剪字段返回None."""
|
||
config = {"other_field": "value", "font_size": 12}
|
||
assert extract_trim_from_clip_config(config) is None
|