53fb25efcf
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (push) Successful in 19s
CI/CD Pipeline / Build Staging API Image (push) Successful in 41s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 48s
CI/CD Pipeline / Integration Tests (push) Successful in 3m10s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m17s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m30s
CI/CD Pipeline / Validate - Style (push) Successful in 4m17s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 59s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 5m33s
CI/CD Pipeline / Validate - Security (push) Successful in 7m12s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m38s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m13s
CI/CD Pipeline / Unit Tests (push) Successful in 10m11s
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Failing after 26h14m3s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 26h24m21s
CI/CD Pipeline / Retag skipped Staging API Image (push) Failing after 26h19m47s
CI/CD Pipeline / PR Build Web Image (push) Failing after 26h23m44s
CI/CD Pipeline / PR Build API Image (push) Failing after 26h23m44s
CI/CD Pipeline / Deploy Production (push) Failing after 26h13m23s
CI/CD Pipeline / Build Production Web Image (push) Failing after 26h13m26s
CI/CD Pipeline / CI Gate (push) Failing after 26h13m25s
CI/CD Pipeline / Build Production API Image (push) Failing after 26h13m26s
CI/CD Pipeline / Canary Release to Production (push) Failing after 26h13m23s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Failing after 26h19m46s
CI/CD Pipeline / Frontend Lint (push) Failing after 26h23m37s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 26h23m45s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Failing after 26h19m46s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
77 lines
3.0 KiB
Python
77 lines
3.0 KiB
Python
"""批量变体配音严格守卫与解析(#1749 问题 A)。
|
||
|
||
事故:批量独立配音视频3 错绑视频1 配音——根因是 voice_library_ids
|
||
缺值/长度不符时静默 fallback 到 voice_library_id(单值),导致多个变体
|
||
共用同一条配音。
|
||
|
||
定稿规则:
|
||
- 独立配音(voice_library_ids 非空):长度必须 == count,逐项非空,
|
||
否则 400;**禁止静默 fallback** 到单值 voice_library_id;
|
||
- 统一配音(voice_library_id 非空、voice_library_ids 空):所有变体共用;
|
||
- 两者都空:返回 [""] * count(无配音,渲染走原 BGM/原声路径)。
|
||
|
||
纯函数,不碰 DB;调用方(路由)负责把 VariantVoiceError 转 400。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Optional
|
||
|
||
|
||
class VariantVoiceError(ValueError):
|
||
"""配音参数错误(路由层捕获后返回 400)。"""
|
||
|
||
|
||
def resolve_variant_voice_ids(
|
||
*,
|
||
count: int,
|
||
voice_library_id: Optional[str] = None,
|
||
voice_library_ids: Optional[list[str]] = None,
|
||
) -> list[str]:
|
||
"""解析每个变体使用的配音 voice_library_id。
|
||
|
||
Args:
|
||
count: 变体数量(必须 >= 1)。
|
||
voice_library_id: 统一配音 ID(所有变体共用)。
|
||
voice_library_ids: 独立配音 ID 列表,长度必须 == count。
|
||
|
||
Returns:
|
||
长度 == count 的列表;元素为 "" 表示该变体无配音。
|
||
|
||
Raises:
|
||
VariantVoiceError: count 非法 / 独立配音长度不符 / 存在缺值。
|
||
"""
|
||
if not count or count < 1:
|
||
raise VariantVoiceError("变体数量 count 必须 >= 1")
|
||
|
||
single = (voice_library_id or "").strip()
|
||
# 归一化:None / 空列表 / 全空白 → 未提供独立配音(走统一配音或无配音)
|
||
raw_multi = [str(v or "").strip() for v in (voice_library_ids or [])]
|
||
multi = [v for v in raw_multi if v]
|
||
|
||
if multi:
|
||
# 显式传了独立配音列表:
|
||
# - 恰好 1 个非空且 count > 1:视为统一配音(与 cover_urls/titles 长度1=共用一致);
|
||
# - 否则按独立配音严格校验:原始长度必须 == count 且逐项非空,禁止静默 fallback;
|
||
# - 其余长度/缺值 → 400。
|
||
if len(raw_multi) == 1 and count > 1:
|
||
return [multi[0]] * count
|
||
if len(raw_multi) != count:
|
||
raise VariantVoiceError(
|
||
f"独立配音数量({len(raw_multi)})与视频数量({count})不一致;"
|
||
f"批量独立配音必须为每个视频分别指定配音,或改用统一配音 voice_library_id"
|
||
)
|
||
missing = [i for i, v in enumerate(raw_multi) if not v]
|
||
if missing:
|
||
raise VariantVoiceError(
|
||
f"第 {[i + 1 for i in missing]} 个视频缺少独立配音 voice_library_id;"
|
||
f"独立配音不允许缺值,也不会回退为统一配音"
|
||
)
|
||
return list(raw_multi)
|
||
|
||
if single:
|
||
return [single] * count
|
||
|
||
# 无配音
|
||
return [""] * count
|