文章目录
-
- 项目目标
- 整体架构
- 方案选择历程
-
- [方案一:edge-tts(在线配音)+ MoviePy 渲染](#方案一:edge-tts(在线配音)+ MoviePy 渲染)
- [方案二:CosyVoice 2.0(阿里通义,离线声音克隆)](#方案二:CosyVoice 2.0(阿里通义,离线声音克隆))
- 方案三:GPT-SoVITS(最终方案)
- 踩坑记录
-
- [坑 1:模型下载极慢](#坑 1:模型下载极慢)
- [坑 2:CosyVoice2 和 PyTorch 2.9.0 不兼容](#坑 2:CosyVoice2 和 PyTorch 2.9.0 不兼容)
- [坑 3:GPT-SoVITS 安装踩坑](#坑 3:GPT-SoVITS 安装踩坑)
- [坑 4:中文模型下载](#坑 4:中文模型下载)
- [坑 5:GPT-SoVITS 零样本输出重复提示词尾](#坑 5:GPT-SoVITS 零样本输出重复提示词尾)
- [坑 6:ffmpeg filter_complex 语法](#坑 6:ffmpeg filter_complex 语法)
- [坑 7:emoji 导致 subprocess 崩溃](#坑 7:emoji 导致 subprocess 崩溃)
- [坑 8:音量和 BGM 平衡](#坑 8:音量和 BGM 平衡)
- 最终部署清单
- 项目总结
- 遗留问题
项目目标
输入几张有序图片 + 每张图的字幕 + 背景音乐 → 输出竖屏短视频(1160×2112),带 AI 配音和字幕叠加。
GitHub:https://github.com/guanlinyi/video-maker
最终效果
- ✅ 图片自动缩放裁剪为竖屏(1160×2112)
- ✅ 每张图显示时长 = 对应配音时长,说完自动切
- ✅ 声音克隆:用一段 15 秒录音克隆出任意文字的语音
- ✅ 字幕叠加:半透明黑底 + 白色文字
- ✅ 背景音乐:从原视频中分离出来的纯音乐
- ✅ 交叉淡入淡出转场
- ✅ GPU 硬件编码,渲染速度快(12s 视频 ≈ 20s 渲染)
- ⚠️ 配音偶尔有轻微的"诶"声(GPT-SoVITS 零样本生成的通病)
整体架构
┌─────────────────────┐
│ input/ │
│ 图片 + 字幕 + BGM │
└──────┬──────────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌────────────────────┐ ┌──────────────────────┐
│ GPT-SoVITS │ │ ffmpeg │
│ 声音克隆引擎 │ │ 视频渲染引擎 │
│ │ │ │
│ 输入: 文字+参考音频 │ │ filter_complex: │
│ 输出: wav 语音文件 │ │ 缩放图片→加字幕 │
└────────┬───────────┘ │ →xfade转场→mix音频 │
│ │ │
▼ │ NVENC: GPU硬件编码 │
┌──────────────┐ │ 输出 MP4 │
│ .tts_cache/ │ └──────────────────────┘
│ TTS 缓存 │ ▲
└──────────────┘ │
│
┌─────────┴──────────┐
│ Demucs (PyTorch) │
│ AI 音频分离模型 │
│ │
│ 原视频 → 人声+音乐 │
│ (bass+drums+other │
│ 混合成背景音乐) │
└─────────────────────┘
术语解释
- ffmpeg filter_complex:ffmpeg 提供的一种"滤镜图"功能,可以把多个视频/音频处理步骤串联起来。这里用它把图片缩放→叠加字幕→交叉淡入淡出→混合音频,全部一步完成,不需要中间文件。
- NVENC:NVIDIA 显卡自带的硬件视频编码器。用它能比用 CPU 编码快 5-10 倍,而且渲染时不占用 CPU。
- Demucs:Meta(Facebook)开源的 AI 音频分离模型。它能把一首歌里的人声、鼓声、贝斯、其他乐器分别提取出来。这里用它将原视频的人声和背景音乐分开。
- xfade:ffmpeg 的交叉淡入淡出滤镜,让两张图之间平滑过渡。
- amix:ffmpeg 的音频混合滤镜,把多路音频叠在一起。
- dynaudnorm:动态音量归一化,让整段音频的音量保持平稳。
方案选择历程
方案一:edge-tts(在线配音)+ MoviePy 渲染
选了它因为:最简单,pip install 就能跑,不需要 GPU,不需要下载模型。
结果:能用,但:
- 必须联网(走代理访问微软服务器)
- 微软晓晓的 AI 女声不够自然
- MoviePy 渲染 1160×2112 大分辨率视频极慢(38s 视频要 3.5 分钟)
方案二:CosyVoice 2.0(阿里通义,离线声音克隆)
选了它因为:号称中文零样本 TTS 最强,2 秒音频就能克隆声音,全离线。
GitHub :https://github.com/FunAudioLLM/CosyVoice
论文 :https://arxiv.org/abs/2412.10117
模型下载:https://www.modelscope.cn/models/iic/CosyVoice2-0.5B
结果:模型下载成功但推理废了(详见坑 2)。
方案三:GPT-SoVITS(最终方案)
GitHub :https://github.com/RVC-Boss/GPT-SoVITS
Stars :58.4k,社区非常活跃
模型下载:https://huggingface.co/lj1995/GPT-SoVITS
选了它因为:
- 对 PyTorch 新版兼容性好
- 中文效果公认最好
- 4060 Ti 实测 RTF 0.028(比实时快 35 倍)
- 零样本 5 秒音频就能克隆
- 也有 v3/v4 但 v2Pro 综合效果最好
踩坑记录
坑 1:模型下载极慢
问题:多个模型(CosyVoice2 ~4.5GB、GPT-SoVITS ~5GB)下载速度很慢。
根因:身处国内网络环境,pip/modelscope/huggingface 下载大文件需要走代理,速度仅 200KB/s-3MB/s 不等,且工具 timeout 频繁中断。
流程:
下载命令 → 开始下载 → 30分钟后被 timeout 杀死 → 手动重跑续传 → 循环 3-4 次
实际的操作步骤:
bash
# ❌ 不要这样下(太慢)
python -c "from modelscope import snapshot_download; snapshot_download('iic/CosyVoice2-0.5B', ...)"
# ✅ 正确方式:用 run_background + wait_for_job 轮询
python -c "from modelscope import snapshot_download; ..." # 会超时但续传
# ✅ 最快方式:让用户浏览器自己下
# 打开 https://www.modelscope.cn/models/iic/CosyVoice2-0.5B
# 浏览器下载 3 分钟顶工具 3 小时
最终解决 :用 run_background 后台跑 + wait_for_job 以 10 分钟超时轮询。每次被杀死后重跑会续传,累积 3-4 次后下载完成。
教训:超过 100MB 的大文件,优先让用户用浏览器自己下载(快 100 倍)。
坑 2:CosyVoice2 和 PyTorch 2.9.0 不兼容
问题:模型加载成功,但推理输出 20-40 秒废音频。
根因 :CosyVoice2 官方基于 PyTorch 2.3.1 开发,其推理代码使用 torch.cuda.amp.autocast()。在 PyTorch 2.9.0 中,这个 API 的行为变了(虽然还没完全删除),导致模型生成完全错误的 token 序列。
尝试的修复:
python
# 尝试 1:禁用混合精度 --- 没用
cosyvoice = AutoModel(..., fp16=False)
# 尝试 2:手动改 model.py 用新版 API --- 没用
torch.amp.autocast('cuda', enabled=self.fp16, dtype=torch.bfloat16)
最终解决:放弃 CosyVoice2,换 GPT-SoVITS。
识别方法 :推理时日志显示 yield speech len 20.0(短句产出 20 秒音频),且音量极低,说明模型没正确停止生成。
坑 3:GPT-SoVITS 安装踩坑
GitHub:https://github.com/RVC-Boss/GPT-SoVITS
官方安装步骤:
bash
conda create -n GPTSoVits python=3.10
conda activate GPTSoVits
pip install -r requirements.txt
实际执行时的问题:
问题 3.1:requirements.txt 里 torch/torchaudio 版本老旧
requirements.txt 指定 torch==2.3.1,但我们的 CUDA 是 12.6 需要 torch>=2.5。
解决:不单独装 PyTorch,直接复用已有环境(reset3)的 PyTorch 2.9.0 + CUDA 12.6。只装缺失的包:
bash
pip install -r requirements.txt
# 已安装的 torch/torchaudio 会自动跳过
问题 3.2:matcha-tts 装不上
matcha-tts 0.0.7.2 依赖旧版 numpy/build 工具,与新版 setuptools 冲突。
解决:
bash
pip install matcha-tts --no-build-isolation --no-deps
问题 3.3:torchcodec 缺少 FFmpeg shared DLLs
新版 torchaudio(2.9.0)强制使用 torchcodec 来读写音频文件,但 torchcodec 需要 FFmpeg 的 .dll 动态库(shared 版本),而我们装的是 FFmpeg full_build(静态编译,没有 DLL)。
尝试 :装 conda 版 ffmpeg(有 DLL)→ 依然缺文件
最终解决:monkey-patch torchaudio.load,用 soundfile 替代:
python
import torchaudio, soundfile as sf, torch
def _patched_load(path, **kwargs):
audio, sr = sf.read(path)
audio_t = torch.from_numpy(audio).float()
if audio_t.ndim == 1:
audio_t = audio_t.unsqueeze(0)
else:
audio_t = audio_t.T
return audio_t, sr
torchaudio.load = _patched_load
问题 3.4:inference_webui.py 的 bert_path 用相对路径
python
bert_path = "GPT_SoVITS/pretrained_models/chinese-roberta-wwm-ext-large"
新版 transformers 把这个路径当 HuggingFace repo ID 校验,拒绝加载。
解决:改为绝对路径:
python
bert_path = os.path.join(os.path.dirname(__file__), "pretrained_models/chinese-roberta-wwm-ext-large")
坑 4:中文模型下载
GPT-SoVITS 依赖多个模型文件,下载方式各异:
| 模型 | 大小 | 下载方式 |
|---|---|---|
| Chinese-RoBERTa-wwm-ext-large | ~1.2GB | from transformers import AutoModel |
| Chinese-HuBERT-base | 180MB | 直接从 HuggingFace 下载 pytorch_model.bin |
| fasttext 语言检测 | ~400MB | urllib.request.urlretrieve(url, ...) |
| GPT-SoVITS v2Pro 全套 | ~5GB | git lfs clone(实测最快) |
GPT-SoVITS 主模型下载(推荐方式):
bash
# git LFS 比 Python SDK 快得多
cd D:\zero_track\Reset
git clone --depth=1 https://huggingface.co/lj1995/GPT-SoVITS
# 如果 hf-mirror 不工作,加代理:
set HTTP_PROXY=http://localhost:9910
模型文件位置:
GPT-SoVITS/GPT_SoVITS/pretrained_models/
├── s1bert25hz-2kh-longer-epoch=68e-step=50232.ckpt ← GPT 模型
├── v2Pro/s2Gv2Pro.pth ← SoVITS 模型
├── v2Pro/s2Gv2ProPlus.pth ← SoVITS ProPlus 模型
├── sv/pretrained_eres2netv2w24s4ep4.ckpt ← 说话人编码器
├── chinese-roberta-wwm-ext-large/ ← 文本编码器
├── chinese-hubert-base/ ← 语音编码器
└── fast_langdetect/lid.176.bin ← 语言检测
坑 5:GPT-SoVITS 零样本输出重复提示词尾
问题:每句话开头会出现参考文本末尾的词语。
例 :参考文本是"土豆削皮上锅蒸熟酱油腌制,大火炒熟西兰花切成块,焯水 ",
目标文字"土豆削皮" → 输出"焯水土豆削皮"
根因:GPT-SoVITS 的零样本推理机制是把参考文本当"前缀"输入给语言模型,目标文字是"续写"。所以它"续写"时会先重复参考文本末尾的词,再开始说目标文字。
尝试的解决:
方法 1: 固定裁剪开头 0.4-0.5s 音频
→ 短句"土豆削皮"被裁掉一半,只听到"削皮"
→ ❌
方法 2: 提高静音阈值(5%)
→ 效果有限,因为跳过去的不是静音是语音
→ ❌
方法 3: 用 Whisper 逐字对齐裁剪
→ Whisper tiny 识别中文不准,"土豆削皮"识别成"塗斗消皮"
→ 匹配不上,反而把正确内容裁掉
→ ❌
方法 4: 改为逐句使用独立参考片段(最终方案)
→ 把完整参考音频按字幕切分成 5 段
→ 每句话用自己的那段音频 + 自己的文字做参考
→ 第 1 句:参考音频只有"土豆削皮",没有"焯水"
→ 模型没有"焯水"可重复 ✅
教训:零样本 TTS 的"内容重叠"问题,不是在输出端裁剪能解决的,要从输入端消除重叠。
批量切分工具 :prepare_voice.py --mode segment
坑 6:ffmpeg filter_complex 语法
问题:filter 字符串报错 "No option name near 'disable'"。
根因 :ffmpeg 用逗号(,)来分隔 filter chain 中的各个滤镜。表达式 max(1160/iw, 2112/ih) 里的逗号被 ffmpeg 错误解析为滤镜分隔符。
python
# ❌ 错误写法
f"scale=iw*max({W}/iw,{H}/ih):ih*max({W}/iw,{H}/ih)"
# ffmpeg 理解为: scale=iw*max(1160/iw → 然后 2112/ih) 成了下一个滤镜
# ✅ 正确写法
f"scale='iw*max({W}/iw,{H}/ih)':'ih*max({W}/iw,{H}/ih)'"
# 用单引号保护表达式,ffmpeg 会把引号内的逗号当作运算符而非分隔符
坑 7:emoji 导致 subprocess 崩溃
问题 :subprocess.run(capture_output=True, text=True) 捕获了包含 emoji 的输出,在 Windows GBK 编码下报 UnicodeDecodeError。
解决:设环境变量让子进程用 UTF-8 输出:
python
env["PYTHONIOENCODING"] = "utf-8"
result = subprocess.run(cmd, capture_output=True, text=True, env=env)
坑 8:音量和 BGM 平衡
问题:背景音乐要么听不到,要么太大盖过人声。
根因 :amix(inputs=2) 混合两路音频时,默认把各路的音量除以输入路数(即各减半)。
最终参数:
python
TTS: volume=8.0 # 原始 TTS 只有 RMS=0.035,提 8 倍到 RMS=0.28
BGM: volume=1.0 # Demucs BGM 有 RMS=0.0375
# 经 amix(÷2) 后:TTS≈0.14, BGM≈0.019, 混合≈0.08
# 原视频 RMS=0.133,已接近
Demucs BGM 的制作方式:
bash
# Demucs 把原视频分成 4 轨:人声、鼓声、贝斯、其他
# 把鼓声+贝斯+其他 3 轨混合 → 背景音乐(无 vocals)
D:\application\ffmpeg\bin\ffmpeg.exe -i bass.wav -i drums.wav -i other.wav \
-filter_complex "[0:a][1:a][2:a]amix=inputs=3:duration=longest[bgm]" \
-map "[bgm]" background_demucs.wav
最终部署清单
硬件要求
- GPU: NVIDIA 4060 Ti (16GB VRAM) 或同等
- 硬盘: 约 10GB(模型文件 + 依赖)
- 内存: 16GB+
- 系统: Windows 10/11
所需软件
| 软件 | 版本 | 安装方式 |
|---|---|---|
| Python | 3.10+ | conda |
| PyTorch | 2.9.0+cu126 | pip |
| CUDA | 12.6 | NVIDIA 驱动自带 |
| FFmpeg | Gyan 版(full_build) | 手动下载,需含 NVENC |
| Git LFS | 最新 | git lfs install |
最终目录结构
D:\zero_track\Reset\
├── video_maker\ ← 主项目
│ ├── make_fast.py ← 主渲染脚本
│ ├── local_tts.py ← GPT-SoVITS 封装
│ ├── prepare_voice.py ← 参考音频准备工具
│ ├── compare.py ← 视频对比分析
│ ├── server.py ← 手机端服务(FastAPI)
│ ├── templates/index.html ← 手机端网页界面
│ ├── input/
│ │ ├── images/ ← 放图片
│ │ ├── subtitles.txt ← 字幕
│ │ └── music/
│ │ └── background_demucs.wav ← 背景音乐
│ ├── output/ ← 生成视频
│ └── voices/ ← 声音库
│
├── GPT-SoVITS\ ← 声音克隆引擎
│ └── GPT_SoVITS/pretrained_models/ ← 预训练模型
│
└── output/ ← 参考音频 / 分离结果
├── cloned_voice.wav ← 原视频提取的人声
└── ref_segments/ ← 切分后的参考片段
一句话使用
bash
set HTTP_PROXY=http://localhost:9910
cd D:\zero_track\Reset\video_maker
python make_fast.py --local-tts
项目总结
目标完成度
| 目标 | 完成度 | 说明 |
|---|---|---|
| 图片→竖屏视频 | ✅ | 1160×2112,自动缩放填充 |
| 字幕叠加 | ✅ | 半透黑底白字,底部居中 |
| AI 配音 | ✅ | GPT-SoVITS 声音克隆 |
| 声音克隆 | ✅ | 从原视频 15s 录音克隆 |
| 背景音乐 | ✅ | Demucs 分离 |
| 画面按配音切换 | ✅ | 每张图显示时长=配音时长 |
| GPU 加速 | ✅ | NVENC 硬件编码 |
| 手机服务端 | ✅ | video_maker_app/ |
| 清晰无杂音 | ⚠️ | 偶尔有极短的"诶"声 |
| 零样本完美效果 | ❌ | GPT-SoVITS 零样本对短句会有填充音 |
最终评价
能用的程度 :日常用 make_fast.py --local-tts 生成短视频完全可行。音色、节奏、BGM 都说得过去。
不能解决的:GPT-SoVITS 零样本对 3-5 字短句有时会带填充音,这是模型本身的局限,后处理无法完美消除。如果每句话写 8-15 字,效果会好很多。
遗留问题
网页端视频无法直接播放
问题 :server.py 生成的视频可以在浏览器下载,但无法在 <video> 标签中直接预览播放(播放器组件空白)。
尝试:修改视频像素格式(yuvj444p → yuv420p)、添加 faststart moov 前置、改用 StaticFiles 挂载、改用 FileResponse → 视频格式正确但浏览器依然不播。
可能原因:FastAPI 对 MP4 视频的 range request 支持不完整,或 h264_nvenc 编码的视频与浏览器解码器存在兼容问题。需要有多模态能力的模型协助调试浏览器端行为。
当前方案 :用户通过下载按钮拿到视频后在本地播放器播放,或直接使用 make_fast.py 命令行生成。