mirror of
https://github.com/Escartem/AnimeWwise.git
synced 2026-06-11 20:20:25 +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 math
|
||||||
import extract
|
import extract
|
||||||
import platform
|
import platform
|
||||||
|
import webbrowser
|
||||||
from PyQt5 import uic
|
from PyQt5 import uic
|
||||||
from requests import get
|
from requests import get
|
||||||
from PyQt5.QtGui import QTextCursor
|
from PyQt5.QtGui import QTextCursor
|
||||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QThread, QMetaType, Qt
|
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")
|
QMetaType.type("QTextCursor")
|
||||||
|
|
||||||
@@ -72,71 +73,114 @@ class AnimeWwise(QMainWindow):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(AnimeWwise, self).__init__()
|
super(AnimeWwise, self).__init__()
|
||||||
uic.loadUi("gui.ui", self)
|
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 = {
|
self.folders = {
|
||||||
"input": "",
|
"input": "",
|
||||||
"output": "",
|
"output": "",
|
||||||
"diff": ""
|
"diff": ""
|
||||||
}
|
}
|
||||||
|
self.format = "wav"
|
||||||
|
self.fileStructure = {"folders": {}, "files": []}
|
||||||
self.setupActions()
|
self.setupActions()
|
||||||
sys.stdout = TextEditStream(self.console)
|
# sys.stdout = TextEditStream(self.console)
|
||||||
self.extract = extract.WwiseExtract()
|
self.extract = extract.WwiseExtract()
|
||||||
self.checkMapsUpdates()
|
self.checkUpdates()
|
||||||
|
|
||||||
# utils
|
# utils
|
||||||
self.selectFolder = lambda: QFileDialog.getExistingDirectory(self, "Select Folder")
|
self.selectFolder = lambda: QFileDialog.getExistingDirectory(self, "Select Folder")
|
||||||
|
|
||||||
def checkMapsUpdates(self):
|
def checkUpdates(self):
|
||||||
print("Checking updates")
|
print("Checking for updates...")
|
||||||
try:
|
try:
|
||||||
getVersion = lambda m: sum([int(e["version"].replace(".", "")) for e in m["maps"]])
|
currentVersion = self.getJson("version")
|
||||||
mapsVersion = getVersion(self.maps)
|
latestVersionReq = get("https://raw.githubusercontent.com/Escartem/AnimeWwise/master/version.json")
|
||||||
latestMaps = get("https://raw.githubusercontent.com/Escartem/AnimeWwise/master/maps/index.json")
|
|
||||||
|
|
||||||
if latestMaps.status_code == 200:
|
if latestVersionReq.status_code == 200:
|
||||||
latestVersion = getVersion(json.loads(latestMaps.text))
|
latestVersion = json.loads(latestMaps.text)
|
||||||
|
|
||||||
if mapsVersion < latestVersion:
|
if currentVersion["version"] < latestVersion["version"]:
|
||||||
print("Update found")
|
print("Update found !")
|
||||||
QMessageBox.information(None, "Info", "Newer version of the mappings are availble, please update the program", QMessageBox.Ok)
|
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:
|
else:
|
||||||
print("No updates")
|
print("No updates")
|
||||||
except:
|
except:
|
||||||
print("Failed to check updates")
|
print("Failed to check updates :(")
|
||||||
|
|
||||||
def getMaps(self):
|
def getJson(self, path):
|
||||||
with open("maps/index.json", "r") as f:
|
with open(f"{path}.json", "r") as f:
|
||||||
maps = json.loads(f.read())
|
data = json.loads(f.read())
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
return maps
|
return data
|
||||||
|
|
||||||
def setFolder(self, elem, folder):
|
def setFolder(self, elem=None, folder=None):
|
||||||
path = self.selectFolder()
|
path = self.selectFolder()
|
||||||
self.folders[folder] = path
|
self.folders[folder] = path
|
||||||
elem.setText(path)
|
if elem:
|
||||||
|
elem.setText(path)
|
||||||
|
|
||||||
def setupActions(self):
|
def setupActions(self):
|
||||||
self.changeInput.clicked.connect(lambda: self.setFolder(self.inputPath, "input"))
|
self.changeInput.clicked.connect(lambda: self.setFolder(self.inputPath, "input"))
|
||||||
self.changeAltInput.clicked.connect(lambda: self.setFolder(self.altInputPath, "diff"))
|
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.assetMap.addItems(["No map", *[f'{e["game"]} - v{e["version"]}' for e in self.maps["maps"]]])
|
||||||
|
|
||||||
self.tabs.setTabEnabled(1, False)
|
self.setExtractionState(False)
|
||||||
self.tabs.setTabEnabled(2, False)
|
|
||||||
|
self.updateTreeWidget(self.fileStructure)
|
||||||
|
|
||||||
self.loadFilesButton.clicked.connect(lambda: self.loadFiles())
|
self.loadFilesButton.clicked.connect(lambda: self.loadFiles())
|
||||||
|
|
||||||
self.actionReset.triggered.connect(lambda: self.resetApp())
|
self.actionReset.triggered.connect(lambda: self.resetApp())
|
||||||
self.actionExit.triggered.connect(lambda: self.close())
|
self.actionExit.triggered.connect(lambda: self.close())
|
||||||
|
|
||||||
self.extractSelected.clicked.connect(lambda: self.extractItems(False))
|
self.actionExtract_Selected.triggered.connect(lambda: self.extractItems(False))
|
||||||
self.extractAll.clicked.connect(lambda: self.extractItems(True))
|
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())
|
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
|
# workers
|
||||||
@pyqtSlot(list)
|
@pyqtSlot(list)
|
||||||
def progressBarSlot(self, progress):
|
def progressBarSlot(self, progress):
|
||||||
@@ -152,23 +196,16 @@ class AnimeWwise(QMainWindow):
|
|||||||
if data["action"] == "load":
|
if data["action"] == "load":
|
||||||
self.fileStructure = data["content"]
|
self.fileStructure = data["content"]
|
||||||
self.updateTreeWidget(self.fileStructure)
|
self.updateTreeWidget(self.fileStructure)
|
||||||
self.tabs.setTabEnabled(0, False)
|
self.setExtractionState(True)
|
||||||
self.tabs.setTabEnabled(1, True)
|
|
||||||
self.tabs.setTabEnabled(2, True)
|
|
||||||
self.tabs.setCurrentIndex(1)
|
self.tabs.setCurrentIndex(1)
|
||||||
print("Done !")
|
print("Done !")
|
||||||
if data["action"] == "error":
|
if data["action"] == "error":
|
||||||
QMessageBox.warning(None, "Warning", data["content"]["msg"], QMessageBox.Ok)
|
QMessageBox.warning(None, "Warning", data["content"]["msg"], QMessageBox.Ok)
|
||||||
state = data["content"]["state"]
|
state = data["content"]["state"]
|
||||||
if state == 1:
|
if state == 2:
|
||||||
self.tabs.setTabEnabled(0, True)
|
self.setExtractionState(True)
|
||||||
elif state == 2:
|
|
||||||
self.tabs.setTabEnabled(1, True)
|
|
||||||
self.tabs.setTabEnabled(2, True)
|
|
||||||
if data["action"] == "extract":
|
if data["action"] == "extract":
|
||||||
self.tabs.setTabEnabled(1, True)
|
self.setExtractionState(True)
|
||||||
self.tabs.setTabEnabled(2, True)
|
|
||||||
self.tabs.setCurrentIndex(2)
|
|
||||||
print("Finished extracting everything !")
|
print("Finished extracting everything !")
|
||||||
|
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
@@ -186,7 +223,6 @@ class AnimeWwise(QMainWindow):
|
|||||||
else:
|
else:
|
||||||
_map = None
|
_map = None
|
||||||
|
|
||||||
self.tabs.setTabEnabled(0, False)
|
|
||||||
self.resetTreeWidget()
|
self.resetTreeWidget()
|
||||||
|
|
||||||
# why is all this required for threading damnit
|
# why is all this required for threading damnit
|
||||||
@@ -225,28 +261,36 @@ class AnimeWwise(QMainWindow):
|
|||||||
|
|
||||||
def resetTreeWidget(self):
|
def resetTreeWidget(self):
|
||||||
self.treeWidget.clear()
|
self.treeWidget.clear()
|
||||||
self.tabs.setTabEnabled(1, False)
|
self.fileStructure = {"folders": {}, "files": []}
|
||||||
|
self.setExtractionState(False)
|
||||||
|
|
||||||
def updateTreeWidget(self, structure):
|
def updateTreeWidget(self, structure):
|
||||||
self.treeWidget.clear()
|
self.treeWidget.clear()
|
||||||
self.treeWidget.setColumnCount(3)
|
self.treeWidget.setColumnCount(4)
|
||||||
self.treeWidget.setHeaderLabels(["Name", "Offset", "Size", "Source"])
|
self.treeWidget.setHeaderLabels(["Name", "Duration", "Source", "Size", "Offset"])
|
||||||
|
|
||||||
self.addItems(None, structure)
|
self.addItems(None, structure)
|
||||||
|
|
||||||
self.treeWidget.expandAll()
|
self.treeWidget.expandAll()
|
||||||
self.treeWidget.header().setSectionResizeMode(0, QHeaderView.Stretch)
|
|
||||||
self.treeWidget.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
|
self.treeWidget.header().setSectionResizeMode(0, QHeaderView.ResizeToContents)
|
||||||
self.treeWidget.header().setSectionResizeMode(2, 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.setHeaderHidden(False)
|
||||||
|
|
||||||
self.treeWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
self.treeWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
||||||
self.treeWidget.setDragDropMode(QAbstractItemView.NoDragDrop)
|
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):
|
def addItems(self, parent, element):
|
||||||
for folder_name in sorted(element.get("folders", {}).keys()):
|
for folder_name in sorted(element.get("folders", {}).keys()):
|
||||||
folder_content = element["folders"][folder_name]
|
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.setFlags(folder_item.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable)
|
||||||
folder_item.setCheckState(0, Qt.Unchecked)
|
folder_item.setCheckState(0, Qt.Unchecked)
|
||||||
if parent is None:
|
if parent is None:
|
||||||
@@ -256,7 +300,8 @@ class AnimeWwise(QMainWindow):
|
|||||||
self.addItems(folder_item, folder_content)
|
self.addItems(folder_item, folder_content)
|
||||||
|
|
||||||
for file in sorted(element.get("files", [])):
|
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.setFlags(file_item.flags() | Qt.ItemIsUserCheckable)
|
||||||
file_item.setCheckState(0, Qt.Unchecked)
|
file_item.setCheckState(0, Qt.Unchecked)
|
||||||
if parent is None:
|
if parent is None:
|
||||||
@@ -266,12 +311,15 @@ class AnimeWwise(QMainWindow):
|
|||||||
|
|
||||||
# page 3 - extraction
|
# page 3 - extraction
|
||||||
def extractItems(self, _all):
|
def extractItems(self, _all):
|
||||||
|
self.setFolder(folder="output")
|
||||||
|
|
||||||
if self.folders["output"] == "":
|
if self.folders["output"] == "":
|
||||||
QMessageBox.warning(None, "Warning", "Missing output folder !", QMessageBox.Ok)
|
QMessageBox.warning(None, "Warning", "Missing output folder !", QMessageBox.Ok)
|
||||||
return
|
return
|
||||||
|
|
||||||
checked_items = []
|
checked_items = []
|
||||||
|
|
||||||
|
# todo: use file structure instead of tree view
|
||||||
def check_items(item, _all):
|
def check_items(item, _all):
|
||||||
if item.checkState(0) == Qt.Checked or _all:
|
if item.checkState(0) == Qt.Checked or _all:
|
||||||
if item.text(1) != "":
|
if item.text(1) != "":
|
||||||
@@ -282,13 +330,11 @@ class AnimeWwise(QMainWindow):
|
|||||||
for i in range(self.treeWidget.topLevelItemCount()):
|
for i in range(self.treeWidget.topLevelItemCount()):
|
||||||
check_items(self.treeWidget.topLevelItem(i), _all)
|
check_items(self.treeWidget.topLevelItem(i), _all)
|
||||||
|
|
||||||
self.tabs.setTabEnabled(1, False)
|
self.setExtractionState(False)
|
||||||
self.tabs.setTabEnabled(2, False)
|
|
||||||
self.tabs.setCurrentIndex(2)
|
|
||||||
|
|
||||||
# yet another block of threading bs
|
# yet another block of threading bs
|
||||||
self.backgroundThread = QThread()
|
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.backgroundWorker.moveToThread(self.backgroundThread)
|
||||||
self.backgroundThread.started.connect(self.backgroundWorker.run)
|
self.backgroundThread.started.connect(self.backgroundWorker.run)
|
||||||
self.backgroundWorker.finished.connect(self.handleFinished)
|
self.backgroundWorker.finished.connect(self.handleFinished)
|
||||||
@@ -310,18 +356,16 @@ class AnimeWwise(QMainWindow):
|
|||||||
return {
|
return {
|
||||||
"name": item.text(0),
|
"name": item.text(0),
|
||||||
"path": path[:-1] if path[0] in ["changed_files", "new_files"] else path[1:-1],
|
"path": path[:-1] if path[0] in ["changed_files", "new_files"] else path[1:-1],
|
||||||
"source": item.text(3),
|
"source": item.text(2),
|
||||||
"offset": int(item.text(1), 16),
|
"offset": int(item.text(4), 16),
|
||||||
"size": int(item.text(2))
|
"size": int(item.text(3))
|
||||||
}
|
}
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
def resetApp(self):
|
def resetApp(self):
|
||||||
self.resetTreeWidget()
|
self.resetTreeWidget()
|
||||||
self.extract.reset()
|
self.extract.reset()
|
||||||
self.tabs.setTabEnabled(0, True)
|
self.setExtractionState(False)
|
||||||
self.tabs.setTabEnabled(1, False)
|
|
||||||
self.tabs.setTabEnabled(2, False)
|
|
||||||
print("Reset !")
|
print("Reset !")
|
||||||
|
|
||||||
def _appendText(self, text):
|
def _appendText(self, text):
|
||||||
|
|||||||
25
extract.py
25
extract.py
@@ -1,9 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
import io
|
import io
|
||||||
|
import wwise
|
||||||
import tempfile
|
import tempfile
|
||||||
import wavescan
|
import wavescan
|
||||||
import subprocess
|
|
||||||
import platform
|
import platform
|
||||||
|
import subprocess
|
||||||
from mapper import Mapper
|
from mapper import Mapper
|
||||||
from allocator import Allocator
|
from allocator import Allocator
|
||||||
from filereader import FileReader
|
from filereader import FileReader
|
||||||
@@ -62,7 +63,7 @@ class WwiseExtract:
|
|||||||
hdiff_files = self.get_hdiff_files(data, hdiff_data, filename)
|
hdiff_files = self.get_hdiff_files(data, hdiff_data, filename)
|
||||||
files = self.compare_diff(files, hdiff_files)
|
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):
|
def compare_diff(self, old, new):
|
||||||
old_dict = {file[0]:file[2] for file in old}
|
old_dict = {file[0]:file[2] for file in old}
|
||||||
@@ -112,7 +113,7 @@ class WwiseExtract:
|
|||||||
|
|
||||||
return files
|
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
|
# disable skip source if required
|
||||||
mapper = self.mapper
|
mapper = self.mapper
|
||||||
base = self.file_structure
|
base = self.file_structure
|
||||||
@@ -128,6 +129,18 @@ class WwiseExtract:
|
|||||||
else:
|
else:
|
||||||
key = None
|
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 key is not None:
|
||||||
if hdiff:
|
if hdiff:
|
||||||
if file in old_files[0]:
|
if file in old_files[0]:
|
||||||
@@ -139,7 +152,7 @@ class WwiseExtract:
|
|||||||
if skip_source:
|
if skip_source:
|
||||||
parts = parts[1:]
|
parts = parts[1:]
|
||||||
|
|
||||||
self.add_to_structure(parts, [file[1], file[2], file[3]])
|
self.add_to_structure(parts, file_data)
|
||||||
else:
|
else:
|
||||||
temp = base["folders"]
|
temp = base["folders"]
|
||||||
|
|
||||||
@@ -161,7 +174,7 @@ class WwiseExtract:
|
|||||||
|
|
||||||
if "unmapped" not in temp:
|
if "unmapped" not in temp:
|
||||||
temp["unmapped"] = {"folders": {}, "files": []}
|
temp["unmapped"] = {"folders": {}, "files": []}
|
||||||
temp["unmapped"]["files"].append(file)
|
temp["unmapped"]["files"].append([file[0], file_data])
|
||||||
|
|
||||||
self.file_structure = base
|
self.file_structure = base
|
||||||
|
|
||||||
@@ -175,7 +188,7 @@ class WwiseExtract:
|
|||||||
current_level = current_level["folders"][part]
|
current_level = current_level["folders"][part]
|
||||||
if "files" not in current_level:
|
if "files" not in current_level:
|
||||||
current_level["files"] = []
|
current_level["files"] = []
|
||||||
current_level["files"].append([parts[-1], meta[0], meta[1], meta[2]])
|
current_level["files"].append([parts[-1], meta])
|
||||||
|
|
||||||
### extracting files ###
|
### extracting files ###
|
||||||
|
|
||||||
|
|||||||
302
gui.ui
302
gui.ui
@@ -5,24 +5,27 @@
|
|||||||
<property name="windowModality">
|
<property name="windowModality">
|
||||||
<enum>Qt::NonModal</enum>
|
<enum>Qt::NonModal</enum>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1100</width>
|
<width>1100</width>
|
||||||
<height>800</height>
|
<height>900</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>1100</width>
|
<width>1100</width>
|
||||||
<height>800</height>
|
<height>900</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximumSize">
|
<property name="maximumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>1100</width>
|
<width>1100</width>
|
||||||
<height>800</height>
|
<height>900</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@@ -39,7 +42,7 @@
|
|||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>1</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="usesScrollButtons">
|
<property name="usesScrollButtons">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@@ -210,7 +213,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>20</y>
|
<y>20</y>
|
||||||
<width>1081</width>
|
<width>1081</width>
|
||||||
<height>591</height>
|
<height>551</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="columnCount">
|
<property name="columnCount">
|
||||||
@@ -235,135 +238,49 @@
|
|||||||
<string>Search something...</string>
|
<string>Search something...</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
<widget class="QWidget" name="horizontalLayoutWidget">
|
||||||
<widget class="QWidget" name="extractTab">
|
|
||||||
<attribute name="title">
|
|
||||||
<string>Extract</string>
|
|
||||||
</attribute>
|
|
||||||
<widget class="QWidget" name="verticalLayoutWidget_2">
|
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>9</x>
|
<x>0</x>
|
||||||
<y>9</y>
|
<y>580</y>
|
||||||
<width>1061</width>
|
<width>1081</width>
|
||||||
<height>601</height>
|
<height>31</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="mainVLayout2">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="outputLayout">
|
<widget class="QLabel" name="audioInfoLabel">
|
||||||
<item>
|
<property name="text">
|
||||||
<widget class="QLabel" name="outputLabel">
|
<string>sample_long_name.wem | Duration : 00:00 | Stereo (48000Hz)</string>
|
||||||
<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>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="progressWrapperLayout">
|
<widget class="QLabel" name="audioPlayPadding">
|
||||||
<item>
|
<property name="text">
|
||||||
<layout class="QHBoxLayout" name="totalProgressLayout">
|
<string/>
|
||||||
<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>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="extractLayout">
|
<widget class="QPushButton" name="playAudioBtn">
|
||||||
<item>
|
<property name="enabled">
|
||||||
<widget class="QPushButton" name="extractAll">
|
<bool>false</bool>
|
||||||
<property name="text">
|
</property>
|
||||||
<string>Extract All</string>
|
<property name="text">
|
||||||
</property>
|
<string>Play</string>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
</widget>
|
||||||
<item>
|
</item>
|
||||||
<widget class="QPushButton" name="extractSelected">
|
<item>
|
||||||
<property name="text">
|
<widget class="QPushButton" name="stopAudioBtn">
|
||||||
<string>Extract Selected</string>
|
<property name="enabled">
|
||||||
</property>
|
<bool>false</bool>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
<property name="text">
|
||||||
</layout>
|
<string>Stop</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
@@ -373,9 +290,9 @@
|
|||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>10</x>
|
<x>10</x>
|
||||||
<y>640</y>
|
<y>720</y>
|
||||||
<width>1081</width>
|
<width>1081</width>
|
||||||
<height>131</height>
|
<height>151</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximumSize">
|
<property name="maximumSize">
|
||||||
@@ -394,6 +311,54 @@
|
|||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</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>
|
||||||
<widget class="QMenuBar" name="menubar">
|
<widget class="QMenuBar" name="menubar">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
@@ -412,7 +377,40 @@
|
|||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionExit"/>
|
<addaction name="actionExit"/>
|
||||||
</widget>
|
</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="menuFile"/>
|
||||||
|
<addaction name="menuView"/>
|
||||||
|
<addaction name="menuExtract"/>
|
||||||
|
<addaction name="menuOther"/>
|
||||||
</widget>
|
</widget>
|
||||||
<action name="actionnot_working_here_yet">
|
<action name="actionnot_working_here_yet">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@@ -439,6 +437,64 @@
|
|||||||
<string>Exit</string>
|
<string>Exit</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</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>
|
</widget>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>inputPath</tabstop>
|
<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