Lol Audio Unpack:这些年折腾的英雄联盟音频工具

4685 字
23 分钟
Lol Audio Unpack:这些年折腾的英雄联盟音频工具

最近给这个工具补好了独立文档,也录了一份操作视频,想着博客里还是应该有一篇文章,把它为什么会出现、这些年又为什么一直改,稍微说清楚。

现在的名字是 Lol Audio Unpack,GitHub 地址还是 lol_extract_voice。早期确实主要提取英雄语音,后来地图、音效、音乐也都加进来了,名字跟着改了,仓库地址就没动。

使用方法已经放在文档里,配了截图,这里就不再从选目录开始讲了。这篇主要回顾我自己怎么折腾到现在。时间隔得太久,有些早期细节只能凭印象说。

录音录烦了#

大概 2015 年那会儿,B 站的鬼畜很火,我觉得挺有意思,就跟着教程学了一点。刚开始用的都是教程作者给的素材,学完总想自己做一个。当时也在玩英雄联盟,就想拿某个英雄的台词做鬼畜,或者剪个混剪。

想做视频,先得有素材。最早的方法很直接,进游戏录。

录了一阵就烦了。有些英雄话很多,遇到不同英雄有不同台词,换个皮肤又有一套,对同系列皮肤还可能有特殊互动。为了录一句话,得先把触发条件凑齐,凑齐了也不一定马上说。反正我当时没有耐心把这些都录一遍。

于是开始研究能不能解包。以前虽然没真正做过游戏 MOD,但多少知道一点,声音总归要存在游戏文件里,找出来就不用在游戏里等它开口了。

早期的文件结构和现在不一样,我记得还能比较直接地找到 BNK、WPK 这些音频容器,也有别人写好的工具能打开、导出。后来资源逐渐换成 WAD,操作又多了一层。再后来用到了 Obsidian,就是英雄联盟解包的那个黑曜石。

我印象里是 2017 年前后开始折腾脚本,不过那时并没有把整套流程都接起来,WAD 怎么处理也是后来慢慢研究的。现在仓库能查到的最早一条提交是 2019 年 3 月 8 日,前面自己折腾的那段没有完整留在 Git 历史里。

一个英雄一个英雄地补#

  1. V1 做的事情,就是拿脚本把已有工具串起来。手工能做的事情,让它替我跑一遍。

  2. 那时重心还在视频上。先拿到一个英雄的素材,脚本就停在那儿,我去剪视频。剪着剪着发现,只用一个英雄太平了,没什么东西可剪,又回来补其他英雄。

    补的时候就发现,有些英雄的路径和前一个不一样,原来的处理方式套不上,又得研究、改脚本。就这么一点点补,慢慢把英雄都加上了。并没有一开始就想好要做一个多完整的工具,纯粹是做视频时缺什么,回来补什么。

  3. 英雄处理完,发现地图里也有声音。地图比英雄麻烦多了,播报、技能和装备音效、背景音乐,还有一堆零碎声音混在一起。拿到文件以后,找自己需要的东西又成了新的问题。

  4. 到了 V2,程序已经是按批量处理写的了,从入口就奔着所有英雄一起解包去。想只处理某一个英雄,反而没那么方便。

那时还没有命令行入口,参数都是在主脚本里改。想换一个英雄,就打开脚本,填它的 WAD 路径,再改这次需要处理什么。自己用知道改哪里,别人拿到就不一定了。后来也有人在 Issues 里问怎么用、怎么只处理一部分,那会儿已经过去好多年,程序还带着最早那种自用脚本的习惯。

2019 年我发过一个手工解包的视频,也把处理好的文件分享了出来。资源后来断断续续更新了好几年,直到 2025 年底左右才停下来。这里面还绕过一圈试听网站和网盘,后面再说。

V3 和配置优先级#

V3 基本上重写了一遍。V2 的条理不多,能用就行,很多东西放在一起,后来想改就越来越费劲。

这次稍微把模块分清楚了,哪个部分准备数据,哪个部分解包,哪个部分转码,各自该放什么,至少在这个项目里能解释得通。拿去跟成熟项目比肯定还有差距,但自己回来看,总算知道该去哪里改。

命令行也是这一轮慢慢补的。至少不用每次先打开 Python 文件改路径,换个英雄、换个输出目录,传参数就行。可是入口加着加着,又把配置弄复杂了。

最开始想得很全:环境变量、配置文件、命令行都支持。真正开始处理优先级才头疼。用户在命令行写了几个参数,后面又加一个 -c 指定配置文件,环境变量里还留着以前的值,到底该用哪一份?

开发者可以规定谁覆盖谁,可使用者未必按这个规则理解。他可能觉得几种配置是互补的,哪儿缺就从另一份拿;也可能早就忘了环境变量里还放着一个旧目录。最后程序读到了一个值,用户却不知道它从哪儿来的。

现在做了取舍,命令行参数和配置文件分开用,使用 -c 就从配置文件读取动作和参数,不再跟命令行选项混着覆盖。那段三种来源叠在一起的逻辑,处理起来费劲,用起来也没省多少事。

从补全到 Agent#

中间停过一段时间。后来有了 AI 辅助,最早用的是 Cursor 那种补全,写个注释,按 Tab 补一段,再自己测试。有时候好用,有时候还得花时间去改,进度也没有一下子快起来。

再后来有了能改文件、跑检查的 Agent,之前积着的东西才推进得快了一些,界面也做出来了。这个界面又刚好接上了很早以前的一个想法:找个英雄,直接试听它的声音。

试听网站和网盘#

2020 年疫情那会儿,我想过做一个英雄台词试听网站。脚本已经能解包,声音也有了,找个地方存起来,再做一个网页,按英雄点开听,看起来没有多复杂。

麻烦的是资源放哪儿。

试过把资源放到 GitHub,再借 CDN 或 Cloudflare 之类的服务来访问。测试版也做过,但国内访问不稳定,后来找不到合适的替代办法。现在回头看,这种用法本来也不适合长期依赖。我只是个人开发者,纯爱好,没什么收入,没办法一直花钱养一个音频站。

NOTE

WAV 的体积

那时候分享的是 WAV,没有再做有损压缩。我自己听过压缩后的效果,觉得有些声音不太对,就想保留好一点的素材。结果光英雄和地图里的台词,印象中就有三十多 GB。

单条音频看起来没多大,试听时却是一条接一条地点。更别说有人需要全部下载,写个爬虫跑一遍就是整份资源的流量。对使用者来说只是下载,对我来说 CDN 的账单还得照付。真放到付费 CDN 上试过一段时间,后来还是放弃了,纯烧钱,还看不到什么成果。

网盘也折腾得不轻。文件解包、分类、压缩、上传,好不容易分享出来,链接过一阵又失效。我不知道它具体在检测什么,只能按自己碰到的情况试。压缩包加密以后有时能多撑一阵,但也没有彻底解决。

后来改成分享原始 WEM,体积小了很多,上传和下载都轻松一些。WEM 是游戏里用的音频文件,常用的剪辑软件不能像 WAV 一样直接拿来用,还得转码。

我找到了 vgmstream,又改了一下它的控制台程序,让它能扫描目录、按规则批量输出。文件太多,靠命令行一个个填进去肯定不现实。这部分后来也单独留成了一个项目。

文件还是继续更新着,但有时候已经只是习惯性更新了。网盘总出问题,我也看不清到底有多少人下载、还有多少人在用。

本地明明就有游戏文件#

中间我还想过把程序改成 Node.js,做成网页:选择本地游戏目录,解包完直接在网页里试听。当时没有把这个想法做完,但这个方向后来一直记着。

到准备做 GUI 的时候,再回头看分享资源这件事,就觉得绕了很大一圈。

很多来找音频的人本身就在玩英雄联盟,电脑里已经有游戏客户端。我从自己的客户端解包,转成 WAV,压缩、上传,再让别人下载、解压。他本地已经有这些声音了,我还让他下一遍,折腾什么呢。

当然,也有人没有安装游戏,或者专门想找重做前的旧语音,那是另外一种情况。程序本身需要本地资源,准备好符合要求的旧版本文件,也可以交给它处理,不会替用户下载客户端。

但对已经有游戏文件的人来说,给他一个界面,选好目录,处理完就能找、能听,需要哪几条再导出,明显方便得多。我也不用再维护一堆网盘链接。

Lol Audio Unpack 实体总览,在常规地图中搜索心之钢事件并试听音效

当前界面截图,取自项目文档。图中是在常规地图里查找心之钢音效。

这也比较符合实际需求。很多人只是想找个金身音效、地图标记声,或者某个英雄的几句台词,用不着下载整份英雄或地图音频包。以前分享时总得按英雄、地图这些单位打包,不可能一个音效一个压缩包。现在直接在本地找,选几条导出就行。

虽然还是 Python 写的,但已经优化过几轮,解包速度对我来说够用了。我之前在自己电脑上试过,全部台词语音的解包加转码大概十五分钟,后来又做了一些优化。这个只是那次处理的耗时,不包括把所有音效都转一遍。

全量处理里,转码通常更慢,尤其音效文件又小又多,CPU 和硬盘都可能卡住,换台电脑、换一批资源,耗时也会变。

能只转自己挑出来的几条,省下来的时间比继续死磕全量转码速度还明显。

导出选中音频前,核对数量、目录和采样格式

试听后只导出自己需要的声音,不用先把整个资源包转成 WAV。

用 Obsidian 加其他工具手工解包,当然也能拿到这些文件。我写这个程序,一直就是为了省这些重复操作,现在界面把找素材的过程也接上了。

视频没怎么做,工具还在改#

写到现在,其实早就偏离最初做鬼畜的目的了。那时遇到一个问题就去改脚本,后来反而花更多时间研究声音怎么整理、程序怎么写,视频没做出多少。

也有我自己的原因。早期看鬼畜,会觉得什么素材都能拿来剪,老素材也能玩出新东西。后来我看的不少视频,更像把最近火的梗揉在一起,做一段时间的汇总。我跟这些热梗的速度没有那么快,有些看了解释,也还是不知道笑点在哪儿。

这只是我自己的理解,倒不是说笑点有多高。做视频总得先知道自己在笑什么,我自己都没弄懂,就更不知道该怎么剪给别人看。慢慢没什么灵感,重心也就移到工具上了。

以前想做的试听网站,最后没有按网站的形式留下来。现在这个本地界面,倒是让我能找个英雄,点开听,挑几条导出去用了。当年的那个需求算是有了一个能用的结果。

事件名和台词,还有两个坑#

文件能解出来,能播放,接下来还得知道它是什么。

事件名的翻译#

现在可以按事件查找,但事件名是游戏内部用的,有些新英雄命名比较规整,老英雄和旧资源就不一定了。有缩写,有历史命名,也有光看英文猜不出来的东西。词典能翻译单词,未必能告诉我它在这个英雄身上代表什么。

我想做的是把这些事件整理成人能看懂的描述,至少知道大概怎么触发、该往哪里找。以后如果能整理好,也可以再做不同语言的显示。这个方向放在 lol-event-semantics 里,目前还在慢慢研究。

难点是数量太多,还需要理解英雄机制和皮肤的特殊互动。一个个靠人工校对,工作量太大,而且我也不是每个英雄都懂。交给大模型试过一些,效果还没有好到能直接全部用上,逐条核对又要花很多时间和 token,只能慢慢来。

另外一个坑是语音转文本。#

2020 年那篇台词整理文章里就试过语音识别,后来也试过腾讯、讯飞这些服务,识别完再人工听、核对。

普通人说话还好,英雄联盟里很大一部分英雄设定上就不是人,说话方式、口音、后期特效都有,光靠自动识别不太放心。人工也不是听一遍就保证正确,还是得反复核验。

后来陆续人工核对过大概一百个英雄,花了不少时间。几个游戏版本过去,再解包一遍,原来的音频 ID 对不上了。

举个例子,同一句话这个版本叫 12345.wem,下个版本可能换成 45678.wem;反过来,同一个 ID 下的文件内容也可能变化。如果把整理好的台词直接挂在 ID 上,后面更新时就容易错位。那会儿还不知道会这样,发现自己处理过的对应关系不能直接继续用,就停下来了。

后来专门做了历史版本的实测,确认这两种情况都存在。现在资源库按原始 WEM 的实际内容做哈希,同样的字节内容可以复用,不同版本的 ID 则分别记录它们指向什么。

当前的做法是,原始 WEM 内容完全一样,就只存一份,通过硬链接让不同目录引用它。文件名换了,内容没变,仍然能找到同一份内容。解包时还得读取实际源文件,不能因为 ID 没变就直接认定声音也没变。

硬链接共享底层文件,所以直接修改一处可能影响其他引用。需要剪辑时先导出独立的 WAV,或者复制到资源库之外。

这只能判断文件字节是否相同,还不能判断两段声音是不是同一句台词。重新编码、重新录制,都可能让哈希变化;配音老师换个语气说同样的话,文本也可以完全没变。语音转文本和跨版本的台词核对,还得继续研究。

至少现在手里有了能确认原始文件内容是否重复的方法,之前的 ID 问题也有了一个处理方向。但它离自动整理好所有台词,还有不少距离。

顺带拆出来的项目#

前面这些事折腾久了,相关工具也慢慢拆出来了。最近这些项目的阶段总结里有整体记录,这里只补它们跟音频工具的关系。

项目跟这条音频处理流程的关系
league-tools把用到的解析、音频解包等能力整理成独立 Python 包,供上层程序使用。
pyvgmstream在 Python 里使用 vgmstream 的解码能力,供程序转码和试听。
vgmstream-cli-build之前为了批量转码改过的控制台版本,也能独立使用。
RiotManifest、RiotManifestGo处理 Riot 清单与资源下载的另一条工具线,分别有 Python 和 Go 版本。

清单和下载有它们自己的项目,现在的 Lol Audio Unpack 处理本地准备好的资源,没有把下载器一起塞进来。后续游戏客户端怎么改、资源怎么组织,还要继续看实际文件;既有的旧资源处理也不能因为新客户端出现就直接扔掉,重做前的语音有人需要,文件也还在那里。

文档和视频#

现在软件的使用说明放在 lau.oogoo.top,界面操作、控制台参数、目录规则和版本更新都在那边维护。这篇就留着记开发经过,不跟文档一起逐版本改功能列表。

Lol Audio Unpack 源码仓库仓库地址仍为 lol_extract_voice,代码、Issues 和发布记录都在这里。

操作视频放在下面,想跟着界面走一遍可以看视频,具体参数和说明还是查文档方便。

Lol Audio Unpack 操作演示

也可以直接到 B 站观看。

最早只是想找点素材,后来视频没怎么做,解包工具倒是一直写到了现在。现在能在自己电脑上找到想要的声音,听一下,导出去用,对我来说已经方便不少。剩下事件名和台词这两个坑,慢慢填吧。

Lol Audio Unpack:这些年折腾的英雄联盟音频工具
https://x-item.com/lol_extract_voice.html
作者
惟一 / Virace
发布于
2026-10-11
许可协议
MIT