11 KiB
11 KiB
🔧 多素材生成优化与健壮性加固 (Day 22)
概述
对 Day 21 实现的多素材视频生成(多机位)功能进行全面审查,修复 6 个高优先级 Bug、完成 8 项体验优化,并将多素材流水线从"逐段 LatentSync"重构为"先拼接再推理"架构,推理次数从 N 次降为 1 次。
一、后端高优 Bug 修复
1. _split_equal() 素材数 > 字符数边界溢出
- 问题: 5 个素材但只有 2 个 Whisper 字符时,边界索引重复,部分素材被跳过
- 修复: 加入
n = min(n, len(all_chars))上限保护 - 文件:
backend/app/modules/videos/workflow.py
2. 多素材 LatentSync 单段失败无 fallback
- 问题: 单素材模式下 LatentSync 失败会 fallback 到原始素材,但多素材模式直接抛异常,整个任务失败
- 修复: 多素材循环中加 try-except,失败时 fallback 到原始素材片段
- 文件:
backend/app/modules/videos/workflow.py
3. num_segments == 0 时 ZeroDivisionError
- 问题: 所有 assignments 被跳过后
i / num_segments触发除零 - 修复: 循环前加
if num_segments == 0检查并抛出明确错误 - 文件:
backend/app/modules/videos/workflow.py
4. split_audio 未校验 duration > 0
- 问题:
end <= start时 FFmpeg 行为异常 - 修复: 加入
if duration <= 0: raise ValueError(...) - 文件:
backend/app/services/video_service.py
5. Whisper 失败时按时长均分兜底
- 问题: Whisper 失败后直接退化为单素材,其他素材被浪费
- 修复: 按
audio_duration / len(material_paths)均分,不依赖字符对齐 - 文件:
backend/app/modules/videos/workflow.py
6. concat_videos 空列表未检查
- 问题: 传入空
video_paths时 FFmpeg 报错 - 修复: 加入
if not video_paths: raise ValueError(...) - 文件:
backend/app/services/video_service.py
二、前端优化
1. payload 构建非空断言修复
m!.path→m?.path+.filter(Boolean),防止素材被删后 crash- 文件:
frontend/src/features/home/model/useHomeController.ts
2. 生成按钮展示后端进度消息
- 新增
messageprop,生成中显示如"(正在处理片段 2/3...)" - 文件:
frontend/src/features/home/ui/GenerateActionBar.tsx,HomePage.tsx
3. 新上传素材自动选中
- 上传成功后对比前后素材列表,新增的 ID 自动追加到
selectedMaterials - 文件:
frontend/src/features/home/model/useMaterials.ts
4. Material 接口统一
- 三处
interface Material重复定义提取到shared/types/material.ts - 文件:
frontend/src/shared/types/material.ts(新建),useMaterials.ts,useHomeController.ts,MaterialSelector.tsx
5. 拖拽排序修复
- 移除
DragOverlay(backdrop-blur创建新 containing block 导致定位错乱) - 改为
useSortable原生拖拽 +CSS.Translate,拖拽中元素高亮加阴影 - 文件:
frontend/src/features/home/ui/MaterialSelector.tsx
6. 素材选择上限 4 个
toggleMaterial新增MAX_MATERIALS = 4限制- UI 选满后未选中项变半透明禁用,提示文字改为"可多选,最多4个"
- 文件:
useMaterials.ts,MaterialSelector.tsx
7. 移动端排序区域响应式
- 素材列表
max-h-64→max-h-48 sm:max-h-64 - 文件:
MaterialSelector.tsx
8. 多素材耗时提示
- 选中 ≥2 素材时生成按钮下方显示"多素材模式 (N 个机位),生成耗时较长"
- 文件:
GenerateActionBar.tsx,HomePage.tsx
三、核心架构重构:先拼接再推理
V1 (Day 21): 逐段 LatentSync
素材A → LatentSync(素材A, 音频片段1) → lipsync_A
素材B → LatentSync(素材B, 音频片段2) → lipsync_B
FFmpeg concat(lipsync_A, lipsync_B) → 最终视频
- 缺点:N 个素材 = N 次 LatentSync 推理(每次 ~30s)
V2 (Day 22): 先拼接再推理
素材A → prepare_segment(裁剪到3.67s) → prepared_A
素材B → prepare_segment(裁剪到4.00s) → prepared_B
FFmpeg concat(prepared_A, prepared_B) → concat_video (7.67s)
LatentSync(concat_video, 完整音频) → 最终视频
- 优点:只需 1 次 LatentSync 推理,时间从 N×30s 降为 1×30s
新增 prepare_segment() 方法
def prepare_segment(self, video_path, target_duration, output_path, target_resolution=None):
# 素材时长 > 目标: 裁剪 (-t)
# 素材时长 < 目标: 循环 (-stream_loop) + 裁剪
# 分辨率一致: -c copy 无损 (不重编码)
# 分辨率不一致: scale + pad 统一到第一个素材分辨率
分辨率处理策略
- 新增
get_resolution()方法检测各素材分辨率 - 所有素材分辨率相同时:
-c copy无损裁剪(保持原画质) - 分辨率不一致时:统一到第一个素材的分辨率,
force_original_aspect_ratio=decrease+pad居中 - LatentSync 只处理嘴部 512×512 区域,输出保持原分辨率
时间对齐验证
| 环节 | 时间基准 | 对齐关系 |
|---|---|---|
| TTS 音频 | 原始时长 (7.67s) | 基准 |
| Whisper 字幕 | 基于 TTS 音频 | 时间戳对齐音频 |
| 均分切分 | assignments 总时长 = 音频时长 | 首段 start=0, 末段 end=audio_duration |
| prepare 各段 | -t seg_dur 精确截断 |
总和 ≈ 音频时长 |
| LatentSync | concat_video + 完整音频 | 内部 0.5s 容差 |
| compose | lipsync_video + 音频/BGM | -shortest 保证同步 |
| Remotion | 基于 captions_path 渲染字幕 | 时间戳对齐音频 |
涉及文件汇总
| 文件 | 变更类型 | 说明 |
|---|---|---|
backend/app/modules/videos/workflow.py |
修改 | 6 个 Bug 修复 + 流水线重构(先拼接再推理) |
backend/app/services/video_service.py |
修改 | 新增 prepare_segment()、get_resolution(),split_audio 校验,concat_videos 空列表检查 |
frontend/src/shared/types/material.ts |
新建 | 统一 Material 接口 |
frontend/src/features/home/model/useMaterials.ts |
修改 | 上传自动选中、素材上限 4 个 |
frontend/src/features/home/model/useHomeController.ts |
修改 | payload 非空断言修复、Material 接口引用 |
frontend/src/features/home/ui/MaterialSelector.tsx |
修改 | 拖拽修复、上限 4 个 UI、移动端响应式 |
frontend/src/features/home/ui/GenerateActionBar.tsx |
修改 | 进度消息展示、多素材耗时提示 |
frontend/src/features/home/ui/HomePage.tsx |
修改 | 传递 message、materialCount prop |
四、AI 多语言翻译
功能
在文案编辑区新增「AI多语言」按钮,支持将中文口播文案一键翻译为 9 种语言,并可随时还原原文。
支持语言
英语 English、日语 日本語、韩语 한국어、法语 Français、德语 Deutsch、西班牙语 Español、俄语 Русский、意大利语 Italiano、葡萄牙语 Português
实现
后端
backend/app/services/glm_service.py— 新增translate_text()方法,调用智谱 GLM API(temperature=0.3),prompt 要求只返回译文、保持语气风格backend/app/modules/ai/router.py— 新增POST /api/ai/translate接口,接收{text, target_lang},返回{translated_text}
前端
frontend/src/features/home/ui/ScriptEditor.tsx— 新增LANGUAGES列表(9 种语言)、语言下拉菜单(点击外部自动关闭)、翻译中 loading 状态、「还原原文」按钮(翻译过后出现在菜单顶部)frontend/src/features/home/model/useHomeController.ts— 新增handleTranslate(调用翻译 API、首次翻译保存原文)、originalText状态、handleRestoreOriginal(恢复原文)
涉及文件
| 文件 | 变更 | 说明 |
|---|---|---|
backend/app/services/glm_service.py |
修改 | 新增 translate_text() 方法 |
backend/app/modules/ai/router.py |
修改 | 新增 /api/ai/translate 接口 |
frontend/src/features/home/ui/ScriptEditor.tsx |
修改 | 语言菜单 UI、翻译 loading、还原原文按钮 |
frontend/src/features/home/model/useHomeController.ts |
修改 | handleTranslate、originalText、handleRestoreOriginal |
五、TTS 多语言支持
背景
翻译功能实现后,用户可将中文文案翻译为其他语言。但翻译后生成视频时 TTS 仍只支持中文:
- EdgeTTS:声音列表只有 5 个
zh-CN-*中文声音 - 声音克隆 (Qwen3-TTS):
language参数硬编码为"Chinese"
实现方案
1. 前端:语言感知的声音列表
VOICES从扁平数组扩展为Record<string, VoiceOption[]>,覆盖 10 种语言(zh-CN / en-US / ja-JP / ko-KR / fr-FR / de-DE / es-ES / ru-RU / it-IT / pt-BR),每种语言 2 个声音(男/女)- 新增
LANG_TO_LOCALE映射:翻译目标语言名 → EdgeTTS locale(如"English" → "en-US") - 新增
textLang状态,跟踪当前文案语言,默认"zh-CN"
2. 翻译时自动切换声音
handleTranslate成功后:根据目标语言设置textLang,EdgeTTS 模式下自动切换voice为目标语言的默认声音handleRestoreOriginal还原时:重置textLang为"zh-CN",恢复中文默认声音VoiceSelector根据textLang动态显示对应语言的声音列表
3. 声音克隆语言透传
- 前端:新增
LOCALE_TO_QWEN_LANG映射(zh-CN→"Chinese",en-US→"English", 其他→"Auto") - 生成请求 payload 加入
language字段(仅声音克隆模式) - 后端
GenerateRequestschema 新增language: str = "Chinese"字段 workflow.py:language="Chinese"硬编码改为language=req.language
4. Bug 修复:textLang 持久化
- 问题:
voice已持久化但textLang未持久化,刷新页面后voice恢复为英文声音但textLang默认回中文,导致 VoiceSelector 显示中文声音列表却选中英文声音,无高亮按钮 - 修复: 在
useHomePersistence中加入textLang的 localStorage 读写
数据流
用户翻译 "English"
→ ScriptEditor.onTranslate("English")
→ LANG_TO_LOCALE["English"] = "en-US"
→ setTextLang("en-US"), setVoice("en-US-GuyNeural")
→ VoiceSelector 显示 VOICES["en-US"] = [Guy, Jenny]
→ 生成时:
EdgeTTS: payload.voice = "en-US-GuyNeural"
声音克隆: payload.language = "English" (via getQwenLanguage)
涉及文件
| 文件 | 变更 | 说明 |
|---|---|---|
frontend/src/features/home/model/useHomeController.ts |
修改 | VOICES 多语言 Record、textLang 状态、LANG_TO_LOCALE / LOCALE_TO_QWEN_LANG 映射、翻译自动切换 voice |
frontend/src/features/home/model/useHomePersistence.ts |
修改 | textLang 持久化读写 |
backend/app/modules/videos/schemas.py |
修改 | GenerateRequest 加 language 字段 |
backend/app/modules/videos/workflow.py |
修改 | 声音克隆调用处用 req.language 替代硬编码 |