232 lines
10 KiB
Markdown
232 lines
10 KiB
Markdown
## Remotion 描边修复 + 字体样式扩展 + TypeScript 修复 (Day 27)
|
||
|
||
### 概述
|
||
|
||
修复标题/字幕描边渲染问题(描边过粗 + 副标题重影),扩展字体样式选项(标题 4→12、字幕 4→8),修复 Remotion 项目 TypeScript 类型错误。
|
||
|
||
---
|
||
|
||
## ✅ 改动内容
|
||
|
||
### 1. 描边渲染修复(标题 + 字幕)
|
||
|
||
- **问题**: 标题黑色描边过粗,副标题出现重影/鬼影
|
||
- **根因**: `buildTextShadow` 用 4 方向 `textShadow` 模拟描边 — 对角线叠加导致描边视觉上比实际 `stroke_size` 更粗;4 角方向在中间有间隙和叠加,造成重影
|
||
- **修复**: 改用 CSS 原生描边 `-webkit-text-stroke` + `paint-order: stroke fill`(Remotion 用 Chromium 渲染,完美支持)
|
||
- **旧方案**:
|
||
```javascript
|
||
textShadow: `-8px -8px 0 #000, 8px -8px 0 #000, -8px 8px 0 #000, 8px 8px 0 #000, 0 0 16px rgba(0,0,0,0.5), 0 2px 4px rgba(0,0,0,0.3)`
|
||
```
|
||
- **新方案**:
|
||
```javascript
|
||
WebkitTextStroke: `5px #000000`,
|
||
paintOrder: 'stroke fill',
|
||
textShadow: `0 2px 4px rgba(0,0,0,0.3)`,
|
||
```
|
||
- 同时将所有预设样式的 `stroke_size` 从 8 降到 5,配合原生描边视觉更干净
|
||
|
||
### 2. 字体样式扩展
|
||
|
||
**标题样式**: 4 个 → 12 个(+8)
|
||
|
||
| ID | 样式名 | 字体 | 配色 |
|
||
|----|--------|------|------|
|
||
| title_pangmen | 庞门正道 | 庞门正道标题体3.0 | 白字黑描 |
|
||
| title_round | 优设标题圆 | 优设标题圆 | 白字紫描 |
|
||
| title_alibaba | 阿里数黑体 | 阿里巴巴数黑体 | 白字黑描 |
|
||
| title_chaohei | 文道潮黑 | 文道潮黑 | 青蓝字深蓝描 |
|
||
| title_wujie | 无界黑 | 标小智无界黑 | 白字深灰描 |
|
||
| title_houdi | 厚底黑 | Aa厚底黑 | 红字深黑描 |
|
||
| title_banyuan | 寒蝉半圆体 | 寒蝉半圆体 | 白字黑描 |
|
||
| title_jixiang | 欣意吉祥宋 | 字体圈欣意吉祥宋 | 金字棕描 |
|
||
|
||
**字幕样式**: 4 个 → 8 个(+4)
|
||
|
||
| ID | 样式名 | 字体 | 高亮色 |
|
||
|----|--------|------|--------|
|
||
| subtitle_pink | 少女粉 | DingTalk JinBuTi | 粉色 #FF69B4 |
|
||
| subtitle_lime | 清新绿 | DingTalk Sans | 荧光绿 #76FF03 |
|
||
| subtitle_gold | 金色隶书 | 阿里妈妈刀隶体 | 金色 #FDE68A |
|
||
| subtitle_kai | 楷体红字 | SimKai | 红色 #FF4444 |
|
||
|
||
### 3. TypeScript 类型错误修复
|
||
|
||
- **Root.tsx**: `Composition` 泛型类型与 `calculateMetadata` 参数类型不匹配 — 内联 `calculateMetadata` 并显式标注参数类型,`defaultProps` 使用 `satisfies VideoProps` 约束
|
||
- **Video.tsx**: `VideoProps` 接口添加 `[key: string]: unknown` 索引签名,兼容 Remotion 要求的 `Record<string, unknown>` 约束
|
||
- **VideoLayer.tsx**: `OffthreadVideo` 组件不支持 `loop` prop — 移除(该 prop 原本就被忽略)
|
||
|
||
### 4. 进度条文案还原
|
||
|
||
- **问题**: 进度条显示后端推送的详细阶段消息(如"正在合成唇型"),用户希望只显示"正在AI生成中..."
|
||
- **修复**: `HomePage.tsx` 进度条文案从 `{currentTask.message || "正在AI生成中..."}` 改为固定 `正在AI生成中...`
|
||
|
||
---
|
||
|
||
## 📁 修改文件清单
|
||
|
||
| 文件 | 改动 |
|
||
|------|------|
|
||
| `remotion/src/components/Title.tsx` | `buildTextShadow` → `buildStrokeStyle`(CSS 原生描边),标题+副标题同时生效 |
|
||
| `remotion/src/components/Subtitles.tsx` | `buildTextShadow` → `buildStrokeStyle`(CSS 原生描边) |
|
||
| `remotion/src/Root.tsx` | 修复 `Composition` 泛型类型、`calculateMetadata` 参数类型 |
|
||
| `remotion/src/Video.tsx` | `VideoProps` 添加索引签名 |
|
||
| `remotion/src/components/VideoLayer.tsx` | 移除 `OffthreadVideo` 不支持的 `loop` prop |
|
||
| `backend/assets/styles/title.json` | 标题样式从 4 个扩展到 12 个,`stroke_size` 8→5 |
|
||
| `backend/assets/styles/subtitle.json` | 字幕样式从 4 个扩展到 8 个 |
|
||
| `frontend/.../HomePage.tsx` | 进度条文案还原为固定"正在AI生成中..." |
|
||
|
||
---
|
||
|
||
## 🔍 验证
|
||
|
||
- `npx tsc --noEmit` — 零错误
|
||
- `npm run build:render` — 渲染脚本编译成功
|
||
- `npm run build`(前端)— 零报错
|
||
- 描边:标题/副标题/字幕使用 CSS 原生描边,无重影、无虚胖
|
||
- 样式选择:前端下拉可加载全部 12 个标题 + 8 个字幕样式
|
||
|
||
---
|
||
|
||
## 视频生成流水线性能优化
|
||
|
||
### 概述
|
||
|
||
针对视频生成流水线进行全面性能优化,涵盖 FFmpeg 编码参数、LatentSync 推理参数、多素材并行化、以及后处理阶段并行化。预估 15s 单素材视频从 ~280s 降至 ~190s (32%),30s 双素材从 ~400s 降至 ~240s (40%)。
|
||
|
||
**服务器配置**: 2x RTX 3090 (24GB), 2x Xeon E5-2680 v4 (56核), 192GB RAM
|
||
|
||
### 第一阶段:FFmpeg 编码优化
|
||
|
||
**最终合成 preset `slow` → `medium`**
|
||
- 合成阶段从 ~50s 降到 ~25s,质量几乎无变化
|
||
|
||
**中间文件 CRF 18 → 23**
|
||
- 中间产物(trim、prepare_segment、concat、loop、normalize_orientation)不是最终输出,不需要高质量编码
|
||
- 每个中间步骤快 3-8 秒
|
||
|
||
**最终合成 CRF 18 → 20**
|
||
- 15 秒口播视频 CRF 18 vs 20 肉眼无法区分
|
||
|
||
### 第二阶段:LatentSync 推理参数调优
|
||
|
||
**inference_steps 20 → 16**
|
||
- 推理时间线性减少 20%(~180s → ~144s)
|
||
|
||
**guidance_scale 2.0 → 1.5**
|
||
- classifier-free guidance 权重降低,每步计算量微降(5-10%)
|
||
|
||
> ⚠️ 两项需重启 LatentSync 服务后测试唇形质量,确认可接受再保留。如质量不佳可回退 .env 参数。
|
||
|
||
### 第三阶段:多素材流水线并行化
|
||
|
||
**素材下载 + 归一化并行**
|
||
- 串行 `for` 循环改为 `asyncio.gather()`,`normalize_orientation` 通过 `run_in_executor` 在线程池执行
|
||
- N 个素材从串行 N×5s → ~5s
|
||
|
||
**片段预处理并行**
|
||
- 逐个 `prepare_segment` 改为 `asyncio.gather()` + `run_in_executor`
|
||
- 2 素材 ~90s → ~50s;4 素材 ~180s → ~60s
|
||
|
||
### 第四阶段:流水线交叠
|
||
|
||
**Whisper 字幕对齐 与 BGM 混音 并行**
|
||
- 两者互不依赖(都只依赖 audio_path),用 `asyncio.gather()` 并行执行
|
||
- 单素材模式下 Whisper 从 LatentSync 之后的串行步骤移至与 BGM 并行
|
||
- 不开 BGM 或不开字幕时行为不变,只有同时启用时才并行
|
||
|
||
### 修改文件
|
||
|
||
| 文件 | 改动 |
|
||
|------|------|
|
||
| `backend/app/services/video_service.py` | compose: preset slow→medium, CRF 18→20; normalize_orientation/prepare_segment/concat: CRF 18→23 |
|
||
| `backend/app/services/lipsync_service.py` | _loop_video_to_duration: CRF 18→23 |
|
||
| `backend/.env` | LATENTSYNC_INFERENCE_STEPS=16, LATENTSYNC_GUIDANCE_SCALE=1.5 |
|
||
| `backend/app/modules/videos/workflow.py` | import asyncio; 素材下载/归一化并行; 片段预处理并行; Whisper+BGM 并行 |
|
||
|
||
### 回退方案
|
||
|
||
- FFmpeg 参数:如画质不满意,将最终 CRF 改回 18、preset 改回 slow
|
||
- LatentSync:如唇形质量下降,将 .env 中 `INFERENCE_STEPS` 改回 20、`GUIDANCE_SCALE` 改回 2.0
|
||
- 并行化:纯架构优化,无质量影响,无需回退
|
||
|
||
---
|
||
|
||
## MuseTalk + LatentSync 混合唇形同步方案
|
||
|
||
### 概述
|
||
|
||
LatentSync 1.6 质量高但推理极慢(~78% 总时长),长视频(>=2min)耗时 20-60 分钟不可接受。MuseTalk 1.5 是单步潜空间修复(非扩散模型),逐帧推理速度接近实时(30fps+ on V100),适合长视频。混合方案按音频时长自动路由:短视频用 LatentSync 保质量,长视频用 MuseTalk 保速度。
|
||
|
||
### 架构
|
||
|
||
- **路由阈值**: `LIPSYNC_DURATION_THRESHOLD` (默认 120s)
|
||
- **短视频 (<120s)**: LatentSync 1.6 (GPU1, 端口 8007)
|
||
- **长视频 (>=120s)**: MuseTalk 1.5 (GPU0, 端口 8011)
|
||
- **回退**: MuseTalk 不可用时自动 fallback 到 LatentSync
|
||
|
||
### 改动文件
|
||
|
||
| 文件 | 改动 |
|
||
|------|------|
|
||
| `models/MuseTalk/` | 从 Temp/MuseTalk 复制代码 + 下载权重 |
|
||
| `models/MuseTalk/scripts/server.py` | 新建 FastAPI 常驻服务 (端口 8011, GPU0) |
|
||
| `backend/app/core/config.py` | 新增 MUSETALK_* 和 LIPSYNC_DURATION_THRESHOLD |
|
||
| `backend/.env` | 新增对应环境变量 |
|
||
| `backend/app/services/lipsync_service.py` | 新增 `_call_musetalk_server()` + 混合路由逻辑 + 扩展 `check_health()` |
|
||
|
||
---
|
||
|
||
## MuseTalk 推理性能优化 (server.py v2)
|
||
|
||
### 概述
|
||
|
||
MuseTalk 首次长视频测试 (136s, 3404 帧) 耗时 1799s (~30 分钟),分析发现瓶颈集中在人脸检测 (28%)、BiSeNet 合成 (22%)、I/O (17%),而非 UNet 推理本身 (17%)。通过 6 项优化预估降至 8-10 分钟 (~3x 加速)。
|
||
|
||
### 性能瓶颈分析 (优化前, 1799s)
|
||
|
||
| 阶段 | 耗时 | 占比 | 瓶颈原因 |
|
||
|------|------|------|---------|
|
||
| DWPose + 人脸检测 | ~510s | 28% | `batch_size_fa=1`, 每帧跑 2 个 NN, 完全串行 |
|
||
| 合成 + BiSeNet 人脸解析 | ~400s | 22% | 每帧都跑 BiSeNet + PNG 写盘 |
|
||
| UNet 推理 | ~300s | 17% | batch_size=8 太小 |
|
||
| I/O (PNG 读写 + FFmpeg) | ~300s | 17% | PNG 压缩慢, ffmpeg→PNG→imread 链路 |
|
||
| VAE 编码 | ~100s | 6% | 逐帧编码, 未批处理 |
|
||
|
||
### 6 项优化
|
||
|
||
| # | 优化项 | 详情 |
|
||
|---|--------|------|
|
||
| 1 | **batch_size 8→32** | `.env` 修改, RTX 3090 显存充裕 |
|
||
| 2 | **cv2.VideoCapture 直读帧** | 跳过 ffmpeg→PNG→imread 链路, 省去 3404 次 PNG 编解码 |
|
||
| 3 | **人脸检测降频 (每5帧)** | 每 5 帧运行 DWPose + FaceAlignment, 中间帧线性插值 bbox |
|
||
| 4 | **BiSeNet mask 缓存 (每5帧)** | 每 5 帧运行 `get_image_prepare_material`, 中间帧用 `get_image_blending` 复用缓存 mask |
|
||
| 5 | **cv2.VideoWriter 直写** | 跳过逐帧 PNG 写盘 + ffmpeg 重编码, 用 VideoWriter 直写 mp4 |
|
||
| 6 | **每阶段计时** | 7 个阶段精确计时, 方便后续进一步调优 |
|
||
|
||
### 修改文件
|
||
|
||
| 文件 | 改动 |
|
||
|------|------|
|
||
| `models/MuseTalk/scripts/server.py` | 完全重写 `_run_inference()`, 新增 `_detect_faces_subsampled()` |
|
||
| `backend/.env` | `MUSETALK_BATCH_SIZE` 8→32 |
|
||
|
||
---
|
||
|
||
## Remotion 并发渲染优化
|
||
|
||
### 概述
|
||
|
||
Remotion 渲染在 56 核服务器上默认只用 8 并发 (`min(8, cores/2)`),改为 16 并发,预估从 ~5 分钟降到 ~2-3 分钟。
|
||
|
||
### 改动
|
||
|
||
- `remotion/render.ts`: `renderMedia()` 新增 `concurrency` 参数 (默认 16), 支持 `--concurrency` CLI 参数覆盖
|
||
- `remotion/dist/render.js`: 重新编译
|
||
|
||
### 修改文件
|
||
|
||
| 文件 | 改动 |
|
||
|------|------|
|
||
| `remotion/render.ts` | `RenderOptions` 新增 `concurrency` 字段, `renderMedia()` 传入 `concurrency` |
|
||
| `remotion/dist/render.js` | TypeScript 重新编译 |
|