1
0
mirror of https://github.com/Escartem/AnimeWwise.git synced 2026-06-04 23:40:25 +08:00
Files
AnimeWwise/wwise.py
2024-12-03 18:25:01 +01:00

156 lines
4.2 KiB
Python

# wwise riff header parser
# thanks to hcs and bnnm work
def parse_wwise(reader):
# default meta config
metadata = {
"format": 0,
"channels": 0,
"sampleRate": 0,
"avgBitrate": 0,
"blockSize": 0,
"bitsPerSample": 0,
"extraSize": 0,
"channelLayout": None,
"channelType": None,
"codec": None,
"codecDisplay": None,
"layoutType": None,
"interleaveBlockSize": None,
"numSamples": None,
"duration": 0
}
if reader.GetStreamLength() == 0:
print(f"[WARNING] null stream size at {reader.GetName()}, unreadable block")
return metadata
header = reader.ReadBytes(4)
# endian check header
if header == b"RIFX":
reader.endianness = "big"
elif header == b"RIFF":
reader.endianness = "little"
else:
print(f"[WARNING] invalid header {header} at {reader.GetName()}, assuming unreadable")
return metadata
# additional check
reader.SetBufferPos(0x08)
check = reader.ReadBytes(4)
if check != b"WAVE" and check != "XWMA":
print(f"[WARNING] invalid check mark {check}, assuming unreadable")
return metadata
# read chunks
reader.SetBufferPos(0x0C)
chunks = {}
while reader.GetBufferPos() < reader.GetStreamLength():
chunk_type = reader.ReadBytes(4)
if chunk_type not in [b"fmt ", b"JUNK", b"data", b"akd ", b"cue ", b"LIST", b"smpl"]:
print(f"[WARNING] unexpected chunk {chunk_type} at {reader.GetName()}")
formatted_chunk_type = chunk_type.decode("utf-8").replace(" ", "")
chunk_length = reader.ReadUInt32()
if chunk_type == b"data" and chunk_length > reader.GetRemainingLength():
chunk_length = reader.GetRemainingLength()
chunks[formatted_chunk_type] = {
"length": chunk_length,
"offset": reader.GetBufferPos(),
"data": reader.ReadBytes(chunk_length)
}
# reader fmt header
fmt_length = chunks["fmt"]["length"]
if fmt_length < 0x10:
print(f"[WARNING] invalid fmt chunk length {fmt_length} at {reader.GetName()}, skipping")
return metadata
reader.SetBufferPos(chunks["fmt"]["offset"])
metadata["format"] = reader.ReadUInt16()
metadata["channels"] = reader.ReadUInt16()
metadata["sampleRate"] = reader.ReadUInt32()
metadata["avgBitrate"] = reader.ReadUInt32()
metadata["blockSize"] = reader.ReadUInt16()
metadata["bitsPerSample"] = reader.ReadUInt16()
if chunks["fmt"]["length"] > 0x10 and metadata["format"] != 0x0165 and metadata["format"] != 0x0166:
metadata["extraSize"] = reader.ReadUInt16()
if metadata["extraSize"] >= 0x06:
metadata["channelLayout"] = reader.ReadUInt32()
if metadata["channelLayout"] & 0xFF == metadata["channels"]:
metadata["channelType"] = (metadata["channelLayout"] >> 8) & 0x0F
metadata["channelLayout"] = metadata["channelLayout"] >> 12
if metadata["format"] == 0x0166:
print(f"[WARNING] XMA2WAVEFORMATEX in fmt at {reader.GetName()}")
return metadata
# parse codec
codecs = {
0x0001: "PCM",
0x0002: "IMA",
0x0069: "IMA",
0x0161: "XWLA",
0x0162: "XWMA",
0x0165: "XMA2",
0x0166: "XMA2",
0xAAC0: "AAC",
0xFFF0: "DSP",
0xFFFB: "HEVAG",
0xFFFC: "ATRAC9",
0xFFFE: "PCM",
0xFFFF: "VORBIS",
0x3039: "OPUSNX",
0x3040: "OPUS",
0x3041: "OPUSWW",
0x8311: "PTADPCM"
}
# genshin should be *mostly* PTADPCM
# hsr and zzz should be VORBIS
if metadata["format"] not in codecs:
print(f'[WARNING] unknown codec {metadata["format"]} at {reader.GetName()}')
return metadata
codec = codecs[metadata["format"]]
if codec not in ["PTADPCM", "VORBIS"]: # Platinum "PtADPCM" custom ADPCM for Wwise
print(f"[WARNING] unhandled codec {codec}, need to implement this later")
metadata["codec"] = codec
# codec name
codecs_names = {
"PTADPCM": "Platinum 4-bit ADPCM",
"VORBIS": "Custom Vorbis"
}
if codec in codecs_names:
metadata["codecDisplay"] = codecs_names[codec]
else:
metadata["codecDisplay"] = codec
# parse more infos
if metadata["codec"] == "PTADPCM":
metadata["layoutType"] = "interleave"
metadata["interleaveBlockSize"] = metadata["blockSize"] // metadata["channels"]
metadata["numSamples"] = int((chunks["data"]["length"] / (metadata["channels"] * metadata["interleaveBlockSize"])) * (2 + (metadata["interleaveBlockSize"] - 0x05) * 2))
metadata["duration"] = metadata["numSamples"] / metadata["sampleRate"]
return metadata
# TODO: parse VORBIS