## 字幕同步修复 + 嘴型参数调优 + 视频流水线全面优化 + 预览背景修复 + CosyVoice 语气控制 (Day 29) ### 概述 本轮对视频生成流水线做全面审查优化:修复字幕与语音不同步问题(Whisper 时间戳平滑 + 原文节奏映射)、调优 LatentSync 嘴型参数、compose 流复制省去冗余重编码、FFmpeg 超时保护、全局并发限制、Redis 任务 TTL、临时文件清理、死代码移除。修复因前端域名迁移导致的样式预览背景 CORS 失效问题。新增 CosyVoice 语气控制功能,声音克隆模式下支持开心/伤心/生气等情绪表达(基于 `inference_instruct2`)。 --- ## ✅ 改动内容 ### 1. 字幕同步修复(Whisper 时间戳 + 原文节奏映射) - **问题**: 字幕高亮与语音不同步,表现为字幕超前/滞后、高亮跳空 - **根因**: Whisper 输出的逐字时间戳存在微小抖动(相邻字 end > 下一字 start),且字间间隙导致高亮"闪烁" #### whisper_service.py — 时间戳后处理 新增 `smooth_word_timestamps()` 函数,三步平滑: 1. **单调递增保证**: 后一字的 start 不早于前一字的 start 2. **重叠消除**: 两字时间重叠时取中点分割 3. **间隙填补**: 字间间隙 < 50ms 时直接连接,避免高亮跳空 ```python def smooth_word_timestamps(words): for i in range(1, len(words)): # 重叠 → 中点分割 if w["start"] < prev["end"]: mid = (prev["end"] + w["start"]) / 2 prev["end"] = mid; w["start"] = mid # 微小间隙 → 直接连接 if 0 < gap < 0.05: prev["end"] = w["start"] ``` #### whisper_service.py — 原文节奏映射 - **问题**: AI 改写/多语言文案与 Whisper 转录文字不一致,直接用 Whisper 文字会乱码 - **方案**: `original_text` 参数非空时,用原文字符替换 Whisper 文字,但保留 Whisper 的语音节奏时间戳 - 实现:将 N 个原文字符按比例映射到 M 个 Whisper 时间戳上(线性插值) - 字数比例异常检测(>1.5x 或 <0.67x 时警告) - 单字时长钳位:40ms ~ 800ms,防止极端漂移 #### captions.ts — Remotion 端字幕查找 新增 `getCurrentSegment()` 和 `getCurrentWordIndex()` 函数: - 根据当前帧时间精确查找应显示的字幕段落和高亮字索引 - 处理字间间隙(两字之间返回前一字索引,保持高亮连续) - 超过最后一字结束时间时返回最后一字(避免末尾闪烁) --- ### 2. LatentSync 嘴型参数调优 | 参数 | Day28 值 | Day29 值 | 说明 | |------|----------|----------|------| | `LATENTSYNC_INFERENCE_STEPS` | 16 | 20 | 适当增加步数提升嘴型质量 | | `LATENTSYNC_GUIDANCE_SCALE` | (默认) | 2.0 | 平衡嘴型贴合度与自然感 | | `LATENTSYNC_ENABLE_DEEPCACHE` | (默认) | true | DeepCache 加速推理 | | `LATENTSYNC_SEED` | (默认) | 1247 | 固定种子保证可复现 | | Remotion concurrency | 16 | 4 | 降低并发防止资源争抢 | --- ### 3. compose() 流复制替代冗余重编码(高优先级) **文件**: `video_service.py` - **问题**: `compose()` 只是合并视频轨+音频轨(mux),却每次用 `libx264 -preset medium -crf 20` 做完整重编码,耗时数分钟。整条流水线一个视频最多被 x264 编码 5 次 - **方案**: 不需要循环时(`loop_count == 1`)用 `-c:v copy` 流复制,几乎瞬间完成;需要循环时仍用 libx264 ```python if loop_count > 1: cmd.extend(["-c:v", "libx264", "-preset", "fast", "-crf", "23"]) else: cmd.extend(["-c:v", "copy"]) ``` - compose 是中间产物(Remotion 会再次编码),流复制省一次编码且无质量损失 --- ### 4. FFmpeg 超时保护(高优先级) **文件**: `video_service.py` - `_run_ffmpeg()`: 新增 `timeout=600`(10 分钟),捕获 `subprocess.TimeoutExpired` - `_get_duration()`: 新增 `timeout=30` - 防止畸形视频导致 FFmpeg 永久挂起阻塞后台任务 --- ### 5. 全局任务并发限制(高优先级) **文件**: `workflow.py` - 模块级 `asyncio.Semaphore(2)`,`process_video_generation()` 入口 acquire - 排队中的任务显示"排队中..."状态 - 防止多个请求同时跑 FFmpeg + Remotion 导致 CPU/内存爆炸 ```python _generation_semaphore = asyncio.Semaphore(2) async def process_video_generation(task_id, req, user_id): _update_task(task_id, message="排队中...") async with _generation_semaphore: await _process_video_generation_inner(task_id, req, user_id) ``` --- ### 6. Redis 任务 TTL + 索引清理(中优先级) **文件**: `task_store.py` - `create()`: 设 24 小时 TTL(`ex=86400`) - `update()`: completed/failed 状态设 2 小时 TTL(`ex=7200`),其余 24 小时 - `list()`: 遍历时顺带清理已过期的索引条目(`srem`) - 解决 Redis 任务 key 永久堆积问题 --- ### 7. 临时字体文件清理(中优先级) **文件**: `workflow.py` - `prepare_style_for_remotion()` 复制字体到 temp_dir,但未加入清理列表 - 现在遍历三组前缀(subtitle/title/secondary_title)× 四种扩展名(.ttf/.otf/.woff/.woff2),将存在的字体文件加入 `temp_files` --- ### 8. Whisper+split 逻辑去重(低优先级) **文件**: `workflow.py` - 两个分支(custom_assignments 不匹配 vs 默认)的 Whisper→_split_equal 代码 100% 相同(36 行重复) - 提取为内部函数 `_whisper_and_split()`,两个分支共用 --- ### 9. LipSync 死代码清理(低优先级) **文件**: `lipsync_service.py` - 删除 `_preprocess_video()` 方法(92 行),全项目无任何调用 --- ### 10. 标题字幕预览背景 CORS 修复 - **问题**: 前端域名从 `vigent.hbyrkj.top` 迁移到 `ipagent.ai-labz.cn` 后,素材签名 URL(`api.hbyrkj.top`)与新前端域名完全不同根域,Supabase Kong 网关的 CORS 不覆盖新域名 → `