用 Opus 5.5 给 Prince 做了一支歌词可视化视频

· 杂 · #Claude, #Prince, #Windows, #音乐可视化

最近一直在循环 Prince 的《Illusion, Coma, Pimp & Circumstance》,出自 2004 年的专辑《Musicology》。鼓和贝斯咬得很紧,越听越想给它配点画面,就让 Claude Opus 5.5 做了一支歌词可视化视频,前后改了三版。

视频投稿在这里:【Opus5.5可视化歌曲 Illusion, Coma, Pimp & Circumstance - Prince】

我的分工是提需求、看预览、挑毛病,代码、音频分析和渲染都是 Opus 5.5 做的。下面主要记两件事:Windows 那台笔记本具体干了什么,以及视频是怎么一版一版改出来的。

为什么要用 Windows

一开始全部在 Mac 上跑。第一版渲染时,Opus 5.5 直接开了 8 个渲染进程,每个进程还各带一个 1080p 的 x264 编码器。我这台 Mac 只有 8 GB 内存,当场卡死了很久,之后临时目录也被清空,脚本和分析结果全没了。

后来改成两台机器分工:Mac 负责写代码、做前期分析、抽帧检查;重活交给局域网里的 Windows 笔记本。Mac 上配好了 ssh win 免密登录,Opus 5.5 全程通过 SSH 在 Windows 上跑命令,我只在中途关了几个后台程序腾内存。

动手前它先查了一遍配置:i7-12700H(14 核 20 线程)、16 GB 内存、RTX 3060 Laptop。那时我开着微信和一堆软件,空闲内存只有 4.7 GB,它按这个算并行数。我关掉一些后台,空闲涨到 9.8 GB,才开始真正渲染。

Windows 上装了什么

中间有个小问题:torchaudio 在这台机器上没有可用的音频读取后端,读 wav 直接报错。它改用 scipy 的 wavfile 读取,再转成 tensor。

所有文件都放在 E:\prince_mv:渲染脚本、音频、字体、歌词,以及后面生成的分轨和对齐数据。

节拍和鼓点

节拍网格是在 Mac 上用 numpy 算的:先求频谱通量作为起音曲线,再穷举拍长和起点,找出最吻合的一组,结果是每拍 0.59475 秒,约 100.9 BPM。这首歌速度很稳,一个固定网格就能从头对到尾。

鼓点交给 Windows。demucs 的 htdemucs 模型在 RTX 3060 上跑一遍只要 13 秒左右,就能把整首歌拆成人声、鼓、贝斯和其他乐器四轨。然后用一个 stems.py 按视频帧率(每帧 1/30 秒)分析各轨:

再用 scipy.signal.find_peaks 找出每一下底鼓和军鼓(各四百多下),做成按指数衰减的包络,画面就能"敲一下、慢慢回落"。底鼓管整体脉动和色差,军鼓管镜头微抖和追光闪亮,踩镲管亮片多少,贝斯管频谱柱高度和黑胶纹路的亮度。

有了分轨,还顺手纠正了一个错误。最早用整首歌的低频猜小节的"1",猜偏了两拍。统计分离出的底鼓里最重的那些落在哪一拍,才找准真正的重拍;之前根据歌词推出的几个段落起点,换算后也正好落在这一拍上。

歌词怎么对上

歌词用的是网易云的 LRC,只有每句的开始时间。

先是整句时间。我看第二版预览时觉得字比歌慢。Opus 5.5 把每句时间戳和人声频段的起音做了比对,发现整首歌都晚了大约半秒,而且从头到尾差不多,不是越来越慢,于是全部提前 0.65 秒。

然后是逐词时间,这一步在 Windows 上做:

  1. 用 demucs 分出的纯人声,跑 MMS_FA 强制对齐。这是基于 wav2vec2 的 CTC 对齐,给定歌词文本,算出每个词在音频里的开始时间。
  2. 对齐前把 Prince 的拼写换成实际读音,比如 2→to、4→for、U→you、Eye→I,不然模型对不上。
  3. 第一次按单句切音频,相邻句的人声会混进来。第三次副歌开头两个词就被对到了前面桥段的结尾,打印人声能量一看确实是错的。改成按整段对齐,并把窗口收紧到时间戳前 0.6 秒,就对上了。
  4. 副歌一共唱了五次,唱法基本一样。把每个词换算成"第几拍"后,五次之间的差距大多不到 0.1 拍。用一个 refine.py 取五次的中位数,修正了 14 个明显偏离的词。

渲染

整个视频是 Python 逐帧画出来的:Pillow 画图形和文字,numpy 做色差、暗角、扫描线和颗粒这类后期,每一帧以原始 RGB 数据通过管道送给 ffmpeg 的 libx264 编码(CRF 18)。

整首 8580 帧切成 7 段,7 个进程同时渲染,最后用 ffmpeg 拼接并合并原音轨。7 个进程加编码器只占两三 GB 内存,一次完整渲染大约 12 到 15 分钟。

最后一版渲染快结束时,Mac 和 Windows 之间的 SSH 连接断了一次,成片没有生成。Opus 5.5 先查了 Windows 上的进程和 7 个分片的帧数,加起来正好 8580 帧,说明被打断的只是最后合并音轨那一步,单独补跑一次就好了。

修改过程

第一版

结构很简单:四种场景(环形频谱、单词闪烁、地平线频谱、半调圆点)每 8 小节轮换一次,歌词条放在画面下方。

看完有两个问题:画面基本是循环的,看第三遍和第一遍没区别;有几段歌词闪来闪去。后者是因为 LRC 里有很多不到 2 秒的空行,歌词条就跟着反复消失、出现;单词闪那个场景还每拍翻一次全屏底色。

第二版

这一版来回改了好几轮,都是围绕歌词时间:

第三版

我截了两张图给它看:一张在主歌里突然冒出一个大大的"PRINCE",一张在间奏里冒出"PIMP"。这些大字是按节拍轮播的装饰词,和正在唱的内容没关系。

第三版改了四件事:

这一版也踩了一个坑:整屏大字下面一直透出那一句的暗字。查下来是 Pillow 的 ImageDraw.Draw(img) 在 RGBA 图上画半透明文字时不做混合,而是把透明度直接写进像素,最后转成 RGB 时透明度被丢掉,字就成了实心的。也就是说,之前副歌文字的淡入淡出其实一直没生效。改成 ImageDraw.Draw(img, 'RGBA') 之后才正常。

另外,追光场景去掉轮播的大字以后显得很空。最后在底部加了一排跟着底鼓点头的观众剪影,顶部加了灯架,空中加了随贝斯起伏的烟雾。黑胶唱片右侧那列忽亮忽暗的词,也换成了静态的"正在播放"信息。

最后

回头看,几处关键的改动都是看预览时发现的:字慢了半秒、某句唱了一半才出来、某个大字出现得莫名其妙。所以后来每次都先出二十秒的预览,确认没问题再渲染整首。

脚本和数据都还留在 Windows 上,以后换一首歌,节拍、分轨、对齐这一套大多可以直接复用,主要重做的是段落表和那张读音替换表。

← 全部文章