Files
ViGent2/Docs/DevLogs/Day22.md
Kevin Wong 3129d45b25 更新
2026-02-09 14:47:19 +08:00

11 KiB
Raw Permalink Blame History

🔧 多素材生成优化与健壮性加固 (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!.pathm?.path + .filter(Boolean),防止素材被删后 crash
  • 文件: frontend/src/features/home/model/useHomeController.ts

2. 生成按钮展示后端进度消息

  • 新增 message prop生成中显示如"(正在处理片段 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. 拖拽排序修复

  • 移除 DragOverlaybackdrop-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-64max-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 APItemperature=0.3prompt 要求只返回译文、保持语气风格
  • 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 修改 handleTranslateoriginalTexthandleRestoreOriginal

五、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 成功后:根据目标语言设置 textLangEdgeTTS 模式下自动切换 voice 为目标语言的默认声音
  • handleRestoreOriginal 还原时:重置 textLang"zh-CN",恢复中文默认声音
  • VoiceSelector 根据 textLang 动态显示对应语言的声音列表
3. 声音克隆语言透传
  • 前端:新增 LOCALE_TO_QWEN_LANG 映射(zh-CN→"Chinese", en-US→"English", 其他→"Auto"
  • 生成请求 payload 加入 language 字段(仅声音克隆模式)
  • 后端 GenerateRequest schema 新增 language: str = "Chinese" 字段
  • workflow.pylanguage="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 替代硬编码