跳到正文

WEM 内容共享与索引 ​

实际 WEM 内容是复用依据,ID 仅记录引用关系。 历史样本已经出现“内容相同、ID 改变”与“ID 不变、内容改变”, 见 音频 ID 与内容实测。

库根是应用输出目录。内容对象与索引分开保存:原始 WEM 按完整字节 SHA-256 保存到 audios/_data/<摘要前两位>/<完整摘要>.wem,版本、区域、实体、皮肤和 ID 在索引中关联到摘要。 可见目录通过硬链接引用;同库字节完全相同的 WEM 只存一份数据,可以跨版本、区域、实体和皮肤复用。 内容不同就分别保存,不做听感去重。 OGG 保留在原业务目录,WAV 保留在固定转换输出目录,两者都不进入 _data。

LCU 基础数据更新默认将选人语音、禁用语音和选人音效预取到 manifest 缓存; 英雄解包时再硬链接到对应英雄的 lobby/,可用 --no-lobby-audio 关闭。 缓存缺失时仍按英雄补齐,不因共享元数据已是最新而跳过。 英语语音和共享音效使用 LCU 的 default 命名空间。

硬链接的各个路径指向同一份底层内容。需要编辑时先导出独立副本,不直接编辑库内文件。 删除一个链接不影响其他链接;修改内容会影响所有链接。系统不支持硬链接时明确失败, 不会静默复制媒体。普通导出始终为独立副本。

持久化格式 ​

每个游戏版本、区域只有一份内容索引: audios/_index/<版本>/<区域>.msgpack。default 规范化为 en_US。 以下 JSON 仅供阅读,实际保存 MessagePack:

json
{
  "schema": 2,
  "version": "16.18",
  "algorithm": "sha256",
  "region": "zh_CN",
  "champions": {
    "60009": {
      "60009000": {"707065442": "<基础皮肤文件的完整 SHA-256>"},
      "60009301": {"707065442": "<经典皮肤文件的完整 SHA-256>"}
    }
  },
  "maps": {},
  "resource_packs": {},
  "wavs": {}
}
  • 英雄:实体 ID → 皮肤 ID → WEM ID → 内容摘要。
  • 地图、资源包:实体 ID → WEM ID → 内容摘要;资源包使用现有稳定 key。
  • 逻辑结构版本、游戏版本、哈希算法、区域必须保存在顶层,并与读取目标核对。
  • 不保存名称、音频类型、事件、容器来源、对象路径或逐条文件大小。
  • 同皮肤的 BNK/WPK 音频取并集,同 ID 优先 WPK,BNK 补充独有 ID。 不要求重复项的字节相同;跨皮肤不同内容由皮肤 ID 区分,无 JADE 特例。
  • 同一身份本次成功获得新内容时更新摘要;不同版本分别记录,各自指向实际内容。 局部处理、失败、取消不删除未处理的成功记录。

MediaRef(entity_type, entity_id, media_id, object, skin_id=None) 用于任务内传递成功引用。 英雄必须提供数字字符串皮肤 ID,其他实体不提供皮肤 ID。 ObjectRef 的运行时字节数不写入索引。

python
from pathlib import Path
from lol_audio_unpack.runtime.library import Library, MediaRef

# 只读查询不验证音频文件;游戏版本和区域由索引头核对。
library = Library(Path("output"))
index = library.load("16.18", "zh_CN")
digest = index.find_media("champion", "60009", 707065442, "60009301")

# 发布当前输入字节,成功建立可见链接后再登记引用。
with Library(Path("output")) as writer:
    published = writer.publish(actual_wem_bytes)
    ref = MediaRef("champion", "60009", 707065442, published.ref, "60009301")
    writer.materialize(ref.object, visible_relative_path)
    writer.merge("16.18", "zh_CN", [ref])

WAV 当前输出 ​

顶层 wavs 的 key 是相对于输出根的实际 WAV 路径,value 只有 {"digest": "<输入摘要>", "format": "pcm16"},其中 format 记录采样格式。每个输出位置只保留当前成功结果。 采样格式从 PCM16 改为 FLOAT 时重新转换并原子替换同一路径,更新同一条记录;不增加历史采样格式记录, 不保存后端构建缓存或隐藏的 WAV 副本。

输入摘要、采样格式匹配且目标文件存在时可以复用;缺失、采样格式变化或强制转换时重新处理。 只有实际新转换的 WAV 才进行转换结果校验,浏览不会完整解码已有 WAV。 同任务的相同输入可以归并转换;外部导出仍生成独立文件。

按事件分类输出 ​

项目合同
交付文件独立 WAV,不写标准 WAV 格式记录,不建立新内容索引,可直接复制或打包
分类来源hashes 中同版本、同语言的精确路径关系
原始库不移动或登记原始 WEM,不迁移旧 WAV,不登记用户额外导出目录
试听始终从库内 WEM 解码,分类目录不参与定位
普通根wavs/<版本>/<语言>/...
分类根wavs_by_event/<版本>/<语言>/...
回退对象无可用精确映射时保留普通布局;有映射但音频未关联时放入“未关联事件”

浏览与事件 ​

  • 事件来源:hashes 中的原映射;内容索引不保存事件,也不代替源 WAD 时效索引。
  • 实体清单:reports/<版本>/<区域>/<实体类>/<ID>.audios.msgpack 保存成功输出清单和摘要,无事件映射也可浏览。
  • 按需读取:已解包筛选读摘要,全部音频读当前实体清单,事件叶子在展开时创建,不逐文件检查磁盘或扫描 _data。
  • 使用时检查:播放、转码、导出才检查所选文件;手动删除的文件在使用时报告缺失。

已发布旧输出归档 ​

启动时一次性处理已发布的旧版目录,不为未发布的实验 schema 保留兼容读取。 旧 WEM 按实际字节计算哈希并归档,再按照原实体、皮肤、类型和 ID 建立可见硬链接及索引。 不需要旧客户端、事件名或事件映射;相同内容自然共享。

旧 WAV、OGG 保留原字节并迁入对应区域目录,不进入共享内容区;没有可信转换记录的旧 WAV 不会被猜测为某种格式缓存。旧 JSON/YAML 元数据在迁移时转为 MessagePack。 全部新链接和索引成功后才移除旧位置,中断后可继续。目标同名但内容不同、区域不明确或 文件发生错误时停止并保留尚未移走的数据。

界面后台初始化时,根据已解包实体清单检查三个固定大厅文件,补齐缺项并登记到音频清单; 不遍历 WEM。当前版本缺少源文件时从本地 LCU 提取;历史版本只使用该版本已保存的大厅资源。 已有大厅文件保持不变,缺少对应版本源资源时记录警告,不用当前版本或其他语言冒充。

写入与恢复 ​

写任务统一持有库锁,第二写者抛 LibraryBusyError;退出或进程结束后由操作系统释放。 锁文件 audios/.writer.lock 本身保留,不通过删除它解除占用。

先完整发布对象,再原子替换索引。索引前态只保留一份 .msgpack.bak;无变化不重写。 索引缺失可用 restore(version, region) 显式恢复前态,或重新解包建立当前记录。 现存损坏、不支持的结构、缺少必填头字段均报错,不默默回填或覆盖。 失败可能留下未引用的完整内容对象,不自动清理。

库路径使用相对于输出根的安全路径;移动或备份时同时带上内容、索引和可见目录。