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:
158
app.py
158
app.py
@@ -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):
|
||||
|
||||
25
extract.py
25
extract.py
@@ -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
302
gui.ui
@@ -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
4
version.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"version": 21,
|
||||
"mapsVersion": 89
|
||||
}
|
||||
127
wwise.py
Normal file
127
wwise.py
Normal 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 ?
|
||||
Reference in New Issue
Block a user