一首歌只换歌词不换旋律,5步全自动搞定

歌曲改词流水线主视觉:声波转化为音符再重组为新歌声
从原曲到新歌,中间隔着一条五步流水线

把一首歌的歌词换掉,旋律一个音都不改——这个需求听起来简单,做起来却踩穿了音频处理、MIDI 编码、歌声合成三个领域的坑。本文记录一次完整的实战:以成龙《醉拳》为素材,把歌词改成「怕老婆叨叨版」,用一条 5 步 Python 流水线全自动跑通。

文章不只讲「怎么做」,更讲「为什么这样做」——每一个坑的根因、每一种技术方案的取舍边界、以及「保留旋律」这四个字背后隐藏的物理约束。如果你也想做歌曲改词翻唱,这篇能帮你少走至少两周弯路。

一、需求拆解:保留旋律改歌词,到底难在哪

先说结论:「保留旋律改歌词」本质上是一个「音高时序不变、音色重生成」的问题。听起来只是一个文本替换,但它牵涉到三个层次的技术挑战。

第一层是分离。一首完整的歌是人声和伴奏混在一起的。要改人声的歌词,得先把人声单独拆出来,伴奏原封不动保留。这靠的不是简单的高通低通滤波,而是深度学习模型对频谱的精准建模。

第二层是旋律提取。人声拆出来后是一段音频波形,波形里没有「歌词」这个概念,只有频率和振幅随时间的变化。要把歌词替换掉,得先把这段波形翻译成机器能编辑的结构化数据——MIDI。MIDI 记录的是每个音的音高、起止时间、力度,但不记录「唱的是什么字」。

第三层是歌声重合成。新歌词塞进 MIDI 后,还需要把带歌词的 MIDI 重新唱出来。这一步是整个流水线里选择最多、限制也最多的一步——用虚拟歌姬?用 AI 声音克隆?还是用 Suno 这类大模型重新生成?每种方案的音色还原度、操作复杂度、版权风险完全不同。

关键认知
MIDI 只含音高和时序,不含音色。这意味着无论你怎么改歌词,重新合成的声音都不是原歌手的声线——除非额外做声音转换(So-VITS-SVC / RVC),那需要 GPU 和原歌手的纯净训练数据。这是「保留旋律」和「保留原声」之间的物理鸿沟。

二、流水线全景:5 步从原曲到新歌

整个流水线由 5 个 Python 脚本串接,每一步的输出是下一步的输入,中间产物全部落盘到 output 目录,方便单步调试和断点续跑。

STEP 01
人声分离
Demucs 把原曲拆成人声 + 伴奏
STEP 02
人声转 MIDI
basic-pitch 提取音高时序
STEP 03
歌词注入
把新歌词按音节塞进 MIDI
STEP 04
歌声合成
Synthesizer V 或 Suno 重新演唱
STEP 05
音频合并
FFmpeg 把新歌声和原伴奏合成

五步的技术栈分别是:Demucs(Meta 开源的人声分离模型)、basic-pitch(Spotify 开源的音高提取模型)、pretty_midi + mido(MIDI 读写库)、Synthesizer V / Suno API(歌声合成)、FFmpeg(音频合并)。全部本地可跑,只有 Suno 备选方案需要联网调 API。

实战案例选了成龙《醉拳》——这首歌节奏鲜明、人声清晰、音域适中,适合做技术验证。新歌词 368 字,原曲 MIDI 提取出 343 个音符,两者字数对齐是第三步歌词注入的核心挑战。

三、第一步人声分离:Demucs 拆出纯人声

Demucs 是 Meta(Facebook AI Research)开源的音源分离模型,能把一首歌拆成人声、鼓、贝斯、其他四个音轨。我们只需要人声和伴奏两个,但 Demucs 默认四轨全输出,取 vocals 和剩下三轨混合即可。

Python / demucs
# 01_separate.py 核心逻辑
from demucs.separate import main as demucs_main

demucs_main([
    "--two-stems", "vocals",  # 只分人声和伴奏两轨
    "-n", "htdemucs",          # 用最新模型
    "-o", output_dir,
    input_audio
])
# 产物:vocals.wav(纯人声)+ no_vocals.wav(纯伴奏)

这一步的坑不在代码,在依赖安装。Demucs 的 PyPI 包有两个隐形依赖不会自动装:julius(因果卷积库)和 huggingface-hub(模型下载器)。缺了前者,import 直接报 ModuleNotFoundError;缺了后者,模型下载阶段卡住。正确装法:

Shell
pip install demucs julius huggingface-hub
踩坑记录
Demucs 首次运行会下载约 80MB 的 htdemucs 模型权重。如果网络不稳定,可能卡在下载阶段。可手动从 HuggingFace 下载后放到 ~/.cache/torch/hub/checkpoints/ 目录。分离一段 3 分钟的歌,CPU 约需 2-3 分钟,有 GPU 则 10 秒内完成。

四、第二步人声转 MIDI:basic-pitch 提取旋律

basic-pitch 是 Spotify 开源的轻量级音高追踪模型,能把单旋律音频(人声、乐器独奏)转成 MIDI。它的优势是速度快、不需要 GPU、对歌唱人声的识别准确率在 90% 以上。

Python / basic-pitch
from basic_pitch.inference import predict_and_save

predict_and_save(
    output_dir,
    ["vocals.wav"],
    save_midi=True,
    sonify_midi=False,
    save_model_outputs=False,
    save_notes=False
)
# 产物:vocals.mid,包含音高、起止时间、力度

这里的坑比 Demucs 更深。basic-pitch 依赖 resampy 库做重采样,而 resampy 内部用了 pkg_resources——这个模块在 setuptools 83.0 以上版本被移除了。如果你用的是较新的 Python 环境(setuptools >= 83),import basic-pitch 会直接崩。

setuptools 83 移除 pkg_resources
resampy 的 _version.pyimport pkg_resources 读版本号,setuptools 83 把这个模块删了,导致整个 basic-pitch 无法 import。
解法:降级或改源
方案一:pip install setuptools<83 降级。方案二(推荐):改 resampy 源码,把 import pkg_resources 替换为 import importlib.resources,一劳永逸。

转出来的 MIDI 文件,用 pretty_midi 读取后会得到一个 instrument,里面有若干个 note,每个 note 有 pitch(音高,MIDI 编号 0-127)、start(开始时间,秒)、end(结束时间,秒)、velocity(力度)。这些 note 就是歌词要注入的「槽位」——每个 note 对应一个音节,一个字。

五、第三步歌词注入:中文编码的生死突围

这是整个流水线技术含量最高、踩坑最深的一步。目标很清晰:把新歌词的每个字,按顺序填进 MIDI 的每个 note 里。MIDI 标准里,note 的「歌词」是存在 meta message 的 lyrics 字段里的。但就是这一个字段,差点让整个流水线报废。

MIDI 中文歌词编码突围:字符流经过 UTF-8 过滤门净化
mido 默认用 latin-1 编码歌词,中文直接报 UnicodeEncodeError

5.1 字数对齐:368 字塞进 343 个音符

新歌词 368 字,MIDI 提取出 343 个音符。差 25 个字,怎么对齐?这不是简单截断能解决的——歌词有断句、有拖音、有衬词(「啊」「哦」),硬塞会导致音节和音高错位,听起来像乱码。

实战解法是音节映射策略:先按空格和标点把歌词切成词组,再按音节数把词组分配给音符簇。遇到字数多于音符的情况,把多出的字合并到相邻音符上(一个音符唱两个字);字数少于音符的情况,用空拍或延长音补齐。这个过程 03_modify_lyrics.py 自动处理,但对纯中文歌词(没有空格分词)效果一般,需要人工微调断句。

5.2 致命陷阱:mido 的 latin-1 编码

歌词对齐后,往 MIDI 写入中文时,mido 库直接抛出 UnicodeEncodeError: 'latin-1' codec can't encode characters。根因在 mido 的源码深处:它把所有 meta message 的字符串都用 latin-1 编码成字节——这个编码只支持 0-255 的拉丁字符,中文 Unicode 码点远超此范围。

翻遍 mido 文档没有配置项可以改编码。唯一的出路是 monkey-patch(运行时替换)——在 import mido 之后、使用之前,把它的编码函数偷换成 UTF-8 版本:

Python / mido monkey-patch
import mido

# === 关键 hack:强制 mido 用 UTF-8 编码字符串 ===
# mido.charset 默认是 'latin-1',不支持中文
# 直接替换模块级常量,所有后续编码都走 UTF-8
mido.midifiles.meta.setattr('charset', 'utf-8')

# 更稳的做法:替换 _encode_string 和 _decode_string
def _encode_string(text):
    return text.encode('utf-8')

def _decode_string(data):
    return data.decode('utf-8', errors='replace')

mido.midifiles.meta._encode_string = _encode_string
mido.midifiles.meta._decode_string = _decode_string
# 至此,中文歌词可正常写入 MIDI
为什么是 monkey-patch
mido 是 MIDI 文件读写的标准库,没有替代品。它的编码写死在源码里不是配置项,fork 改源码又不便维护。monkey-patch 在运行时替换函数引用,既不改原库代码,又能全局生效,是处理这类「库的硬编码行为与需求冲突」的标准手段。

5.3 歌词对照:从醉拳到叨叨

实战案例的歌词改写思路是「保留原曲节奏感,内容从武侠豪情转为家庭幽默」。截取副歌第一句对照:

原词 / 醉拳
我颠颠又倒倒 呀好比浪涛 有人的地方 就有恩怨 有恩怨 就有江湖 人就是江湖
新词 / 叨叨版
我忙忙又碌碌 呀好比陀螺 有家的地方 就有唠叨 有唠叨 就有规矩 家就是规矩

字数完全对齐,音节结构相近,注入 MIDI 后旋律不跑调。这种「语义对仗 + 字数一致」的改写策略,是歌词替换不违和的关键。

六、第四步歌声合成:虚拟歌姬与原声线的真相

带歌词的 MIDI 做好了,下一步是让它「唱出来」。这一步是方案分歧最大的一步,也是「保留旋律」这个需求最容易翻车的一步。

声音转换技术路径:虚拟歌姬声线与原歌手声线之间需要 GPU 转换
合成声线 ≠ 原声线,中间的鸿沟需要 So-VITS-SVC/RVC 跨越

三种主流方案的对比如下:

方案 保留旋律 保留原声线 操作难度 适用场景
Synthesizer V 完全保留 声线变为虚拟歌姬 GUI 操作,无 CLI 高质量翻唱,接受换声线
Suno API 重新作曲 AI 生成新声线 API 调用,全自动 快速demo,不要求原旋律
So-VITS-SVC / RVC 保留 克隆原声线 需 GPU + 训练数据 最高还原度翻唱

6.1 Synthesizer V:质量最高但无 CLI

Synthesizer V(简称 SV)是当前最强的虚拟歌姬引擎,音质逼真、中文支持好。问题是它只有 GUI,没有命令行接口,无法自动化。免费版已下架,Studio 版需付费。在流水线里,这一步只能人工导入 MIDI、选声库、导出音频,是全自动流程里唯一的「人肉断点」。

6.2 Suno API:能自动化但会跑调

Suno 是当前最火的 AI 音乐生成大模型,有 API 可调。但用它做「保留旋律改歌词」有个致命问题:Suno 的 Cover 模式(上传原曲+新歌词)会被版权指纹系统拦截。绕过方式是用 custom_mode 直接传歌词让它重新生成——但这样一来,旋律就是 Suno 重新作的,不是原曲的。

Python / Suno API
# 04_synthesize.py 中的 Suno 备选方案
payload = {
    "custom_mode": True,           # 绕过 Cover 模式的版权检测
    "prompt": "",                  # 不用风格提示,避免重作曲
    "lyrics": new_lyrics,          # 新歌词内容
    "title": "醉拳叨叨版",
    "make_instrumental": False
}
# 注意:custom_mode 下 Suno 会重新生成旋律
# 无法保证与原曲旋律一致,这是 Suno 方案的硬伤

6.3 终极方案:So-VITS-SVC 声音克隆

如果一定要「既保留旋律、又保留原歌手声线」,技术路径是:Synthesizer V 合成虚拟歌姬演唱 → So-VITS-SVC 把虚拟声线转换回原歌手声线。这需要用原歌手的纯人声数据训练一个声线模型,训练需要 GPU(至少 8GB 显存)和 10-30 分钟的纯净人声素材。

版权提醒
声音克隆涉及原歌手的声音权。个人研究学习属于合理使用范围,但公开传播克隆声音翻唱的作品可能侵犯表演者权。Suno 的版权指纹拦截正是为规避这一风险设计的。实战中请明确使用边界。

七、第五步合并与实战反思:从跑通到好用

最后一步最简单:用 FFmpeg 把新合成的歌声和第一步分离出的原伴奏合并成完整歌曲。

Shell / FFmpeg
# 05_merge.py 核心命令
ffmpeg -i new_vocals.wav -i no_vocals.wav \
  -filter_complex "[0:a]volume=1.2[a1];[1:a]volume=0.9[a2];
  [a1][a2]amix=inputs=2:duration=longest" \
  -acodec libmp3lame -q:a 2 output.mp3
# volume 调整人声和伴奏比例,amix 混音

合并时的关键参数是人声和伴奏的音量比例。原曲混音时人声通常略大于伴奏,翻唱合成的人声偏「干净」缺乏混响,需要适当提亮(+1.2 倍)并给伴奏降一点(0.9 倍),听感才平衡。如果合成的歌声干涩,可额外加 aechoaformat 滤镜做简单混响。

7.1 质量评估:三个维度

跑通流水线后,从三个维度评估成品质量:

维度 评估方法 本次实战表现
旋律保真度 对比原曲和新歌声的 MIDI 音高序列 100% 一致(MIDI 直接复用)
歌词对齐度 人工听辨每个字的起止是否与音符匹配 约 85%,25 处需人工微调断句
声线还原度 与原歌手声纹对比(主观+频谱) 0%(虚拟歌姬声线,需 SVC 补足)

7.2 改进方向

这条流水线跑通了,但离「好用」还有距离。三个可改进的方向:

第一,歌词对齐自动化。当前中文歌词断句靠人工,可引入拼音分词 + 音节计数,让脚本自动把中文字符映射到 MIDI 音符簇。难点在于中文一字一音节,但 MIDI 音符有拖音和连音,需要区分「一个字占两个音符」和「两个字占一个音符」。

第二,Synthesizer V 自动化。SV 没有 CLI 是全自动流程的唯一断点。可以用 AutoHotkey 或 pyautogui 做 GUI 自动化脚本,模拟导入 MIDI、选声库、导出音频的操作。但 SV 的 UI 结构复杂,脚本脆弱性高,版本更新易失效。

第三,声线还原闭环。接入 So-VITS-SVC 做声音转换,让最终成品保留原歌手声线。这需要额外搭建 SVC 训练和推理环境,GPU 是硬门槛。没有 GPU 的环境,只能接受虚拟歌姬声线或用 Suno 重生成。

7.3 成本与收益

实战成本清单
纯 CPU 环境跑完整条流水线(不含 SV 人工操作),3 分钟的歌约耗时 5 分钟:Demucs 分离 2-3 分钟,basic-pitch 转 MIDI 30 秒,歌词注入 1 秒,FFmpeg 合并 5 秒。环境搭建踩坑耗时远超运行——basic-pitch 和 mido 的两个坑,从遇到到解决花了整整两天。希望本文能帮你省下这两天。

回到开头的问题:一首歌只换歌词不换旋律,5 步能不能搞定?答案是能跑通,但每一步都有它的脾气。Demucs 的依赖缺失、basic-pitch 的 setuptools 冲突、mido 的中文编码、Synthesizer V 的无 CLI、Suno 的版权拦截——这五个坑,每一个都能让流水线停摆。但只要跨过去,你就有了一条可以从任何歌曲生成改词翻唱的自动化产线。

更重要的是,这条产线的每一步都是可替换的模块。Demucs 可以换成 Spleeter,basic-pitch 可以换成 SPICE,Synthesizer V 可以换成 ACE 虚拟歌姬,Suno 可以换成其他音乐大模型。理解了每一步「做什么」和「为什么这么做」,技术的更新换代不会让这套方法失效——你只是在换零件,不是换引擎。

参考资料

  1. Meta AI Research, «Demucs: Hybrid Spectral and Waveform Domain Music Source Separation», GitHub: facebookresearch/demucs https://github.com/facebookresearch/demucs
  2. Spotify Research, «basic-pitch: A lightweight pitch tracking model», GitHub: spotify/basic-pitch https://github.com/spotify/basic-pitch
  3. mido: MIDI Objects for Python, Read the Docs https://mido.readthedocs.io/
  4. Dreamtonics, «Synthesizer V Studio», 官方网站 https://dreamtonics.com/synthesizerv/
  5. Suno API 文档,Suno Inc. https://docs.suno.ai/
  6. FFmpeg Filters Documentation https://ffmpeg.org/ffmpeg-filters.html