歌词 MV 成片(视频制作)
把"一首歌 + 一份歌词"变成可直接发布的成片。整链路实战验证:音频 → 分镜图 → 合成 → 质检 → 封面。
音频 ──► 分镜图 ──► build_lyric_mv.py ──► qc_subtitle.py ──► make_cover.py
(带字幕的成片) (平台封面)
安装自检(装完先跑一遍)
本技能是脚本驱动的:文档只是说明书,真正干活的是 scripts/ 下的 5 个脚本与 assets/ 下的配置模板。安装后先确认文件齐全:
ls scripts/ # 应有 5 个:build_lyric_mv.py / qc_subtitle.py / make_cover.py / fetch_works.js / dl_audio.js
ls assets/ # 应有 1 个:config_template.json
任何文件缺失 = 安装包不完整(多为市场同步或安装异常导致),此时不要按文档继续执行——直接重装本技能;重装后仍缺失请反馈给作者。缺脚本最典型的症状:文档每一步都读得懂,但一运行就报"找不到文件/脚本"。
环境准备
# Python 隔离环境 + ffmpeg(无系统 ffmpeg 时,用 imageio-ffmpeg 自带的)
python -m venv <venv目录>
<venv>/Scripts/python.exe -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple imageio-ffmpeg pillow
# playwright-core(仅"妙响抓音频"模块需要,纯本地音频可跳过)
npm install --prefix <node工作区> playwright-core
- ffmpeg 由脚本自动解析:环境变量
FFMPEG_BIN→imageio_ffmpeg.get_ffmpeg_exe()→ 系统 PATH,无需写死路径 - 中文字体:默认 ASS 样式用
Microsoft YaHei(Windows 自带C:/Windows/Fonts/msyh.ttc)。
其他系统把 ASS 样式里的Fontname改成本机已装中文字体名(如PingFang SC、Noto Sans CJK SC),
字体名对不上时 libass 会静默回退、字幕不渲染(质检 Step 4 能兜住) - 所有脚本支持环境变量配置,无硬编码个人路径(详见各脚本头部说明)
Step 1 — 准备音频(两条路线)
路线 A:本地音频(推荐起点)
已有 mp3/wav 文件时直接进入 Step 2,放一份到项目 audio/ 目录即可。
逐行时间轴可用任何 LRC 文件,或按听感手动标注(config 里的 lines)。
路线 B:妙响平台音频(可选模块,需登录态)
妙响(music.douyin.com)不提供下载,唯一可行路径是在详情页触发播放,抓真实音频流:
node scripts/fetch_works.js <歌名关键词> # → 输出 SongID / LRC 时间轴(路径见环境变量 VM_WORKS_JSON)
node scripts/dl_audio.js "https://music.douyin.com/console/songs/detail/<SongID>" <输出名> [输出目录]
关键字段:SongID(拼详情页 URL)、Lyrics(提交的正确歌词)、LyricsDetail.Lrc(AI 实际演唱的逐行时间轴)。
原理:详情页点播放后,音频以 content-type: audio/mpeg 从 *.douyinvod.com 返回,用
page.request.get(url, {headers:{Referer:'https://music.douyin.com/'}}) 下载(约 128kbps / 1.5-2MB 一首)。
⚠️ 音频下载的两个坑(都踩过)
- 详情页会预载 27MB 的
home_video.mp4宣传视频,content-type 也是媒体类型,会被误抓。
脚本已加校验:只接受 0.8~12MB 的文件,并优先content-type: audio/*。
正常一首歌 128kbps / 1.5~2 分钟 ≈ 1.5~3.5MB。抓到 20MB+ 一定是抓手错了。 - 必须有登录态:脚本用 playwright 持久化 profile(默认
~/.workbuddy/ab-pw-profile,
可用环境变量VM_PW_PROFILE改)。首次使用先在该 profile 里完成妙响登录。
🩸 选版铁律:一次生成两版,全链路只认"生成早的那版"(v1.2.2 定稿)
一次生成产出 2 个版本(同一 TaskID、两条记录、曲名各不相同),两条记录的 LRC 时间轴互不通用。
音频、LRC、曲名、时长必须全部来自同一条选定记录——混用两版 = 字幕与演唱整体错位(用户可见事故)。
选定规则:同 TaskID 组内取 CreateTime 最早,同秒按 WorkID 数值较小者(雪花 ID 单调递增 = 生成顺序)。
fetch_works.js 已内置自动选版:运行后读 ab-works-chosen.json(VM_CHOSEN_JSON 可改路径)拿选定记录;
资产页抓流时点【选定记录曲名】的卡片,抓完 ffprobe 对账(偏差 >1.5s = 抓错版本,换卡片重抓)。
⚠️ 未发行歌(SongID=0)的三个新坑(2026-09-27 实战)
- 同词多版本、曲名不同:一次生成 2 版,AI 给两版各起一个曲名,work-list 里两条是不同录音,LRC 互不通用。按关键词抓流务必确认抓到的就是打算用的那条。
- 抓流后必须 ffprobe 对账:实际时长 vs work-list 元数据 Duration(ms),对不上就不用——资产页抓流可能抓到与记录不符的渲染,导致字幕 LRC 与演唱完全错位(用户可见事故)。兜底:用 250-3400Hz 人声频段逐秒 RMS 找首句进入点判定真实版本;确属目标版本但前奏不同长时,可整体平移 LRC(实测 Δ=+7.6s 案例)。
- 改 scene dur 必须重跑 seg:seg 是按 dur 生成的固定长度段,只改 dur 直接 mix 会因 seg 提前 EOF 输出截断视频且不报错。改 dur → seg → mix 全链重跑;验收必须
ffmpeg -i out.mp4 -f null -核真实时长 + 人工抽帧核对画面与字幕的语义对应(亮像素统计发现不了语义错位)。
Step 2 — 生成分镜图
用 ImageGen 生成 5-8 张竖屏图(size: 1024x1536),风格必须统一。有效 prompt 模板:
Vertical cinematic: <场景描述>, teal-green cinematic color grading, soft mist,
subtle film grain, photorealistic, minimalist, no text, no watermark
- 场景按歌词情绪切:都市夜景 → 雨中细节 → 自然(山涧/海)→ 抽象(水墨/涟漪)→ 收尾空镜
- 图统一落到项目
scenes/目录 - ⚠️ AI 生成图底部常有平台水印,合成时用
crop_bottom_pct裁掉(默认 0.10)
Step 3 — 编写 config 并合成
复制 assets/config_template.json 为项目的 config.json,填入音频/图片路径与歌词行,然后:
<venv python> scripts/build_lyric_mv.py config.json all # 或 ass / seg / mix 分段重跑
字幕样式(ASS 里预置 6 种,可自行调字号/颜色):
| 样式 | 用途 |
|---|---|
Title / Title2 |
片头(出处 / 歌名),顶部大字号 |
Main |
主歌白话,白字 |
Gold |
副歌 / 情绪高点,暖金色(BGR &H006ED8F5) |
Quote |
原文金句,字号更大、字距更宽 |
Tail |
尾奏署名 |
lines 编写规范(时间轴取自 LRC,文本用最终确认的正确歌词):
{"start": 12.12, "end": 17.96, "style": "Main", "text": "写字楼的灯 换了月亮"}
- 中文行内用全角空格
做停顿分隔,比半角稳 - LRC 最后一行常是 AI 把 2-3 句挤在一起,要拆成多行依次显示,否则结尾 20 秒只有一句字幕
- 片头从 1.2s 起(留一口气),尾奏最后 6 秒放署名
- 各 scene 的
dur只需给相对比例,合成时按音频时长自动缩放对齐
Step 4 — 字幕质检(必做)
<venv python> scripts/qc_subtitle.py <成片.mp4> [--points 3.5,20,40] [--band 0.76,0.92]
抽帧 + Pillow 统计字幕区(默认 0.76H~0.92H)亮像素:
- white% 1-7% 且 gold% 有值 → 字幕正常
- white% 全 0 → libass 没找到字体,检查 ASS 的 Fontname 是否为本机已装中文字体
- 整帧平均亮度长期 < 15 → 画面过暗或黑屏
Step 5 — 抽封面(发布前必做)
<venv python> scripts/make_cover.py <成片.mp4> 12.5 cover.jpg --crop-bottom 0.10 --width 1080
--crop-bottom 裁掉水印;各平台封面尺寸要求不同,--width 按需调。
交付前必查
ffmpeg -i out.mp4确认:时长 ≈ 音频时长、分辨率与 config 一致、帧率正确、有 Audio 流- 抽帧看字幕(Step 4)
- 告知用户 AI 演唱咬字偏差:AI 合成音对中文歌词常有偏差(实测出现"争抢→逞强""利万物→离万物"这类误唱),
字幕是正确文本、唱出来可能是另一个字,副歌金句处最明显。给出"改写易错字 / 换模型 / 接受"三个选项
衍生路线:15 秒钩子切片
短视频平台适合先发 15-22 秒的副歌钩子引流,完整 MV(约 2 分钟)做正片。从成片音频裁副歌段:
FF=$(python -c "import imageio_ffmpeg;print(imageio_ffmpeg.get_ffmpeg_exe())")
"$FF" -y -ss 58.2 -t 15 -i audio/song.mp3 \
-af "afade=t=in:d=0.4,afade=t=out:st=13.5:d=1.5" \
-c:a aac -b:a 192k hook_audio.m4a
"$FF" -y -i hook.mp4 -i hook_audio.m4a -map 0:v -map 1:a -c:v copy -c:a aac -shortest hook_with_audio.mp4
> ⚠️ 血的教训(2026-09-16):钩子流水线原设计输出无声视频、指望"站内挂 BGM",结果视频号根本没有歌可挂、抖音也忘了挂——发出去全是哑巴视频。
> 此后一律直接把歌曲片段烧进音轨(淡入 0.4s / 淡出 1.5s),不做无声版。
已知不可行的路径(别再试)
- ❌ 抖音
aweme/v1/play/?video_id=<VID>→ 全部 404 - ❌ 妙响音乐编辑器的"素材"库 → 只有本地/Beats/Sample,无法导入资产歌曲
- ❌ 妙响资产卡片的"更多"菜单 → 无下载项
- ❌ 妙响平台自带下载 →
Downloadable: false