e83a0b86f8
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Check push changed paths (push) Successful in 9s
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 / Production Browser E2E (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
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m15s
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 / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Failing after 4m48s
CI/CD Pipeline / Canary Release to Production (push) Failing after 221h53m0s
CI/CD Pipeline / CI Gate (push) Failing after 221h53m4s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 221h53m4s
CI/CD Pipeline / Build Production API Image (push) Failing after 221h53m6s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Failing after 221h58m26s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Failing after 221h59m0s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Failing after 221h59m0s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 222h0m54s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 222h0m57s
CI/CD Pipeline / Retag skipped Staging API Image (push) Failing after 221h59m0s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 222h0m57s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 222h0m55s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 222h1m13s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 222h1m17s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 222h0m55s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 222h1m38s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 222h1m38s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 222h1m23s
CI/CD Pipeline / Validate - Security (pull_request) Failing after 222h1m38s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 222h1m28s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 222h1m39s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 222h1m33s
CI/CD Pipeline / Frontend Lint (push) Failing after 222h1m41s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 222h1m37s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 222h1m42s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 222h1m43s
CI/CD Pipeline / PR Build API Image (push) Failing after 222h1m43s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 222h1m42s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 222h1m44s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 222h1m42s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 222h1m45s
CI/CD Pipeline / Deploy Production (push) Failing after 222h27m37s
CI/CD Pipeline / Build Production Web Image (push) Failing after 222h27m42s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 222h35m33s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 222h35m50s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 222h36m14s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Failing after 222h36m15s
CI/CD Pipeline / PR Build Web Image (push) Failing after 222h36m20s
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 List, 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
|