e83a0b86f8
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 9s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 35s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 38s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 58s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 55s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m5s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m18s
CI/CD Pipeline / Validate - Style (push) Successful in 2m31s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m30s
CI/CD Pipeline / Integration Tests (push) Successful in 2m35s
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m7s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m15s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m30s
CI/CD Pipeline / Validate - Security (push) Successful in 4m34s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m37s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m54s
AI Code Review / AI Code Review (pull_request) Successful in 6m19s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m57s
CI/CD Pipeline / Unit Tests (push) Successful in 8m33s
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 / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Failing after 4m48s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
138 lines
4.9 KiB
Python
138 lines
4.9 KiB
Python
"""#1749 问题 C:跨视频素材级去重 + target_durations 测试。"""
|
||
|
||
import random
|
||
|
||
import pytest
|
||
|
||
from packages.domain.variant_plan_selector import (
|
||
BATCH_CLIP_OVERLAP_LIMIT,
|
||
_clip_overlap_ratio,
|
||
reselect_clips_for_variant,
|
||
)
|
||
|
||
|
||
def _src_clips(n=3, dur=5.0):
|
||
return [
|
||
{
|
||
"order": i,
|
||
"asset_id": f"src{i}",
|
||
"start_time": 0.0,
|
||
"duration": dur,
|
||
"clip_type": "main",
|
||
"transition_effect": "cut",
|
||
"transition_duration": 0.0,
|
||
}
|
||
for i in range(n)
|
||
]
|
||
|
||
|
||
def test_three_variants_three_clips_eleven_assets_no_cross_reuse():
|
||
"""3 变体 × 3 片段 / 11 素材:fresh 优先 → 跨变体零重复。"""
|
||
pool = [f"a{i}" for i in range(11)]
|
||
durs = {a: 30.0 for a in pool}
|
||
batch: dict = {}
|
||
used_per_variant = []
|
||
for v in range(3):
|
||
clips = reselect_clips_for_variant(
|
||
_src_clips(3), pool, asset_durations=durs, batch_segments=batch, rng=random.Random(100 + v)
|
||
)
|
||
assert len(clips) == 3
|
||
used_per_variant.append({c["asset_id"] for c in clips})
|
||
# 两两交集为空(9 个素材位置,11 素材足够 fresh 分配)
|
||
assert used_per_variant[0] & used_per_variant[1] == set()
|
||
assert used_per_variant[0] & used_per_variant[2] == set()
|
||
assert used_per_variant[1] & used_per_variant[2] == set()
|
||
|
||
|
||
def test_reuse_overlap_under_limit_and_no_full_overlap():
|
||
"""素材池不足被迫复用时:重叠 ≤20% 且不得完全重叠。"""
|
||
# 2 个长素材、3 变体 × 3 片段 → 必然复用
|
||
pool = ["x", "y"]
|
||
durs = {"x": 60.0, "y": 60.0}
|
||
batch: dict = {}
|
||
for v in range(3):
|
||
reselect_clips_for_variant(
|
||
_src_clips(3, 5.0), pool, asset_durations=durs, batch_segments=batch, rng=random.Random(7 + v)
|
||
)
|
||
# 校验 batch_segments 中同素材任意两区间重叠占比
|
||
for asset, segs in batch.items():
|
||
for i in range(len(segs)):
|
||
for j in range(i + 1, len(segs)):
|
||
s1, e1 = segs[i]
|
||
s2, e2 = segs[j]
|
||
ov = max(0.0, min(e1, e2) - max(s1, s2))
|
||
seg_dur = min(e1 - s1, e2 - s2)
|
||
ratio = ov / seg_dur if seg_dur > 0 else 0.0
|
||
assert ratio <= BATCH_CLIP_OVERLAP_LIMIT + 0.01, f"{asset} overlap {ratio}"
|
||
# 不得完全重叠
|
||
assert not (abs(s1 - s2) < 0.01 and abs(e1 - e2) < 0.01), f"{asset} 完全重叠"
|
||
|
||
|
||
def test_short_asset_cannot_be_reused_across_variants():
|
||
"""短素材(时长 < 段长 80%)数学上无法错开 → 禁跨变体复用。"""
|
||
pool = ["short", "long1", "long2"]
|
||
durs = {"short": 6.2, "long1": 40.0, "long2": 40.0}
|
||
batch = {"short": [(0.0, 6.2)]} # 短素材已被变体0使用
|
||
clips = reselect_clips_for_variant(
|
||
_src_clips(1, 10.0), pool, asset_durations=durs, batch_segments=batch, rng=random.Random(1)
|
||
)
|
||
assert clips[0]["asset_id"] != "short"
|
||
|
||
|
||
def test_target_durations_applied_to_clips():
|
||
"""target_durations 落库到片段 duration。"""
|
||
pool = ["a", "b", "c"]
|
||
durs = {"a": 30.0, "b": 30.0, "c": 30.0}
|
||
clips = reselect_clips_for_variant(
|
||
_src_clips(3, 5.0),
|
||
pool,
|
||
asset_durations=durs,
|
||
batch_segments={},
|
||
target_durations=[7.333, 7.333, 7.334],
|
||
rng=random.Random(3),
|
||
)
|
||
durs_out = sorted(c["duration"] for c in clips)
|
||
assert durs_out == pytest.approx([7.333, 7.333, 7.334], abs=0.01)
|
||
|
||
|
||
def test_short_asset_freeze_start_zero():
|
||
"""素材短于目标段长:起点为 0(末帧冻结由渲染侧铺满),不报错。"""
|
||
pool = ["short6s"]
|
||
durs = {"short6s": 6.0}
|
||
clips = reselect_clips_for_variant(
|
||
_src_clips(1, 10.0),
|
||
pool,
|
||
asset_durations=durs,
|
||
batch_segments={},
|
||
target_durations=[10.0],
|
||
rng=random.Random(5),
|
||
)
|
||
assert clips[0]["asset_id"] == "short6s"
|
||
assert clips[0]["start_time"] == 0.0
|
||
assert clips[0]["duration"] == pytest.approx(10.0)
|
||
|
||
|
||
def test_empty_pool_raises():
|
||
with pytest.raises(ValueError):
|
||
reselect_clips_for_variant(_src_clips(2), [], asset_durations={}, rng=random.Random(1))
|
||
|
||
|
||
def test_empty_source_raises():
|
||
with pytest.raises(ValueError):
|
||
reselect_clips_for_variant([], ["a"], asset_durations={"a": 10.0}, rng=random.Random(1))
|
||
|
||
|
||
def test_zero_duration_assets_raises():
|
||
with pytest.raises(ValueError):
|
||
reselect_clips_for_variant(
|
||
_src_clips(2), ["a", "b"], asset_durations={"a": 0.0, "b": 0.0}, rng=random.Random(1)
|
||
)
|
||
|
||
|
||
def test_overlap_ratio_helper():
|
||
batch = {"a": [(0.0, 5.0)]}
|
||
assert _clip_overlap_ratio("a", 0.0, 5.0, batch) == pytest.approx(1.0)
|
||
assert _clip_overlap_ratio("a", 5.0, 5.0, batch) == pytest.approx(0.0)
|
||
assert _clip_overlap_ratio("a", 4.0, 5.0, batch) == pytest.approx(0.2)
|
||
assert _clip_overlap_ratio("b", 0.0, 5.0, batch) == 0.0
|