diff --git a/Docs/BACKEND_README.md b/Docs/BACKEND_README.md index 2964d53..088706f 100644 --- a/Docs/BACKEND_README.md +++ b/Docs/BACKEND_README.md @@ -65,6 +65,7 @@ backend/ * `POST /api/materials`: 上传素材 * `GET /api/materials`: 获取素材列表 * `PUT /api/materials/{material_id}`: 重命名素材 + * `GET /api/materials/stream/{material_id}`: 同源流式返回素材文件(用于前端 canvas 截帧,避免跨域 CORS taint) 4. **社交发布 (Publish)** * `POST /api/publish`: 发布视频到 抖音/微信视频号/B站/小红书 @@ -160,6 +161,18 @@ backend/ - 多素材片段在拼接前统一重编码,并强制 `25fps + CFR`,减少段边界时间基不一致导致的画面卡顿。 - concat 流程启用 `+genpts` 重建时间戳,提升拼接后时间轴连续性。 - 对带旋转元数据的 MOV 素材会先做方向归一化,再进入分辨率判断和后续流程。 +- compose 阶段(视频轨+音频轨合并)使用 `-c:v copy` 流复制替代重编码,几乎瞬间完成。 +- FFmpeg 子进程设有超时保护:`_run_ffmpeg()` 600 秒、`_get_duration()` 30 秒,防止畸形文件导致永久挂起。 + +### 全局并发控制 + +- 视频生成入口使用 `asyncio.Semaphore(2)` 限制最多 2 个任务同时执行,排队中的任务显示"排队中..."状态。 +- Redis 任务 key 设有 TTL:创建时 24 小时,completed/failed 状态 2 小时,`list()` 时自动清理过期索引。 + +### 字幕时间戳优化 + +- Whisper 输出经 `smooth_word_timestamps()` 三步平滑:单调递增保证、重叠消除(中点分割)、微小间隙填补(<50ms)。 +- 支持 `original_text` 原文节奏映射:原文字符按比例映射到 Whisper 时间戳上,解决 AI 改写/多语言文案与转录不一致问题。 ## 📦 资源库与静态资源 diff --git a/Docs/DEPLOY_MANUAL.md b/Docs/DEPLOY_MANUAL.md index 330fb80..5f21848 100644 --- a/Docs/DEPLOY_MANUAL.md +++ b/Docs/DEPLOY_MANUAL.md @@ -211,8 +211,10 @@ cp .env.example .env | `SUPABASE_PUBLIC_URL` | `https://api.hbyrkj.top` | Supabase API 公网地址 (前端访问) | | `LATENTSYNC_GPU_ID` | 1 | GPU 选择 (0 或 1) | | `LATENTSYNC_USE_SERVER` | false | 设为 true 以启用常驻服务加速 | -| `LATENTSYNC_INFERENCE_STEPS` | 16 | 推理步数 (16-50) | -| `LATENTSYNC_GUIDANCE_SCALE` | 1.5 | 引导系数 (1.0-3.0) | +| `LATENTSYNC_INFERENCE_STEPS` | 20 | 推理步数 (16-50) | +| `LATENTSYNC_GUIDANCE_SCALE` | 2.0 | 引导系数 (1.0-3.0) | +| `LATENTSYNC_ENABLE_DEEPCACHE` | true | DeepCache 推理加速 | +| `LATENTSYNC_SEED` | 1247 | 固定随机种子(可复现) | | `DEBUG` | true | 生产环境改为 false | | `REDIS_URL` | `redis://localhost:6379/0` | 任务状态存储(不可用时回退内存) | | `WEIXIN_HEADLESS_MODE` | headless-new | 视频号 Playwright 模式 (headful/headless-new) | diff --git a/Docs/DevLogs/Day29.md b/Docs/DevLogs/Day29.md new file mode 100644 index 0000000..38d9bae --- /dev/null +++ b/Docs/DevLogs/Day29.md @@ -0,0 +1,206 @@ +## 字幕同步修复 + 嘴型参数调优 + 视频流水线全面优化 + 预览背景修复 (Day 29) + +### 概述 + +本轮对视频生成流水线做全面审查优化:修复字幕与语音不同步问题(Whisper 时间戳平滑 + 原文节奏映射)、调优 LatentSync 嘴型参数、compose 流复制省去冗余重编码、FFmpeg 超时保护、全局并发限制、Redis 任务 TTL、临时文件清理、死代码移除。同时修复因前端域名迁移(`vigent.hbyrkj.top` → `ipagent.ai-labz.cn`)导致的样式预览背景 CORS 失效问题。 + +--- + +## ✅ 改动内容 + +### 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 不覆盖新域名 → `