Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cbd8d9df5c |
Executable
+612
@@ -0,0 +1,612 @@
|
||||
"""trim_config 单元测试 - wave165
|
||||
|
||||
覆盖:
|
||||
- TrimConfig.from_dict 构造
|
||||
- TrimConfig.validate_and_resolve 三选二推导 + 边界钳制
|
||||
- TrimConfig.is_valid / is_noop / trim_from_start 属性
|
||||
- TrimSegment.from_dict 构造
|
||||
- build_video_trim_filter 视频裁剪滤镜
|
||||
- build_audio_trim_filter 音频裁剪滤镜
|
||||
- resolve_segments 多段解析
|
||||
- parse_segments_from_config 配置解析
|
||||
- extract_trim_from_clip_config 提取工具
|
||||
"""
|
||||
|
||||
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:
|
||||
def test_none_returns_none(self):
|
||||
assert TrimConfig.from_dict(None) is None
|
||||
|
||||
def test_empty_dict_returns_none(self):
|
||||
assert TrimConfig.from_dict({}) is None
|
||||
|
||||
def test_all_zero_returns_none(self):
|
||||
assert TrimConfig.from_dict({"start_time": 0, "end_time": 0, "duration": 0}) is None
|
||||
|
||||
def test_start_only_returns_config(self):
|
||||
result = TrimConfig.from_dict({"start_time": 5.0})
|
||||
assert result is not None
|
||||
assert result.start_time == 5.0
|
||||
assert result.end_time == 0
|
||||
assert result.duration == 0
|
||||
|
||||
def test_duration_only_returns_config(self):
|
||||
result = TrimConfig.from_dict({"duration": 10.0})
|
||||
assert result is not None
|
||||
assert result.duration == 10.0
|
||||
assert result.start_time == 0
|
||||
|
||||
def test_start_and_end(self):
|
||||
result = TrimConfig.from_dict({"start_time": 5.0, "end_time": 15.0})
|
||||
assert result is not None
|
||||
assert result.start_time == 5.0
|
||||
assert result.end_time == 15.0
|
||||
|
||||
def test_start_and_duration(self):
|
||||
result = TrimConfig.from_dict({"start_time": 2.0, "duration": 8.0})
|
||||
assert result is not None
|
||||
assert result.start_time == 2.0
|
||||
assert result.duration == 8.0
|
||||
|
||||
def test_end_and_duration(self):
|
||||
result = TrimConfig.from_dict({"end_time": 20.0, "duration": 5.0})
|
||||
assert result is not None
|
||||
assert result.end_time == 20.0
|
||||
assert result.duration == 5.0
|
||||
|
||||
def test_all_three(self):
|
||||
result = TrimConfig.from_dict({"start_time": 1, "end_time": 5, "duration": 4})
|
||||
assert result is not None
|
||||
assert result.start_time == 1.0
|
||||
|
||||
def test_string_values(self):
|
||||
result = TrimConfig.from_dict({"start_time": "3.5", "duration": "2.0"})
|
||||
assert result is not None
|
||||
assert result.start_time == 3.5
|
||||
assert result.duration == 2.0
|
||||
|
||||
def test_falsy_values_treated_as_zero(self):
|
||||
result = TrimConfig.from_dict({"start_time": None, "duration": None})
|
||||
assert result is None # 两个都是None等价于0
|
||||
|
||||
def test_zero_start_with_duration(self):
|
||||
result = TrimConfig.from_dict({"start_time": 0, "duration": 5.0})
|
||||
assert result is not None
|
||||
assert result.duration == 5.0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# validate_and_resolve - 三选二推导
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestValidateAndResolveDerive:
|
||||
ASSET_DUR = 60.0 # 素材时长60秒
|
||||
|
||||
def test_start_plus_end(self):
|
||||
config = TrimConfig(start_time=10.0, end_time=25.0)
|
||||
result = config.validate_and_resolve(self.ASSET_DUR)
|
||||
assert result.start_time == 10.0
|
||||
assert result.end_time == 25.0
|
||||
assert result.duration == 15.0
|
||||
|
||||
def test_start_plus_duration(self):
|
||||
config = TrimConfig(start_time=5.0, duration=10.0)
|
||||
result = config.validate_and_resolve(self.ASSET_DUR)
|
||||
assert result.start_time == 5.0
|
||||
assert result.end_time == 15.0
|
||||
assert result.duration == 10.0
|
||||
|
||||
def test_end_plus_duration(self):
|
||||
config = TrimConfig(end_time=30.0, duration=10.0)
|
||||
result = config.validate_and_resolve(self.ASSET_DUR)
|
||||
assert result.start_time == 20.0
|
||||
assert result.end_time == 30.0
|
||||
assert result.duration == 10.0
|
||||
|
||||
def test_end_plus_duration_start_negative_clamped(self):
|
||||
# end=5, duration=10 → start=-5 → 钳制到0,duration=5
|
||||
config = TrimConfig(end_time=5.0, duration=10.0)
|
||||
result = config.validate_and_resolve(self.ASSET_DUR)
|
||||
assert result.start_time == 0.0
|
||||
assert result.end_time == 5.0
|
||||
assert result.duration == 5.0
|
||||
|
||||
def test_start_only_takes_to_end(self):
|
||||
config = TrimConfig(start_time=50.0)
|
||||
result = config.validate_and_resolve(self.ASSET_DUR)
|
||||
assert result.start_time == 50.0
|
||||
assert result.end_time == 60.0
|
||||
assert result.duration == 10.0
|
||||
|
||||
def test_end_only_starts_from_zero(self):
|
||||
config = TrimConfig(end_time=30.0)
|
||||
result = config.validate_and_resolve(self.ASSET_DUR)
|
||||
assert result.start_time == 0.0
|
||||
assert result.end_time == 30.0
|
||||
assert result.duration == 30.0
|
||||
|
||||
def test_duration_only_starts_from_zero(self):
|
||||
config = TrimConfig(duration=20.0)
|
||||
result = config.validate_and_resolve(self.ASSET_DUR)
|
||||
assert result.start_time == 0.0
|
||||
assert result.end_time == 20.0
|
||||
assert result.duration == 20.0
|
||||
|
||||
def test_all_three_uses_start_end(self):
|
||||
# start=5, end=20, duration=10 → 优先用 start+end 推导 → duration=15
|
||||
config = TrimConfig(start_time=5.0, end_time=20.0, duration=10.0)
|
||||
result = config.validate_and_resolve(self.ASSET_DUR)
|
||||
assert result.start_time == 5.0
|
||||
assert result.end_time == 20.0
|
||||
assert result.duration == 15.0 # start+end 优先
|
||||
|
||||
def test_start_equals_end_invalid(self):
|
||||
config = TrimConfig(start_time=10.0, end_time=10.0)
|
||||
result = config.validate_and_resolve(self.ASSET_DUR)
|
||||
assert result.duration == 0.0
|
||||
assert result.is_valid is False
|
||||
|
||||
|
||||
# ============================================================
|
||||
# validate_and_resolve - 边界钳制
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestValidateAndResolveClamp:
|
||||
ASSET_DUR = 60.0
|
||||
|
||||
def test_negative_start_clamped(self):
|
||||
config = TrimConfig(start_time=-5.0, duration=10.0)
|
||||
result = config.validate_and_resolve(self.ASSET_DUR)
|
||||
assert result.start_time == 0.0
|
||||
assert result.duration == 10.0
|
||||
|
||||
def test_end_exceeds_asset_duration(self):
|
||||
config = TrimConfig(start_time=50.0, duration=20.0)
|
||||
result = config.validate_and_resolve(self.ASSET_DUR)
|
||||
assert result.end_time == 60.0
|
||||
assert result.duration == 10.0
|
||||
|
||||
def test_start_exceeds_asset_duration(self):
|
||||
config = TrimConfig(start_time=70.0, duration=5.0)
|
||||
result = config.validate_and_resolve(self.ASSET_DUR)
|
||||
assert result.start_time < self.ASSET_DUR
|
||||
assert result.end_time == self.ASSET_DUR
|
||||
|
||||
def test_zero_asset_duration_returns_noop(self):
|
||||
config = TrimConfig(start_time=5.0, duration=10.0)
|
||||
result = config.validate_and_resolve(0.0)
|
||||
assert result.is_noop is True
|
||||
|
||||
def test_negative_asset_duration_returns_noop(self):
|
||||
config = TrimConfig(start_time=5.0, duration=10.0)
|
||||
result = config.validate_and_resolve(-1.0)
|
||||
assert result.is_noop is True
|
||||
|
||||
def test_no_params_returns_noop(self):
|
||||
config = TrimConfig()
|
||||
result = config.validate_and_resolve(self.ASSET_DUR)
|
||||
assert result.is_noop is True
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 属性 is_valid / is_noop / trim_from_start
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestTrimConfigProperties:
|
||||
def test_is_valid_true(self):
|
||||
config = TrimConfig(start_time=0, end_time=0, duration=5.0)
|
||||
assert config.is_valid is True
|
||||
|
||||
def test_is_valid_false_zero(self):
|
||||
config = TrimConfig(duration=0.0)
|
||||
assert config.is_valid is False
|
||||
|
||||
def test_is_valid_false_negative(self):
|
||||
config = TrimConfig(duration=-1.0)
|
||||
assert config.is_valid is False
|
||||
|
||||
def test_is_valid_exactly_minimum(self):
|
||||
config = TrimConfig(duration=MIN_TRIM_DURATION)
|
||||
assert config.is_valid is True
|
||||
|
||||
def test_is_valid_below_minimum(self):
|
||||
config = TrimConfig(duration=MIN_TRIM_DURATION / 2)
|
||||
assert config.is_valid is False
|
||||
|
||||
def test_is_noop_true(self):
|
||||
config = TrimConfig()
|
||||
assert config.is_noop is True
|
||||
|
||||
def test_is_noop_false_with_start(self):
|
||||
config = TrimConfig(start_time=5.0)
|
||||
assert config.is_noop is False
|
||||
|
||||
def test_is_noop_false_with_duration(self):
|
||||
config = TrimConfig(duration=10.0)
|
||||
assert config.is_noop is False
|
||||
|
||||
def test_trim_from_start_true(self):
|
||||
config = TrimConfig(start_time=0.0, duration=10.0)
|
||||
assert config.trim_from_start is True
|
||||
|
||||
def test_trim_from_start_false(self):
|
||||
config = TrimConfig(start_time=5.0, duration=10.0)
|
||||
assert config.trim_from_start is False
|
||||
|
||||
def test_trim_from_start_negative_treated_as_start(self):
|
||||
# trim_from_start 检查 start_time <= 0,负值也算从开头
|
||||
config = TrimConfig(start_time=-1.0)
|
||||
assert config.trim_from_start is True
|
||||
|
||||
|
||||
# ============================================================
|
||||
# TrimSegment
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestTrimSegment:
|
||||
def test_from_dict_minimal(self):
|
||||
seg = TrimSegment.from_dict({"start_time": 5.0, "duration": 10.0})
|
||||
assert seg.segment_id # 有默认值
|
||||
assert seg.trim.start_time == 5.0
|
||||
assert seg.trim.duration == 10.0
|
||||
assert seg.order == 0
|
||||
|
||||
def test_from_dict_with_segment_id(self):
|
||||
seg = TrimSegment.from_dict({"segment_id": "seg_abc", "start_time": 1.0, "duration": 2.0})
|
||||
assert seg.segment_id == "seg_abc"
|
||||
|
||||
def test_from_dict_with_order(self):
|
||||
seg = TrimSegment.from_dict({"order": 3, "start_time": 1.0, "duration": 2.0})
|
||||
assert seg.order == 3
|
||||
|
||||
def test_from_dict_default_order(self):
|
||||
seg = TrimSegment.from_dict({"start_time": 1.0}, default_order=5)
|
||||
assert seg.order == 5
|
||||
|
||||
def test_segment_dataclass(self):
|
||||
seg = TrimSegment(
|
||||
segment_id="s1",
|
||||
trim=TrimConfig(start_time=0, duration=5),
|
||||
order=1,
|
||||
)
|
||||
assert seg.segment_id == "s1"
|
||||
assert seg.order == 1
|
||||
assert seg.trim.duration == 5.0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# build_video_trim_filter
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestBuildVideoTrimFilter:
|
||||
def test_noop_passthrough(self):
|
||||
trim = TrimConfig()
|
||||
result = build_video_trim_filter("[0:v]", trim, "[out]")
|
||||
assert result == "[0:v]setpts=PTS-STARTPTS[out]"
|
||||
|
||||
def test_with_start_and_duration(self):
|
||||
trim = TrimConfig(start_time=5.5, duration=10.0).validate_and_resolve(60.0)
|
||||
result = build_video_trim_filter("[0:v]", trim, "[v0]")
|
||||
assert "trim=" in result
|
||||
assert "start=5.500" in result
|
||||
assert "duration=10.000" in result
|
||||
assert "setpts=PTS-STARTPTS" in result
|
||||
assert result.startswith("[0:v]")
|
||||
assert result.endswith("[v0]")
|
||||
|
||||
def test_start_at_zero(self):
|
||||
trim = TrimConfig(duration=5.0).validate_and_resolve(60.0)
|
||||
result = build_video_trim_filter("[in]", trim, "[out]")
|
||||
assert "trim=" in result
|
||||
assert "start=" not in result # start=0 不写
|
||||
assert "duration=5.000" in result
|
||||
|
||||
def test_trim_only_start(self):
|
||||
# 只有 start,duration 由素材推导
|
||||
trim = TrimConfig(start_time=10.0).validate_and_resolve(30.0)
|
||||
result = build_video_trim_filter("[0:v]", trim, "[out]")
|
||||
assert "start=10.000" in result
|
||||
assert "duration=20.000" in result
|
||||
|
||||
def test_comma_separated_filters(self):
|
||||
trim = TrimConfig(start_time=1.0, duration=2.0).validate_and_resolve(10.0)
|
||||
result = build_video_trim_filter("[in]", trim, "[out]")
|
||||
assert "trim=" in result
|
||||
# trim 和 setpts 用逗号分隔
|
||||
assert "setpts=PTS-STARTPTS" in result
|
||||
|
||||
|
||||
# ============================================================
|
||||
# build_audio_trim_filter
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestBuildAudioTrimFilter:
|
||||
def test_noop_passthrough(self):
|
||||
trim = TrimConfig()
|
||||
result = build_audio_trim_filter("[0:a]", trim, "[out]")
|
||||
assert result == "[0:a]asetpts=PTS-STARTPTS[out]"
|
||||
|
||||
def test_with_start_and_duration(self):
|
||||
trim = TrimConfig(start_time=3.0, duration=7.0).validate_and_resolve(60.0)
|
||||
result = build_audio_trim_filter("[0:a]", trim, "[a0]")
|
||||
assert "atrim=" in result
|
||||
assert "start=3.000" in result
|
||||
assert "duration=7.000" in result
|
||||
assert "asetpts=PTS-STARTPTS" in result
|
||||
assert result.startswith("[0:a]")
|
||||
assert result.endswith("[a0]")
|
||||
|
||||
def test_start_at_zero(self):
|
||||
trim = TrimConfig(duration=5.0).validate_and_resolve(60.0)
|
||||
result = build_audio_trim_filter("[in]", trim, "[out]")
|
||||
assert "atrim=" in result
|
||||
assert "start=" not in result
|
||||
assert "duration=5.000" in result
|
||||
|
||||
def test_uses_atrim_not_trim(self):
|
||||
trim = TrimConfig(start_time=1.0, duration=2.0).validate_and_resolve(10.0)
|
||||
result = build_audio_trim_filter("[in]", trim, "[out]")
|
||||
assert "atrim=" in result
|
||||
# 不应该有单独的 trim=(即视频的 trim)
|
||||
# 注意:atrim= 包含 "trim=" 子串,所以检查完整的
|
||||
|
||||
|
||||
# ============================================================
|
||||
# resolve_segments
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestResolveSegments:
|
||||
def test_empty_list(self):
|
||||
result = resolve_segments([], 60.0)
|
||||
assert result == []
|
||||
|
||||
def test_single_segment(self):
|
||||
segs = [
|
||||
TrimSegment(
|
||||
segment_id="s1",
|
||||
trim=TrimConfig(start_time=0, duration=10.0),
|
||||
order=0,
|
||||
)
|
||||
]
|
||||
result = resolve_segments(segs, 60.0)
|
||||
assert len(result) == 1
|
||||
assert result[0].segment_id == "s1"
|
||||
assert result[0].trim.duration == 10.0
|
||||
|
||||
def test_invalid_segment_skipped(self):
|
||||
segs = [
|
||||
TrimSegment(
|
||||
segment_id="good",
|
||||
trim=TrimConfig(start_time=0, duration=10.0),
|
||||
order=0,
|
||||
),
|
||||
TrimSegment(
|
||||
segment_id="bad",
|
||||
trim=TrimConfig(start_time=10, end_time=10), # 0时长
|
||||
order=1,
|
||||
),
|
||||
]
|
||||
result = resolve_segments(segs, 60.0)
|
||||
assert len(result) == 1
|
||||
assert result[0].segment_id == "good"
|
||||
|
||||
def test_sorted_by_order(self):
|
||||
segs = [
|
||||
TrimSegment(
|
||||
segment_id="third",
|
||||
trim=TrimConfig(duration=1.0),
|
||||
order=2,
|
||||
),
|
||||
TrimSegment(
|
||||
segment_id="first",
|
||||
trim=TrimConfig(duration=1.0),
|
||||
order=0,
|
||||
),
|
||||
TrimSegment(
|
||||
segment_id="second",
|
||||
trim=TrimConfig(duration=1.0),
|
||||
order=1,
|
||||
),
|
||||
]
|
||||
result = resolve_segments(segs, 60.0)
|
||||
assert len(result) == 3
|
||||
assert [s.segment_id for s in result] == ["first", "second", "third"]
|
||||
|
||||
def test_negative_order_uses_index(self):
|
||||
segs = [
|
||||
TrimSegment(
|
||||
segment_id="s0",
|
||||
trim=TrimConfig(duration=1.0),
|
||||
order=-1,
|
||||
),
|
||||
TrimSegment(
|
||||
segment_id="s1",
|
||||
trim=TrimConfig(duration=1.0),
|
||||
order=-1,
|
||||
),
|
||||
]
|
||||
result = resolve_segments(segs, 60.0)
|
||||
assert len(result) == 2
|
||||
# order 为负时使用索引 i,所以 s0 order=0, s1 order=1
|
||||
assert result[0].segment_id == "s0"
|
||||
assert result[1].segment_id == "s1"
|
||||
|
||||
def test_respects_asset_duration(self):
|
||||
segs = [
|
||||
TrimSegment(
|
||||
segment_id="s1",
|
||||
trim=TrimConfig(start_time=50, duration=20.0), # 超出60s素材
|
||||
order=0,
|
||||
)
|
||||
]
|
||||
result = resolve_segments(segs, 60.0)
|
||||
assert len(result) == 1
|
||||
assert result[0].trim.end_time == 60.0
|
||||
assert result[0].trim.duration == 10.0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# parse_segments_from_config
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestParseSegmentsFromConfig:
|
||||
def test_none_returns_empty(self):
|
||||
assert parse_segments_from_config(None) == []
|
||||
|
||||
def test_empty_dict_returns_empty(self):
|
||||
assert parse_segments_from_config({}) == []
|
||||
|
||||
def test_trim_segments_list(self):
|
||||
config = {
|
||||
"trim_segments": [
|
||||
{"segment_id": "s1", "start_time": 0, "duration": 5, "order": 0},
|
||||
{"segment_id": "s2", "start_time": 10, "duration": 5, "order": 1},
|
||||
]
|
||||
}
|
||||
result = parse_segments_from_config(config)
|
||||
assert len(result) == 2
|
||||
assert result[0].segment_id == "s1"
|
||||
assert result[1].segment_id == "s2"
|
||||
|
||||
def test_trim_segments_empty_list(self):
|
||||
config = {"trim_segments": []}
|
||||
result = parse_segments_from_config(config)
|
||||
assert result == []
|
||||
|
||||
def test_trim_segments_not_list_ignored(self):
|
||||
config = {"trim_segments": "not_a_list"}
|
||||
result = parse_segments_from_config(config)
|
||||
assert result == []
|
||||
|
||||
def test_single_trim_start(self):
|
||||
config = {"trim_start": 5.0}
|
||||
result = parse_segments_from_config(config)
|
||||
assert len(result) == 1
|
||||
assert result[0].segment_id == "main"
|
||||
assert result[0].trim.start_time == 5.0
|
||||
|
||||
def test_single_trim_end(self):
|
||||
config = {"trim_end": 30.0}
|
||||
result = parse_segments_from_config(config)
|
||||
assert len(result) == 1
|
||||
assert result[0].trim.end_time == 30.0
|
||||
|
||||
def test_single_trim_duration(self):
|
||||
config = {"trim_duration": 10.0}
|
||||
result = parse_segments_from_config(config)
|
||||
assert len(result) == 1
|
||||
assert result[0].trim.duration == 10.0
|
||||
|
||||
def test_single_full(self):
|
||||
config = {"trim_start": 2.0, "trim_end": 8.0, "trim_duration": 6.0}
|
||||
result = parse_segments_from_config(config)
|
||||
assert len(result) == 1
|
||||
assert result[0].trim.start_time == 2.0
|
||||
|
||||
def test_segments_priority_over_single(self):
|
||||
# 同时有 trim_segments 和单段字段,优先多段
|
||||
config = {
|
||||
"trim_segments": [
|
||||
{"segment_id": "s1", "start_time": 0, "duration": 5},
|
||||
],
|
||||
"trim_start": 10.0,
|
||||
}
|
||||
result = parse_segments_from_config(config)
|
||||
assert len(result) == 1
|
||||
assert result[0].segment_id == "s1"
|
||||
|
||||
def test_segment_dict_with_non_dict_entries_ignored(self):
|
||||
config = {
|
||||
"trim_segments": [
|
||||
{"segment_id": "s1", "start_time": 0, "duration": 5},
|
||||
"not_a_dict",
|
||||
None,
|
||||
]
|
||||
}
|
||||
result = parse_segments_from_config(config)
|
||||
assert len(result) == 1
|
||||
|
||||
|
||||
# ============================================================
|
||||
# extract_trim_from_clip_config
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestExtractTrimFromClipConfig:
|
||||
def test_none_returns_none(self):
|
||||
assert extract_trim_from_clip_config(None) is None
|
||||
|
||||
def test_empty_dict_returns_none(self):
|
||||
assert extract_trim_from_clip_config({}) is None
|
||||
|
||||
def test_trim_subdict(self):
|
||||
config = {"trim": {"start_time": 5.0, "duration": 10.0}}
|
||||
result = extract_trim_from_clip_config(config)
|
||||
assert result is not None
|
||||
assert result.start_time == 5.0
|
||||
assert result.duration == 10.0
|
||||
|
||||
def test_trim_subdict_empty(self):
|
||||
config = {"trim": {}}
|
||||
result = extract_trim_from_clip_config(config)
|
||||
assert result is None
|
||||
|
||||
def test_flat_trim_start(self):
|
||||
config = {"trim_start": 5.0, "trim_duration": 10.0}
|
||||
result = extract_trim_from_clip_config(config)
|
||||
assert result is not None
|
||||
assert result.start_time == 5.0
|
||||
assert result.duration == 10.0
|
||||
|
||||
def test_flat_trim_end_only(self):
|
||||
config = {"trim_end": 20.0}
|
||||
result = extract_trim_from_clip_config(config)
|
||||
assert result is not None
|
||||
assert result.end_time == 20.0
|
||||
|
||||
def test_trim_subdict_priority_over_flat(self):
|
||||
# trim子字典优先
|
||||
config = {
|
||||
"trim": {"start_time": 1.0, "duration": 2.0},
|
||||
"trim_start": 10.0,
|
||||
"trim_duration": 20.0,
|
||||
}
|
||||
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_no_trim_fields(self):
|
||||
config = {"other_field": "value"}
|
||||
assert extract_trim_from_clip_config(config) is None
|
||||
|
||||
def test_trim_not_dict_ignored(self):
|
||||
config = {"trim": "not_a_dict"}
|
||||
# trim 不是 dict,继续看扁平字段 → 没有 → None
|
||||
result = extract_trim_from_clip_config(config)
|
||||
assert result is None
|
||||
Reference in New Issue
Block a user