1
0
mirror of https://github.com/Escartem/AnimeWwise.git synced 2026-06-12 04:42:12 +08:00

rework all app, add gui, wip

This commit is contained in:
Escartem
2024-07-19 12:15:09 +02:00
parent d06c190f79
commit a54fb25c7a
3 changed files with 668 additions and 273 deletions

133
app.py Normal file
View File

@@ -0,0 +1,133 @@
import os
import sys
import json
import time
import mapper
import extract
from PyQt5 import uic
from PyQt5.QtGui import QTextCursor
from PyQt5.QtWidgets import QMessageBox, QMainWindow, QApplication, QFileDialog
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QThread, QMetaType
QMetaType.type('QTextCursor')
class TextEditStream(QObject):
append_text = pyqtSignal(str)
def __init__(self, text_edit):
super().__init__()
self.text_edit = text_edit
self.append_text.connect(self._append_text)
def write(self, text):
self.append_text.emit(text)
def flush(self):
pass
def _append_text(self, text):
self.text_edit.moveCursor(QTextCursor.End)
self.text_edit.insertPlainText(text)
self.text_edit.moveCursor(QTextCursor.End)
class ExtractionWorker(QObject):
finished = pyqtSignal()
progress = pyqtSignal(list)
def __init__(self, folders, _map, _format):
super().__init__()
self.folders = folders
self.map = _map
self.format = _format
def run(self):
_map = None
if self.map:
_map = mapper.Mapper(os.path.join(os.getcwd(), f"maps/{self.map}"))
print("\n==========\n")
self.progress.emit(["total", 5])
extracter = extract.WwiseExtract(_map, self.format, *self.folders.values(), progress=self.progress.emit)
extracter.extract()
self.finished.emit()
class AnimeWwise(QMainWindow):
def __init__(self):
super(AnimeWwise, self).__init__()
uic.loadUi("gui.ui", self)
# self.setupUi(self)
self.maps = self.getMaps()
self.folders = {
"input": "",
"output": "",
"diff": ""
}
self.setupActions()
sys.stdout = TextEditStream(self.console)
# utils
self.selectFolder = lambda: QFileDialog.getExistingDirectory(self, "Select Folder")
def setFolder(self, elem, folder):
path = self.selectFolder()
self.folders[folder] = path
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(["mp3", "ogg"])
self.assetMap.addItems(["No map", *[f'{e["game"]} - v{e["version"]}' for e in self.maps["maps"]]])
self.startButton.clicked.connect(lambda: self.start())
def getMaps(self):
with open("maps/index.json", "r") as f:
maps = json.loads(f.read())
f.close()
return maps
@pyqtSlot(list)
def progressBarSlot(self, progress):
if progress[0] == "total":
self.progress.setValue(progress[1])
elif progress[0] == "task":
self.taskProgress.setValue(progress[1])
def start(self):
if "" in [self.folders["input"], self.folders["output"]]:
QMessageBox.warning(None, "Warning", "Missing input/output folder !", QMessageBox.Ok)
return
_map = self.assetMap.currentIndex()
if _map != 0:
_map = self.maps["maps"][_map-1]["name"]
else:
_map = None
self.extractThread = QThread()
self.extractWorker = ExtractionWorker(self.folders, _map, self.outputFormat.currentText())
self.extractWorker.moveToThread(self.extractThread)
self.extractThread.started.connect(self.extractWorker.run)
self.extractWorker.finished.connect(self.extractThread.quit)
self.extractWorker.finished.connect(self.extractWorker.deleteLater)
self.extractThread.finished.connect(self.extractThread.deleteLater)
self.extractWorker.progress.connect(self.progressBarSlot)
self.extractThread.start()
def _appendText(self, text):
cursor = self.console.textCursor()
cursor.movePosition(cursor.End)
cursor.insertText(text)
self.console.setTextCursor(cursor)
self.console.ensureCursorVisible()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = AnimeWwise()
window.show()
sys.exit(app.exec_())

View File

@@ -1,21 +1,16 @@
import os import os
import sys import sys
import mapper import time
import shutil import shutil
import filecmp import filecmp
import argparse import tempfile
import wavescan import wavescan
import subprocess import subprocess
from halo import Halo
from progress.bar import PixelBar
print("Setting up...")
cwd = os.getcwd() cwd = os.getcwd()
path = lambda path: os.path.join(cwd, path) path = lambda path: os.path.join(cwd, path)
call = lambda args: subprocess.call(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) call = lambda args: subprocess.call(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
mapper = mapper.Mapper(path("mapping/latest.map"))
spinner = Halo(text="spinner", spinner={'interval': 100, 'frames': ['', '', '', '', '', '']}, placement="right")
skips = "000000000" # used for debugging skips = "000000000" # used for debugging
# 1 - original extract # 1 - original extract
@@ -28,310 +23,281 @@ skips = "000000000" # used for debugging
# 8 - clean up # 8 - clean up
# 9 - temp clean up # 9 - temp clean up
def main(): class WwiseExtract:
parser = argparse.ArgumentParser() def __init__(self, _map, _format, input_folder, output_folder, diff_folder, progress):
self.map = _map
self.format = _format
self.paths = {
"input": input_folder,
"output": output_folder,
"diff": diff_folder,
"temp": tempfile.TemporaryDirectory()
}
self.progress = progress
# TODO: add skip / select mapping option # TODO: add skip / select mapping option
parser.add_argument("--format", nargs="?", type=str, default="mp3", help="Output audio format, can be either mp3 or ogg")
args = parser.parse_args()
formats = ["mp3", "ogg"] def path(self, base, path):
audio_format = "mp3" base_path = self.paths[base]
if args.format in formats: if base == "temp":
audio_format = args.format base_path = base_path.name
return os.path.join(base_path, path)
print(f'Format: {audio_format}') def extract(self):
audio_format = self.format
mapper = self.map
_p = self.path # lazy
# Initial cleanup print(f'Format: {audio_format}')
if os.path.exists("temp") and skips[8] != "1":
shutil.rmtree("temp")
if os.path.exists("output") and len(os.listdir("output")) > 0: # TODO: ui popup
print("The output folder needs to be cleared, continue ? [Y/N]") # if os.path.exists("output") and len(os.listdir("output")) > 0:
select = input(">") # print("The output folder needs to be cleared, continue ? [Y/N]")
if select.lower() == "y": # select = input(">")
shutil.rmtree("output") # if select.lower() == "y":
else: # shutil.rmtree("output")
print("Aborting") # else:
exit() # print("Aborting")
# exit()
# Get all files to process # Get all files to process
hdiff_files = [f for f in os.listdir("audio") if f.endswith(".pck") and os.path.exists(f"patch/{f}.hdiff")] hdiff_files = [f for f in os.listdir(self.paths["input"]) if f.endswith(".pck") and os.path.exists(_p("diff", f"{f}.hdiff"))]
alone_files = [f for f in os.listdir("audio") if f.endswith(".pck") and not os.path.exists(f"patch/{f}.hdiff")] alone_files = [f for f in os.listdir(self.paths["input"]) if f.endswith(".pck") and not os.path.exists(_p("diff", f"{f}.hdiff"))]
files = [*hdiff_files, *alone_files] files = [*hdiff_files, *alone_files]
if len(files) == 0: if len(files) == 0:
print("No files found !") print("No files found !")
return # self.progress(100)
return
print(f"{len(files)} file{'s' if len(files) != 1 else ''} to extract") print(self.paths["temp"].name)
iteration = 0
for file in files: print(f"{len(files)} file{'s' if len(files) != 1 else ''} to extract")
try: iteration = 0
iteration += 1
filename = file
if file in hdiff_files:
filename = f"{file.split('.')[0]}.hdiff.pck"
print(f"--- {filename} ({iteration}/{len(files)}) ---")
alone, steps, curr = False, 8, 1 for file in files:
if file in alone_files: try:
alone, steps = True, 5 iteration += 1
filename = file
if file in hdiff_files:
filename = f"{file.split('.')[0]}.hdiff.pck"
print(f"--- {filename} ({iteration}/{len(files)}) ---")
###################################### alone = False #8 steps
### 1 - Extract original .pck file ### if file in alone_files:
###################################### alone = True # 5 steps
if skips[0] != "1": ######################################
# update files ### 1 - Extract original .pck file ###
if os.path.exists("temp"): ######################################
shutil.rmtree("temp")
os.makedirs(path("temp"), exist_ok=True)
shutil.copy(f"audio/{file}", f"temp/{file}")
output_path = "original_decoded" if skips[0] != "1":
if alone: shutil.copy(_p("input", file), _p("temp", file))
output_path = "wem"
# update spinner and call program output_path = "original_decoded"
spinner.text = f"[{curr}/{steps}] Extracting" if alone:
spinner.start() output_path = "wem"
wavescan.extract(path(f"temp/{file}"), path(f"temp/{output_path}"))
spinner.stop()
print(f"[{curr}/{steps}] Extracting")
if alone: print(f"Extracting")
all_files = os.listdir(path("temp/wem")) wavescan.extract(_p("temp", file), _p("temp", output_path))
self.progress(["total", 15])
###################################### if alone:
### 2 - Patch the .pck with .hdiff ### all_files = os.listdir(_p("temp", "wem"))
######################################
if skips[1] != "1": ######################################
if not alone: ### 2 - Patch the .pck with .hdiff ###
curr += 1 ######################################
# update files if skips[1] != "1":
shutil.copy(f"patch/{file}.hdiff", f"temp/{file}.hdiff") if not alone:
shutil.move(f"temp/{file}", f"temp/{file.split('.')[0]}.original.pck") print(f"Patching")
# prepare args # update files
args = [ shutil.copy(_p("diff", f"{file}.hdiff"), _p("temp", f"{file}.hdiff"))
path("tools/hpatchz/hpatchz.exe"), shutil.move(_p("temp", file), _p("temp", f"{file.split('.')[0]}.original.pck"))
"-f",
path(f"temp/{file.split('.')[0]}.original.pck"),
path(f"temp/{file}.hdiff"),
path(f"temp/{file}")
]
# update spinner and call program # prepare args
spinner.text = f"[{curr}/{steps}] Patching" args = [
spinner.start() path("tools/hpatchz/hpatchz.exe"),
call(args) "-f",
spinner.stop() _p("temp", f"{file.split('.')[0]}.original.pck"),
print(f"[{curr}/{steps}] Patching") _p("temp", f"{file}.hdiff"),
_p("temp", file)
]
##################################### call(args)
### 3 - Extract patched .pck file ### self.progress(["total", 20])
#####################################
if skips[2] != "1": #####################################
if not alone: ### 3 - Extract patched .pck file ###
curr += 1 #####################################
# update spinner and call program if skips[2] != "1":
spinner.text = f"[{curr}/{steps}] Extracting patch" if not alone:
spinner.start() print(f"Extracting patch")
wavescan.extract(path(f"temp/{file}"), path(f"temp/patched_decoded")) wavescan.extract(path(f"temp/{file}"), path(f"temp/patched_decoded"))
spinner.stop() self.progress(["total", 30])
print(f"[{curr}/{steps}] Extracting patch")
# cleanup useless files to save storage # cleanup useless files to save storage
os.remove(f"temp/{file}") os.remove(_p("temp", file))
os.remove(f"temp/{file}.hdiff") os.remove(_p("temp", f"{file}.hdiff"))
os.remove(f"temp/{file.split('.')[0]}.original.pck") os.remove(_p("temp", f"{file.split('.')[0]}.original.pck"))
#################################### ####################################
### 4 - Search new/changed files ### ### 4 - Search new/changed files ###
#################################### ####################################
if skips[3] != "1": if skips[3] != "1":
if not alone: if not alone:
curr += 1 print(f"Filtering files")
# compare folders
diff = filecmp.dircmp(_p("temp", "original_decoded"), _p("temp", "patched_decoded"))
new_files, changed_files = diff.right_only, diff.diff_files
all_files = [*new_files, *changed_files]
# update spinner # merge files
spinner.text = f"[{curr}/{steps}] Filtering files" os.makedirs(_p("temp", "wem"), exist_ok=True)
spinner.start()
# compare folders for file in all_files:
diff = filecmp.dircmp(path("temp/original_decoded"), path("temp/patched_decoded")) shutil.move(_p("temp", f"patched_decoded/{file}"), _p("temp", f"wem/{file}"))
new_files, changed_files = diff.right_only, diff.diff_files
all_files = [*new_files, *changed_files]
# merge files # cleanup useless folders to save storage
os.makedirs(path("temp/wem"), exist_ok=True) shutil.rmtree(_p("temp", "original_decoded"))
shutil.rmtree(_p("temp", "patched_decoded"))
self.progress(["total", 35])
######################################
### 5 - Convert .wem files to .wav ###
######################################
if skips[4] != "1":
# updates folders
os.makedirs(_p("temp", "wav"), exist_ok=True)
print(f"Converting to wav")
pos = 0
# convert each file one by one
for file in all_files:
pos += 1
args = [
path("tools/vgmstream/vgmstream-cli.exe"),
"-o",
_p("temp", f"wav/{file.split('.')[0]}.wav"),
_p("temp", f"wem/{file}")
]
call(args)
self.progress(["total", round(35 + (pos * 30) / len(all_files))])
self.progress(["task", round((pos * 100) / len(all_files))])
# cleanup
shutil.rmtree(_p("temp", "wem"))
wem_length = len(all_files)
all_files = [f for f in os.listdir(_p("temp", "wav"))]
diff_length = wem_length - len(all_files)
if diff_length > 0:
print(f": Failed to extract {diff_length} files out of {wem_length} (probably no extractable content)")
#############################################
### 6 - Convert .wav files to .mp3 or ogg ###
#############################################
if skips[5] != "1":
# updates folders and progress bar
os.makedirs(_p("temp", audio_format), exist_ok=True)
print(f"Converting to {audio_format}")
# update file list
all_files = [f"{f.split('.')[0]}.wav" for f in all_files]
pos = 0
# convert each file one by one
for file in all_files:
pos += 1
args = [
path("tools/ffmpeg/ffmpeg.exe"),
"-i",
_p("temp", f"wav/{file}"),
"-acodec",
"libvorbis" if audio_format == "ogg" else "libmp3lame",
"-b:a",
"192k", # 192k | 4k
_p("temp", f"{audio_format}/{file.split('.')[0]}.{audio_format}"),
]
call(args)
self.progress(["total", (round(65 + (pos * 30) / len(all_files)))])
self.progress(["task", round((pos * 100) / len(all_files))])
# cleanup
shutil.rmtree(_p("temp", "wav"))
# update files list
all_files = [f"{f.split('.')[0]}.{audio_format}" for f in all_files]
if not alone:
new_files = [f"{f.split('.')[0]}.{audio_format}" for f in new_files]
changed_files = [f"{f.split('.')[0]}.{audio_format}" for f in changed_files]
#########################
### 7 - Map filenames ###
#########################
if skips[6] != "1" or mapper != None:
print(f"Mapping names")
os.makedirs(_p("temp", "map/unmapped"), exist_ok=True)
if not alone:
os.makedirs(_p("temp", f"map/new_files/unmapped"), exist_ok=True)
os.makedirs(_p("temp", f"map/changed_files/unmapped"), exist_ok=True)
lang = None
for file in all_files: for file in all_files:
shutil.move(f"temp/patched_decoded/{file}", f"temp/wem/{file}") file_name = file.split(".")[0]
base_path = "map"
if not alone:
if file in new_files:
base_path = "map/new_files"
elif file in changed_files:
base_path = "map/changed_files"
# cleanup useless folders to save storage key_data = mapper.get_key(file_name, lang is None)
shutil.rmtree("temp/original_decoded")
shutil.rmtree("temp/patched_decoded")
spinner.stop() if key_data is not None:
print(f"[{curr}/{steps}] Filtering files") if lang is None:
lang = key_data[1]
# TODO: use language for output path
print(f"\n: {lang} detected")
###################################### dir_path = _p("temp", f"{base_path}/{key_data[0]}.{audio_format}")
### 5 - Convert .wem files to .wav ### os.makedirs(os.path.dirname(dir_path), exist_ok=True)
###################################### shutil.copy(_p("temp", f"{audio_format}/{file}"), dir_path)
else:
shutil.copy(_p("temp", f"{audio_format}/{file}"), _p("temp", f"{base_path}/unmapped/{file}"))
if skips[4] != "1": ######################################################
curr += 1 ### 8 - Clean everything and move result to output ###
######################################################
# updates folders and progress bar if skips[7] != "1":
os.makedirs(path("temp/wav"), exist_ok=True) print(f"Cleaning up")
bar = PixelBar(f"[{curr}/{steps}] Converting to wav ", max=len(all_files), suffix='%(percent).1f%% - %(eta)ds left')
# convert each file one by one filename = filename.split('.')[0]
for file in all_files:
args = [
path("tools/vgmstream/vgmstream-cli.exe"),
"-o",
path(f"temp/wav/{file.split('.')[0]}.wav"),
path(f"temp/wem/{file}")
]
call(args) shutil.move(_p("temp", "map"), _p("output", filename))
bar.next()
bar.finish()
# cleanup self.paths["temp"].cleanup()
shutil.rmtree("temp/wem")
wem_length = len(all_files)
all_files = [f for f in os.listdir(path("temp/wav"))]
diff_length = wem_length - len(all_files)
if diff_length > 0: self.progress["total", 100]
print(f": Failed to extract {diff_length} files out of {wem_length} (probably no extractable content)")
############################################# except Exception as e:
### 6 - Convert .wav files to .mp3 or ogg ### print("")
############################################# print("An error occured while processing this file ! Skipping... details of the error bellow :")
print(f"Line {sys.exc_info()[-1].tb_lineno}, {e}")
if skips[5] != "1": print("-"*30)
curr += 1 print("Done extracting everything !")
# updates folders and progress bar
os.makedirs(path(f"temp/{audio_format}"), exist_ok=True)
bar = PixelBar(
f"[{curr}/{steps}] Converting to {audio_format} ",
max=len(all_files),
suffix="%(percent).1f%% - %(eta)ds left",
)
# update file list
all_files = [f"{f.split('.')[0]}.wav" for f in all_files]
# convert each file one by one
for file in all_files:
args = [
path("tools/ffmpeg/ffmpeg.exe"),
"-i",
path(f"temp/wav/{file}"),
"-acodec",
"libvorbis" if audio_format == "ogg" else "libmp3lame",
"-b:a",
"192k",
path(f"temp/{audio_format}/{file.split('.')[0]}.{audio_format}"),
]
call(args)
bar.next()
bar.finish()
# cleanup
shutil.rmtree("temp/wav")
# update files list
all_files = [f"{f.split('.')[0]}.{audio_format}" for f in all_files]
if not alone:
new_files = [f"{f.split('.')[0]}.{audio_format}" for f in new_files]
changed_files = [f"{f.split('.')[0]}.{audio_format}" for f in changed_files]
#########################
### 7 - Map filenames ###
#########################
if skips[6] != "1":
curr += 1
# update spinner
spinner.text = f"[{curr}/{steps}] Mapping names"
spinner.start()
os.makedirs(path(f"temp/map/unmapped"), exist_ok=True)
if not alone:
os.makedirs(path(f"temp/map/new_files/unmapped"), exist_ok=True)
os.makedirs(path(f"temp/map/changed_files/unmapped"), exist_ok=True)
lang = None
for file in all_files:
file_name = file.split(".")[0]
base_path = "temp/map"
if not alone:
if file in new_files:
base_path = "temp/map/new_files"
elif file in changed_files:
base_path = "temp/map/changed_files"
key_data = mapper.get_key(file_name, lang is None)
if key_data is not None:
if lang is None:
lang = key_data[1]
# TODO: use language for output path
print(f"\n: {lang} detected")
dir_path = path(f"{base_path}/{key_data[0]}.{audio_format}")
os.makedirs(os.path.dirname(dir_path), exist_ok=True)
shutil.copy(path(f"temp/{audio_format}/{file}"), dir_path)
else:
shutil.copy(path(f"temp/{audio_format}/{file}"), path(f"{base_path}/unmapped/{file}"))
# stop spinner
spinner.stop()
print(f"[{curr}/{steps}] Mapping names")
######################################################
### 8 - Clean everything and move result to output ###
######################################################
if skips[7] != "1":
curr += 1
# update spinner
spinner.text = f"[{curr}/{steps}] Cleaning up"
spinner.start()
filename = filename.split('.')[0]
shutil.move(f"temp/map", f"output/{filename}")
spinner.stop()
print(f"[{curr}/{steps}] Cleaning up")
except Exception as e:
print("")
print("An error occured while processing this file ! Skipping to the next one, details of the error bellow :")
print(f"Line {sys.exc_info()[-1].tb_lineno}, {e}")
# all files processed
if os.path.exists("temp") and skips[8] != "1":
shutil.rmtree("temp")
print("-"*30)
print("Done extracting everything !")
if __name__ == "__main__":
main()

296
gui.ui Normal file
View File

@@ -0,0 +1,296 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AnimeWwise</class>
<widget class="QMainWindow" name="AnimeWwise">
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1100</width>
<height>800</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>1100</width>
<height>800</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>1100</width>
<height>800</height>
</size>
</property>
<property name="windowTitle">
<string>AnimeWwise</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QTabWidget" name="tabs">
<property name="geometry">
<rect>
<x>4</x>
<y>-1</y>
<width>1091</width>
<height>791</height>
</rect>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<property name="usesScrollButtons">
<bool>true</bool>
</property>
<property name="documentMode">
<bool>false</bool>
</property>
<property name="tabsClosable">
<bool>false</bool>
</property>
<property name="movable">
<bool>false</bool>
</property>
<property name="tabBarAutoHide">
<bool>false</bool>
</property>
<widget class="QWidget" name="extractTab">
<property name="enabled">
<bool>true</bool>
</property>
<attribute name="title">
<string>Extract</string>
</attribute>
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>9</x>
<y>9</y>
<width>1071</width>
<height>741</height>
</rect>
</property>
<layout class="QVBoxLayout" name="mainVLayout">
<item>
<layout class="QGridLayout" name="ioGrid">
<item row="2" column="0">
<widget class="QLabel" name="outputLabel">
<property name="text">
<string>Output folder</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="changeOutput">
<property name="text">
<string>Select</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="changeInput">
<property name="text">
<string>Select</string>
</property>
<property name="default">
<bool>false</bool>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="changeAltInput">
<property name="text">
<string>Select</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="inputLabel">
<property name="text">
<string>Input folder</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="altInputLabel">
<property name="text">
<string>Diff folder (optional)</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="inputPath">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string/>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="altInputPath">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="outputPath">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="separatorA">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="settingsGrid">
<item row="0" column="0">
<widget class="QLabel" name="outputFormatLabel">
<property name="text">
<string>Output format</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="assetMapLabel">
<property name="text">
<string>Asset map (optional)</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="outputFormat">
<property name="currentText">
<string/>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="assetMap"/>
</item>
<item row="0" column="2">
<widget class="QLabel" name="spacingLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="startButton">
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="separatorB">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="progressWrapperLayout">
<item>
<layout class="QHBoxLayout" name="progressLayout">
<item>
<widget class="QLabel" name="progressLabel">
<property name="text">
<string>Progress</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progress">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="taskProgressLayout">
<item>
<widget class="QLabel" name="taskProgressLabel">
<property name="text">
<string>Task Progress</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="taskProgress">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="console">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>220</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QWidget" name="browseTab">
<attribute name="title">
<string>Browse</string>
</attribute>
</widget>
</widget>
</widget>
</widget>
<tabstops>
<tabstop>inputPath</tabstop>
<tabstop>changeInput</tabstop>
<tabstop>altInputPath</tabstop>
<tabstop>changeAltInput</tabstop>
<tabstop>outputPath</tabstop>
<tabstop>changeOutput</tabstop>
<tabstop>outputFormat</tabstop>
<tabstop>assetMap</tabstop>
<tabstop>startButton</tabstop>
<tabstop>tabs</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>