1
0
mirror of https://github.com/Escartem/AnimeWwise.git synced 2026-06-05 07:50:23 +08:00

begin new version

This commit is contained in:
Escartem
2024-11-03 22:02:16 +01:00
parent 358648f716
commit da8ee97264
5 changed files with 430 additions and 186 deletions

158
app.py
View File

@@ -4,11 +4,12 @@ import json
import math
import extract
import platform
import webbrowser
from PyQt5 import uic
from requests import get
from PyQt5.QtGui import QTextCursor
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QThread, QMetaType, Qt
from PyQt5.QtWidgets import QMessageBox, QMainWindow, QApplication, QFileDialog, QHeaderView, QAbstractItemView, QTreeWidgetItem
from PyQt5.QtWidgets import QMessageBox, QMainWindow, QApplication, QFileDialog, QHeaderView, QAbstractItemView, QTreeWidgetItem, QAction, QActionGroup
QMetaType.type("QTextCursor")
@@ -72,71 +73,114 @@ class AnimeWwise(QMainWindow):
def __init__(self):
super(AnimeWwise, self).__init__()
uic.loadUi("gui.ui", self)
self.maps = self.getMaps()
self.maps = self.getJson("maps/index")
self.setWindowTitle(f'AnimeWwise | v{".".join(list(str(self.getJson("version")["version"])))}')
self.folders = {
"input": "",
"output": "",
"diff": ""
}
self.format = "wav"
self.fileStructure = {"folders": {}, "files": []}
self.setupActions()
sys.stdout = TextEditStream(self.console)
# sys.stdout = TextEditStream(self.console)
self.extract = extract.WwiseExtract()
self.checkMapsUpdates()
self.checkUpdates()
# utils
self.selectFolder = lambda: QFileDialog.getExistingDirectory(self, "Select Folder")
def checkMapsUpdates(self):
print("Checking updates")
def checkUpdates(self):
print("Checking for updates...")
try:
getVersion = lambda m: sum([int(e["version"].replace(".", "")) for e in m["maps"]])
mapsVersion = getVersion(self.maps)
latestMaps = get("https://raw.githubusercontent.com/Escartem/AnimeWwise/master/maps/index.json")
currentVersion = self.getJson("version")
latestVersionReq = get("https://raw.githubusercontent.com/Escartem/AnimeWwise/master/version.json")
if latestMaps.status_code == 200:
latestVersion = getVersion(json.loads(latestMaps.text))
if latestVersionReq.status_code == 200:
latestVersion = json.loads(latestMaps.text)
if mapsVersion < latestVersion:
print("Update found")
QMessageBox.information(None, "Info", "Newer version of the mappings are availble, please update the program", QMessageBox.Ok)
if currentVersion["version"] < latestVersion["version"]:
print("Update found !")
QMessageBox.information(None, "Info", "Newer version of the program is availble, please update.", QMessageBox.Ok)
elif currentVersion["mapsVersion"] < latestVersion["mapsVersion"]:
print("Update found !")
QMessageBox.information(None, "Info", "Newer version of the mappings are availble, please update the program.", QMessageBox.Ok)
else:
print("No updates")
except:
print("Failed to check updates")
print("Failed to check updates :(")
def getMaps(self):
with open("maps/index.json", "r") as f:
maps = json.loads(f.read())
def getJson(self, path):
with open(f"{path}.json", "r") as f:
data = json.loads(f.read())
f.close()
return maps
return data
def setFolder(self, elem, folder):
def setFolder(self, elem=None, folder=None):
path = self.selectFolder()
self.folders[folder] = path
elem.setText(path)
if elem:
elem.setText(path)
def setupActions(self):
self.changeInput.clicked.connect(lambda: self.setFolder(self.inputPath, "input"))
self.changeAltInput.clicked.connect(lambda: self.setFolder(self.altInputPath, "diff"))
self.changeOutput.clicked.connect(lambda: self.setFolder(self.outputPath, "output"))
self.outputFormat.addItems(["wem (fastest)", "wav (fast)", "mp3 (slow)", "ogg (slow)"])
self.assetMap.addItems(["No map", *[f'{e["game"]} - v{e["version"]}' for e in self.maps["maps"]]])
self.tabs.setTabEnabled(1, False)
self.tabs.setTabEnabled(2, False)
self.setExtractionState(False)
self.updateTreeWidget(self.fileStructure)
self.loadFilesButton.clicked.connect(lambda: self.loadFiles())
self.actionReset.triggered.connect(lambda: self.resetApp())
self.actionExit.triggered.connect(lambda: self.close())
self.extractSelected.clicked.connect(lambda: self.extractItems(False))
self.extractAll.clicked.connect(lambda: self.extractItems(True))
self.actionExtract_Selected.triggered.connect(lambda: self.extractItems(False))
self.actionExtract_All.triggered.connect(lambda: self.extractItems(True))
self.actionReport_a_bug.triggered.connect(lambda: self.openLink(0))
self.actionSource_code.triggered.connect(lambda: self.openLink(1))
self.actionDiscord.triggered.connect(lambda: self.openLink(2))
self.searchAsset.textChanged.connect(lambda: self.filterAsset())
# output format
formats = ["wem (fastest)", "wav (fast)", "mp3 (slow)", "ogg (slow)"]
action_group = QActionGroup(self)
action_group.setExclusive(True)
for index, item_name in enumerate(formats):
action = QAction(item_name, self)
action.setCheckable(True)
if index == 1:
action.setChecked(True)
self.menuOutput_format.addAction(action)
action_group.addAction(action)
action_group.triggered.connect(self.updateFormat)
def updateFormat(self, event):
text = event.text()
self.format = text.split(" ")[0]
def openLink(self, id):
urls = [
"https://github.com/Escartem/AnimeWwise/issues/new",
"https://github.com/Escartem/AnimeWwise",
"https://discord.gg/fzRdtVh"
]
webbrowser.open(urls[id])
def setExtractionState(self, state):
self.actionExtract_Selected.setEnabled(state)
self.actionExtract_All.setEnabled(state)
self.actionExpand_all.setEnabled(state)
self.actionCollapse_all.setEnabled(state)
# workers
@pyqtSlot(list)
def progressBarSlot(self, progress):
@@ -152,23 +196,16 @@ class AnimeWwise(QMainWindow):
if data["action"] == "load":
self.fileStructure = data["content"]
self.updateTreeWidget(self.fileStructure)
self.tabs.setTabEnabled(0, False)
self.tabs.setTabEnabled(1, True)
self.tabs.setTabEnabled(2, True)
self.setExtractionState(True)
self.tabs.setCurrentIndex(1)
print("Done !")
if data["action"] == "error":
QMessageBox.warning(None, "Warning", data["content"]["msg"], QMessageBox.Ok)
state = data["content"]["state"]
if state == 1:
self.tabs.setTabEnabled(0, True)
elif state == 2:
self.tabs.setTabEnabled(1, True)
self.tabs.setTabEnabled(2, True)
if state == 2:
self.setExtractionState(True)
if data["action"] == "extract":
self.tabs.setTabEnabled(1, True)
self.tabs.setTabEnabled(2, True)
self.tabs.setCurrentIndex(2)
self.setExtractionState(True)
print("Finished extracting everything !")
if platform.system() == "Windows":
@@ -186,7 +223,6 @@ class AnimeWwise(QMainWindow):
else:
_map = None
self.tabs.setTabEnabled(0, False)
self.resetTreeWidget()
# why is all this required for threading damnit
@@ -225,28 +261,36 @@ class AnimeWwise(QMainWindow):
def resetTreeWidget(self):
self.treeWidget.clear()
self.tabs.setTabEnabled(1, False)
self.fileStructure = {"folders": {}, "files": []}
self.setExtractionState(False)
def updateTreeWidget(self, structure):
self.treeWidget.clear()
self.treeWidget.setColumnCount(3)
self.treeWidget.setHeaderLabels(["Name", "Offset", "Size", "Source"])
self.treeWidget.setColumnCount(4)
self.treeWidget.setHeaderLabels(["Name", "Duration", "Source", "Size", "Offset"])
self.addItems(None, structure)
self.treeWidget.expandAll()
self.treeWidget.header().setSectionResizeMode(0, QHeaderView.Stretch)
self.treeWidget.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
self.treeWidget.header().setSectionResizeMode(2, QHeaderView.ResizeToContents)
self.treeWidget.header().setSectionResizeMode(0, QHeaderView.ResizeToContents)
self.treeWidget.header().setSectionResizeMode(1, QHeaderView.Stretch)
self.treeWidget.header().setSectionResizeMode(2, QHeaderView.Stretch)
self.treeWidget.header().setSectionResizeMode(3, QHeaderView.Stretch)
self.treeWidget.setHeaderHidden(False)
self.treeWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.treeWidget.setDragDropMode(QAbstractItemView.NoDragDrop)
self.treeWidget.itemClicked.connect(self.updateAudioPreview)
def updateAudioPreview(self, item, column):
print(item.text(0))
def addItems(self, parent, element):
for folder_name in sorted(element.get("folders", {}).keys()):
folder_content = element["folders"][folder_name]
folder_item = QTreeWidgetItem([folder_name, "", "", ""])
folder_item = QTreeWidgetItem([folder_name, "", "", "", ""])
folder_item.setFlags(folder_item.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
folder_item.setCheckState(0, Qt.Unchecked)
if parent is None:
@@ -256,7 +300,8 @@ class AnimeWwise(QMainWindow):
self.addItems(folder_item, folder_content)
for file in sorted(element.get("files", [])):
file_item = QTreeWidgetItem([str(file[0]), str(hex(file[1])), str(file[2]), str(file[3])])
file_meta = file[1]
file_item = QTreeWidgetItem([file[0], f'{round(file_meta["metadata"]["duration"], 2)} seconds', file_meta["source"], str(file_meta["size"]), str(hex(file_meta["offset"]))])
file_item.setFlags(file_item.flags() | Qt.ItemIsUserCheckable)
file_item.setCheckState(0, Qt.Unchecked)
if parent is None:
@@ -266,12 +311,15 @@ class AnimeWwise(QMainWindow):
# page 3 - extraction
def extractItems(self, _all):
self.setFolder(folder="output")
if self.folders["output"] == "":
QMessageBox.warning(None, "Warning", "Missing output folder !", QMessageBox.Ok)
return
checked_items = []
# todo: use file structure instead of tree view
def check_items(item, _all):
if item.checkState(0) == Qt.Checked or _all:
if item.text(1) != "":
@@ -282,13 +330,11 @@ class AnimeWwise(QMainWindow):
for i in range(self.treeWidget.topLevelItemCount()):
check_items(self.treeWidget.topLevelItem(i), _all)
self.tabs.setTabEnabled(1, False)
self.tabs.setTabEnabled(2, False)
self.tabs.setCurrentIndex(2)
self.setExtractionState(False)
# yet another block of threading bs
self.backgroundThread = QThread()
self.backgroundWorker = BackgroundWorker("extract", self.extract, {"input": self.folders["input"], "files": checked_items, "format": self.outputFormat.currentText()[:3], "output": self.folders["output"]})
self.backgroundWorker = BackgroundWorker("extract", self.extract, {"input": self.folders["input"], "files": checked_items, "format": self.format, "output": self.folders["output"]})
self.backgroundWorker.moveToThread(self.backgroundThread)
self.backgroundThread.started.connect(self.backgroundWorker.run)
self.backgroundWorker.finished.connect(self.handleFinished)
@@ -310,18 +356,16 @@ class AnimeWwise(QMainWindow):
return {
"name": item.text(0),
"path": path[:-1] if path[0] in ["changed_files", "new_files"] else path[1:-1],
"source": item.text(3),
"offset": int(item.text(1), 16),
"size": int(item.text(2))
"source": item.text(2),
"offset": int(item.text(4), 16),
"size": int(item.text(3))
}
# misc
def resetApp(self):
self.resetTreeWidget()
self.extract.reset()
self.tabs.setTabEnabled(0, True)
self.tabs.setTabEnabled(1, False)
self.tabs.setTabEnabled(2, False)
self.setExtractionState(False)
print("Reset !")
def _appendText(self, text):

View File

@@ -1,9 +1,10 @@
import os
import io
import wwise
import tempfile
import wavescan
import subprocess
import platform
import subprocess
from mapper import Mapper
from allocator import Allocator
from filereader import FileReader
@@ -62,7 +63,7 @@ class WwiseExtract:
hdiff_files = self.get_hdiff_files(data, hdiff_data, filename)
files = self.compare_diff(files, hdiff_files)
self.map_names(files, filename, hdiff is not None)
self.map_names(files, filename, hdiff is not None, data)
def compare_diff(self, old, new):
old_dict = {file[0]:file[2] for file in old}
@@ -112,7 +113,7 @@ class WwiseExtract:
return files
def map_names(self, files, filename, hdiff=False, skip_source=True):
def map_names(self, files, filename, hdiff=False, data=None, skip_source=True):
# disable skip source if required
mapper = self.mapper
base = self.file_structure
@@ -128,6 +129,18 @@ class WwiseExtract:
else:
key = None
file_data = {
"source": file[3],
"size": file[2],
"offset": file[1],
"metadata": {}
}
wem_data = data[file_data["offset"]:file_data["offset"]+file_data["size"]]
parsed_wem = wwise.parse_wwise(FileReader(io.BytesIO(wem_data), "little"))
file_data["metadata"] = parsed_wem
if key is not None:
if hdiff:
if file in old_files[0]:
@@ -139,7 +152,7 @@ class WwiseExtract:
if skip_source:
parts = parts[1:]
self.add_to_structure(parts, [file[1], file[2], file[3]])
self.add_to_structure(parts, file_data)
else:
temp = base["folders"]
@@ -161,7 +174,7 @@ class WwiseExtract:
if "unmapped" not in temp:
temp["unmapped"] = {"folders": {}, "files": []}
temp["unmapped"]["files"].append(file)
temp["unmapped"]["files"].append([file[0], file_data])
self.file_structure = base
@@ -175,7 +188,7 @@ class WwiseExtract:
current_level = current_level["folders"][part]
if "files" not in current_level:
current_level["files"] = []
current_level["files"].append([parts[-1], meta[0], meta[1], meta[2]])
current_level["files"].append([parts[-1], meta])
### extracting files ###

302
gui.ui
View File

@@ -5,24 +5,27 @@
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1100</width>
<height>800</height>
<height>900</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>1100</width>
<height>800</height>
<height>900</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>1100</width>
<height>800</height>
<height>900</height>
</size>
</property>
<property name="windowTitle">
@@ -39,7 +42,7 @@
</rect>
</property>
<property name="currentIndex">
<number>1</number>
<number>0</number>
</property>
<property name="usesScrollButtons">
<bool>true</bool>
@@ -210,7 +213,7 @@
<x>0</x>
<y>20</y>
<width>1081</width>
<height>591</height>
<height>551</height>
</rect>
</property>
<property name="columnCount">
@@ -235,135 +238,49 @@
<string>Search something...</string>
</property>
</widget>
</widget>
<widget class="QWidget" name="extractTab">
<attribute name="title">
<string>Extract</string>
</attribute>
<widget class="QWidget" name="verticalLayoutWidget_2">
<widget class="QWidget" name="horizontalLayoutWidget">
<property name="geometry">
<rect>
<x>9</x>
<y>9</y>
<width>1061</width>
<height>601</height>
<x>0</x>
<y>580</y>
<width>1081</width>
<height>31</height>
</rect>
</property>
<layout class="QVBoxLayout" name="mainVLayout2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QHBoxLayout" name="outputLayout">
<item>
<widget class="QLabel" name="outputLabel">
<property name="text">
<string>Output folder</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="outputPath">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="changeOutput">
<property name="text">
<string>Select</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="outputFormatLayout">
<item>
<widget class="QLabel" name="outputFormatLabel">
<property name="text">
<string>Output format</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="outputFormat">
<property name="currentText">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="separatorC">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<widget class="QLabel" name="audioInfoLabel">
<property name="text">
<string>sample_long_name.wem | Duration : 00:00 | Stereo (48000Hz)</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="progressWrapperLayout">
<item>
<layout class="QHBoxLayout" name="totalProgressLayout">
<item>
<widget class="QLabel" name="totalProgressLabel">
<property name="text">
<string>Total progress</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="totalProgress">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="fileProgressLayout">
<item>
<widget class="QLabel" name="fileProgressLabel">
<property name="text">
<string>Per file progress</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="fileProgress">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="separatorD">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<widget class="QLabel" name="audioPlayPadding">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="extractLayout">
<item>
<widget class="QPushButton" name="extractAll">
<property name="text">
<string>Extract All</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="extractSelected">
<property name="text">
<string>Extract Selected</string>
</property>
</widget>
</item>
</layout>
<widget class="QPushButton" name="playAudioBtn">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Play</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stopAudioBtn">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Stop</string>
</property>
</widget>
</item>
</layout>
</widget>
@@ -373,9 +290,9 @@
<property name="geometry">
<rect>
<x>10</x>
<y>640</y>
<y>720</y>
<width>1081</width>
<height>131</height>
<height>151</height>
</rect>
</property>
<property name="maximumSize">
@@ -394,6 +311,54 @@
<bool>true</bool>
</property>
</widget>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>16</x>
<y>650</y>
<width>1071</width>
<height>61</height>
</rect>
</property>
<layout class="QVBoxLayout" name="progressWrapperLayout">
<item>
<layout class="QHBoxLayout" name="totalProgressLayout">
<item>
<widget class="QLabel" name="totalProgressLabel">
<property name="text">
<string>Total progress</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="totalProgress">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="fileProgressLayout">
<item>
<widget class="QLabel" name="fileProgressLabel">
<property name="text">
<string>Per file progress</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="fileProgress">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
@@ -412,7 +377,40 @@
<addaction name="separator"/>
<addaction name="actionExit"/>
</widget>
<widget class="QMenu" name="menuExtract">
<property name="title">
<string>Extract</string>
</property>
<widget class="QMenu" name="menuOutput_format">
<property name="title">
<string>Output format</string>
</property>
<addaction name="actionformat"/>
</widget>
<addaction name="menuOutput_format"/>
<addaction name="separator"/>
<addaction name="actionExtract_All"/>
<addaction name="actionExtract_Selected"/>
</widget>
<widget class="QMenu" name="menuOther">
<property name="title">
<string>Other</string>
</property>
<addaction name="actionReport_a_bug"/>
<addaction name="actionSource_code"/>
<addaction name="actionDiscord"/>
</widget>
<widget class="QMenu" name="menuView">
<property name="title">
<string>View</string>
</property>
<addaction name="actionExpand_all"/>
<addaction name="actionCollapse_all"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuView"/>
<addaction name="menuExtract"/>
<addaction name="menuOther"/>
</widget>
<action name="actionnot_working_here_yet">
<property name="text">
@@ -439,6 +437,64 @@
<string>Exit</string>
</property>
</action>
<action name="actionExtract_All">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Extract All</string>
</property>
</action>
<action name="actionExtract_Selected">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Extract Selected</string>
</property>
</action>
<action name="actionReport_a_bug">
<property name="text">
<string>Report a bug</string>
</property>
</action>
<action name="actionSource_code">
<property name="text">
<string>Source code</string>
</property>
</action>
<action name="actionDiscord">
<property name="text">
<string>Discord</string>
</property>
</action>
<action name="actionformat">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>format</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</action>
<action name="actionExpand_all">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Expand all</string>
</property>
</action>
<action name="actionCollapse_all">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Collapse all</string>
</property>
</action>
</widget>
<tabstops>
<tabstop>inputPath</tabstop>

4
version.json Normal file
View File

@@ -0,0 +1,4 @@
{
"version": 21,
"mapsVersion": 89
}

127
wwise.py Normal file
View File

@@ -0,0 +1,127 @@
# wwise riff header parser
# thanks to hcs and bnnm
def parse_wwise(reader):
header = reader.ReadBytes(4)
# endian check header
if header == b"RIFX":
reader.endianness = "big"
elif header == b"RIFF":
reader.endianness = "little"
else:
raise Exception("invalid header")
# additional check
reader.SetBufferPos(0x08)
check = reader.ReadBytes(4)
if check != b"WAVE" and check != "XWMA":
raise Exception("invalid file")
# read chunks
reader.SetBufferPos(0x0C)
chunks = {}
while reader.GetBufferPos() < reader.GetStreamLength():
# relevants chunks types
# "fmt "
# "data"
# "JUNK"
chunk_type = reader.ReadBytes(4)
if chunk_type not in [b"fmt ", b"JUNK", b"data"]:
raise Exception("invalid chunk")
formatted_chunk_type = chunk_type.decode("utf-8").replace(" ", "")
chunk_length = reader.ReadUInt32()
chunks[formatted_chunk_type] = {
"length": chunk_length,
"offset": reader.GetBufferPos(),
"data": reader.ReadBytes(chunk_length)
}
# reader fmt header
if chunks["fmt"]["length"] < 0x10:
raise Exception("invalid fmt chunk length")
reader.SetBufferPos(chunks["fmt"]["offset"])
metadata = {
"format": reader.ReadUInt16(),
"channels": reader.ReadUInt16(),
"sampleRate": reader.ReadUInt32(),
"avgBitrate": reader.ReadUInt32(),
"blockSize": reader.ReadUInt16(),
"bitsPerSample": reader.ReadUInt16(),
"extraSize": 0,
"channelLayout": None,
"channelType": None,
"codec": None,
"layoutType": None,
"interleaveBlockSize": None,
"numSamples": None,
"duration": None
}
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:
raise Exception("XMA2WAVEFORMATEX in fmt")
# 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 PTADPCM
# hsr and zzz should be VORBIS
if metadata["format"] not in codecs:
raise Exception("unknown codec")
codec = codecs[metadata["format"]]
if codec not in ["PTADPCM", "VORBIS"]: # Platinum "PtADPCM" custom ADPCM for Wwise
raise Exception(f"unhandled codec -> {codec}")
metadata["codec"] = 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
# TODO: rewrite codec ?