AI 音乐 MV 制作流水线
把「音频 + 参考图」做成完整 MV,covers 4 个核心阶段。
When to load
- 用 InfiniteTalk / Wan I2V 跑 lip-sync 音乐视频
- 把多段视频按歌词时间轴拼接 + 加智能过场 + 烧字幕
- 多平台输出 (B 站 16:9 / 抖音 9:16 / 小红书 4:3)
- 排查 MV 剪辑常见坑 (口型漂移、过场时间累积、xfade chain bug、必剪/剪映工程不互通)
4 阶段流水线
阶段 1: 音频对齐 & 段位划分
import stable_whisper
m = stable_whisper.load_model("base", device="cpu")
r = m.align(MP3, text=lyrics_txt, language="zh", original_split=True)
r.to_srt_vtt(SRT, segment_level=True)
# 找关键段起点 (Intro / Hook / Verse / Bridge / Outro)
for s in r.segments:
if "副歌金句" in s.text: print(s.start)
输出: srt + 段位时间表 (锚段需要哪些段位的精确时间)。
阶段 2: 视频生成 (ComfyUI 远程 GPU)
两种 clip:
| 类型 | 工具 | 长度 | 跟音频 |
|---|---|---|---|
| 锚段 (口型) | Wan 2.1 InfiniteTalk Single | 5-19s | 是 (输入 mp3 片段) |
| 短镜 (氛围) | Wan 2.2 I2V + KJ wrapper (G01_basic.json) | 4-7s | 否 (只输入图) |
关键坑 (踩过):
AudioCrop.widgets_values必须整数秒,"0:18.5"报错 → 用"0:18"- Wan I2V 的 G05 模板 (LightX2V x3 LoRA) 风格保持极弱, 经常跑成"白衣古风+电光鸟"。必须用 G01 (KJ WanVideo wrapper, lightx2v 只 1 个 LoRA on high)
KSamplerAdvanced.widgets_values中 seed 在 index 1 不是 3 (index 3 是 steps)- Wan I2V 末尾 1-2 帧有 "倒走" bug → 每段 trim 末尾 1s
- 锚段视频末尾有 ~1s padding → trim 到精确音频长度
ui2api_v2.py已处理 SetNode/GetNode 链 + Anything Everywhere
双卡分配: 段长平均分到 GPU 6006 / 6010, 长度大致均衡。
阶段 3: 时间轴拼接 (MoviePy, 防漂移核心)
永远用 MoviePy CompositeVideoClip + with_start(精确时间), 不用 ffmpeg xfade chain (xfade 必漂移, ffmpeg 8.1+ chain 还有 truncation bug)。
from moviepy import VideoFileClip, CompositeVideoClip, vfx
clips = []
for path, start, dur in plan:
c = VideoFileClip(path).subclipped(0, dur).with_start(start)
if prev_was_crossfade: c = c.with_effects([vfx.CrossFadeIn(0.3)])
clips.append(c)
final = CompositeVideoClip(clips, size=(832,1480)).with_audio(audio)
防漂移原理: 每段 with_start(t) 把段位置锚定在 timeline 绝对时间, 不依赖前段长度。锚段(口型)位置永远精确 = mp3 中对应歌词时间。
阶段 4: 智能过场 + 字幕
过场分配:
| 场景 | 过场 | 时长 |
|---|---|---|
| 同细节链 / 锚段连续 | hard cut | 0 |
| 跨场景 (大部分接缝) | CrossFadeIn | 0.25-0.4s |
| 大景别变化 (wide↔close) | zoom + crossfade | 0.4s |
| Hook→Bridge 戏剧停顿 | FadeOut+FadeIn (黑场) | 0.4s |
| 夜→黎明 / 重要爆点 | ColorClip(white) overlay + fade | 0.5-0.6s |
字幕: stable-whisper SRT → ASS (PingFang SC 52pt 白字黑边半透明黑底) → ffmpeg -vf ass=... 烧制。
已踩坑的固定规则
- xfade chain 漂移: 30+ 段 × 0.3s 累积 9s, 锚段口型必偏 → 用 MoviePy
- ffmpeg 8.1.1 xfade chain bug: 多段 chain 后输出被截到 ~20s → 用 MoviePy
- 必剪 / 剪映工程文件私有: 无法生成 .ce/.draft, 只能拖单文件素材 → 输出按时间编号
01.mp4...33.mp4+ manifest.txt - MoviePy 2.x API:
with_start不是set_start,with_effects([vfx.CrossFadeIn(d)])不是crossfadein(d) - DaVinci Resolve / Final Cut 用户: 生成 FCPXML 让他们打开 (用 opentimelineio)
Reference
- 模板路径:
templates/v16_moviepy.py(主拼接含智能过场) /templates/v15_simple_crossfade.py(简化版) - AutoDL 远程:
~/.ssh/configHostautodl, ComfyUI 双卡 6006/6010 - 工作流:
/root/zealman-app/comfyui-workflows/J视频-对口型/J04-(InfiniteTalk) +G视频-Wan图生/G01-(Wan I2V) - ui2api 转换器:
/tmp/ui2api_v2.py(重启后需 re-scp)
Anti-patterns
- ❌ ffmpeg xfade chain 串多个 transition (漂移 + 截断)
- ❌ MoviePy
concatenate_videoclips期望保持时间精确 (overlap 会引发漂移) - ❌ 锚段时间分配靠"累计上段长度"算 (每段实际长度 ≠ manifest 长度)
- ❌ 假设歌词不同版本节奏一样 (Suno 同首歌的不同 take 段间过门会变 5-10s,全部锚段必须按新音频重对齐时间窗后重跑 InfiniteTalk)
- ❌ 用堆叠多个 LightX2V LoRA 的工作流跑短镜 (风格保持极弱, 模型自由发挥跑成完全不相关的内容如古风/魔法/电光鸟)
- ❌ 跳过和 user 对齐主角身份就开 brief: 同一个中文词可能有多种解读 (如 "X 班"="工作班次" 还是 "X 场"="表演场所"), 先跟 user 确认主角是工人 / 表演者 / 路人, 避免做完整套 MV 后气质割裂返工