虽然在北国出差,到现在还无法玩到《荒野大镖客救赎 2》,但是可以先用原声带(OST)培养一下西部情绪啊!结果发现 Rockstar 至今还没正式发行原声带,而且连个预计发售日期都没有,这事儿办的可真的一点儿都不 Rock。

但是,幸运的是,有朋友在 YouTube 上发布了可能是从游戏中转录/抓取的音轨,我听了一下,质量还不错,可以暂时解解馋。

YouTube 下载下来的音频是 OPUS 格式,而且是包含所有音轨的单一文件。这样如果我特别喜欢其中一首,想单曲循环就比较麻烦。好在上传者提供了文本时间轴,所以我写了一个 Python 脚本按照时间轴把这个文件分割成了独立的 MP3,而且打上了 ID3 标签(专辑名称、专辑封面、艺术家、年代、轨道编号等等),比较完美地模拟了专辑的效果。

荒野大镖客救赎 2 原声音乐 iTunes 截图

脚本源代码

脚本的源代码放在了 GitHub 上

基本原理是,读取时间轴文件,获取起止时间戳和标题,使用 MPV 命令行切割转码,然后使用 eyeD3 模块来添加 ID3 标签。

理论上,类似这种有明确的时间轴,需要将一整个音频文件做分割的情况都可以使用。不过我暂时也只用了两次,一次是《荒野大镖客救赎2》,一次是《Ruiner》。

关于 eyeD3 模块的笔记

添加专辑封面的 API

直接用 MPV 分割出来的 MP3 文件没有任何 Metadata,这样在播放器里显示得就不太整洁,而且也不够美观。所以找到了 eyeD3 这个模块给每个音轨加 ID3 标签。API 是很直观的:

import eyed3

audiofile = eyed3.load("song.mp3")
audiofile.tag.artist = u"Integrity"
audiofile.tag.album = u"Humanity Is The Devil"
audiofile.tag.album_artist = u"Integrity"
audiofile.tag.title = u"Hollow"
audiofile.tag.track_num = 2

audiofile.tag.save()

但是上面的代码里,并没有提到添加专辑封面这个比较重要的 API,我猜测是因为这个 API 的参数相对比较复杂。

def set(self, type_, img_data, mime_type, description=u"", img_url=None):

一共有 5 个参数之多,而且文档里并没有详细说明,只能看看源码。

参数 解释
type_ 图片的类型,ID3 定义的有 21 种之多,真是大开眼界
img_data 图片的二进制数据
mime_type 图片的 MIME 类型,可以用 eyed3.utils.guessMimetype 函数从文件名获取
description 图片的说明,可为空
img_url 图片的链接,如果指定 URL,则 img_datamime_type 均可为空

于是如果接着上面的例子,可以这么写。

img = '/path/to/cover.jpg'

audiofile.tag.images.set(
    3, #FRONT_COVER
    open(img, 'rb').read(),
    eyed3.utils.guessMimetype(img),
    'ALBUM_COVER' #DESCRIPTION
)

audiofile.tag.save()

Logging 设置引发的血案

在写这个脚本的过程中,我发现自己的 log 怎么设置都无法正常输出,最后才发现是 eyeD3 模块作者挖的一个坑。

他在 eyed3.utils.log 子模块的全局 scope 中调用了 logging.basicConfig() 初始化 log 设置,这样所有输出都会导入到 stderr 里,我即便重新调用 basicConfig 都没用。

有人提了 issue,作者也改过了,不过没有 release,所以 PyPI 上也还是有问题的代码,不知道还会坑多少人😁。

荒野大镖客救赎 2 原声音乐 下载

分割出来的原声 一共 25 个 MP3 文件,上传到了百度盘。

荒野大镖客救赎 2 原声音乐 下载 | 提取码: lmkq