1
0
mirror of https://github.com/Escartem/AnimeWwise.git synced 2026-06-23 04:30:33 +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,41 +23,57 @@ 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):
# TODO: add skip / select mapping option self.map = _map
parser.add_argument("--format", nargs="?", type=str, default="mp3", help="Output audio format, can be either mp3 or ogg") self.format = _format
args = parser.parse_args()
formats = ["mp3", "ogg"] self.paths = {
audio_format = "mp3" "input": input_folder,
if args.format in formats: "output": output_folder,
audio_format = args.format "diff": diff_folder,
"temp": tempfile.TemporaryDirectory()
}
self.progress = progress
# TODO: add skip / select mapping option
def path(self, base, path):
base_path = self.paths[base]
if base == "temp":
base_path = base_path.name
return os.path.join(base_path, path)
def extract(self):
audio_format = self.format
mapper = self.map
_p = self.path # lazy
print(f'Format: {audio_format}') print(f'Format: {audio_format}')
# Initial cleanup # TODO: ui popup
if os.path.exists("temp") and skips[8] != "1": # if os.path.exists("output") and len(os.listdir("output")) > 0:
shutil.rmtree("temp") # print("The output folder needs to be cleared, continue ? [Y/N]")
# select = input(">")
if os.path.exists("output") and len(os.listdir("output")) > 0: # if select.lower() == "y":
print("The output folder needs to be cleared, continue ? [Y/N]") # shutil.rmtree("output")
select = input(">") # else:
if select.lower() == "y": # print("Aborting")
shutil.rmtree("output") # exit()
else:
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 !")
# self.progress(100)
return return
print(self.paths["temp"].name)
print(f"{len(files)} file{'s' if len(files) != 1 else ''} to extract") print(f"{len(files)} file{'s' if len(files) != 1 else ''} to extract")
iteration = 0 iteration = 0
@@ -74,34 +85,27 @@ def main():
filename = f"{file.split('.')[0]}.hdiff.pck" filename = f"{file.split('.')[0]}.hdiff.pck"
print(f"--- {filename} ({iteration}/{len(files)}) ---") print(f"--- {filename} ({iteration}/{len(files)}) ---")
alone, steps, curr = False, 8, 1 alone = False #8 steps
if file in alone_files: if file in alone_files:
alone, steps = True, 5 alone = True # 5 steps
###################################### ######################################
### 1 - Extract original .pck file ### ### 1 - Extract original .pck file ###
###################################### ######################################
if skips[0] != "1": if skips[0] != "1":
# update files shutil.copy(_p("input", file), _p("temp", 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" output_path = "original_decoded"
if alone: if alone:
output_path = "wem" output_path = "wem"
# update spinner and call program print(f"Extracting")
spinner.text = f"[{curr}/{steps}] Extracting" wavescan.extract(_p("temp", file), _p("temp", output_path))
spinner.start() self.progress(["total", 15])
wavescan.extract(path(f"temp/{file}"), path(f"temp/{output_path}"))
spinner.stop()
print(f"[{curr}/{steps}] Extracting")
if alone: if alone:
all_files = os.listdir(path("temp/wem")) all_files = os.listdir(_p("temp", "wem"))
###################################### ######################################
### 2 - Patch the .pck with .hdiff ### ### 2 - Patch the .pck with .hdiff ###
@@ -109,27 +113,23 @@ def main():
if skips[1] != "1": if skips[1] != "1":
if not alone: if not alone:
curr += 1 print(f"Patching")
# update files # update files
shutil.copy(f"patch/{file}.hdiff", f"temp/{file}.hdiff") shutil.copy(_p("diff", f"{file}.hdiff"), _p("temp", f"{file}.hdiff"))
shutil.move(f"temp/{file}", f"temp/{file.split('.')[0]}.original.pck") shutil.move(_p("temp", file), _p("temp", f"{file.split('.')[0]}.original.pck"))
# prepare args # prepare args
args = [ args = [
path("tools/hpatchz/hpatchz.exe"), path("tools/hpatchz/hpatchz.exe"),
"-f", "-f",
path(f"temp/{file.split('.')[0]}.original.pck"), _p("temp", f"{file.split('.')[0]}.original.pck"),
path(f"temp/{file}.hdiff"), _p("temp", f"{file}.hdiff"),
path(f"temp/{file}") _p("temp", file)
] ]
# update spinner and call program
spinner.text = f"[{curr}/{steps}] Patching"
spinner.start()
call(args) call(args)
spinner.stop() self.progress(["total", 20])
print(f"[{curr}/{steps}] Patching")
##################################### #####################################
### 3 - Extract patched .pck file ### ### 3 - Extract patched .pck file ###
@@ -137,19 +137,14 @@ def main():
if skips[2] != "1": if skips[2] != "1":
if not alone: if not alone:
curr += 1 print(f"Extracting patch")
# update spinner and call program
spinner.text = f"[{curr}/{steps}] Extracting patch"
spinner.start()
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 ###
@@ -157,58 +152,51 @@ def main():
if skips[3] != "1": if skips[3] != "1":
if not alone: if not alone:
curr += 1 print(f"Filtering files")
# update spinner
spinner.text = f"[{curr}/{steps}] Filtering files"
spinner.start()
# compare folders # compare folders
diff = filecmp.dircmp(path("temp/original_decoded"), path("temp/patched_decoded")) diff = filecmp.dircmp(_p("temp", "original_decoded"), _p("temp", "patched_decoded"))
new_files, changed_files = diff.right_only, diff.diff_files new_files, changed_files = diff.right_only, diff.diff_files
all_files = [*new_files, *changed_files] all_files = [*new_files, *changed_files]
# merge files # merge files
os.makedirs(path("temp/wem"), exist_ok=True) os.makedirs(_p("temp", "wem"), exist_ok=True)
for file in all_files: for file in all_files:
shutil.move(f"temp/patched_decoded/{file}", f"temp/wem/{file}") shutil.move(_p("temp", f"patched_decoded/{file}"), _p("temp", f"wem/{file}"))
# cleanup useless folders to save storage # cleanup useless folders to save storage
shutil.rmtree("temp/original_decoded") shutil.rmtree(_p("temp", "original_decoded"))
shutil.rmtree("temp/patched_decoded") shutil.rmtree(_p("temp", "patched_decoded"))
self.progress(["total", 35])
spinner.stop()
print(f"[{curr}/{steps}] Filtering files")
###################################### ######################################
### 5 - Convert .wem files to .wav ### ### 5 - Convert .wem files to .wav ###
###################################### ######################################
if skips[4] != "1": if skips[4] != "1":
curr += 1
# updates folders and progress bar
os.makedirs(path("temp/wav"), exist_ok=True)
bar = PixelBar(f"[{curr}/{steps}] Converting to wav ", max=len(all_files), suffix='%(percent).1f%% - %(eta)ds left')
# updates folders
os.makedirs(_p("temp", "wav"), exist_ok=True)
print(f"Converting to wav")
pos = 0
# convert each file one by one # convert each file one by one
for file in all_files: for file in all_files:
pos += 1
args = [ args = [
path("tools/vgmstream/vgmstream-cli.exe"), path("tools/vgmstream/vgmstream-cli.exe"),
"-o", "-o",
path(f"temp/wav/{file.split('.')[0]}.wav"), _p("temp", f"wav/{file.split('.')[0]}.wav"),
path(f"temp/wem/{file}") _p("temp", f"wem/{file}")
] ]
call(args) call(args)
bar.next() self.progress(["total", round(35 + (pos * 30) / len(all_files))])
bar.finish() self.progress(["task", round((pos * 100) / len(all_files))])
# cleanup # cleanup
shutil.rmtree("temp/wem") shutil.rmtree(_p("temp", "wem"))
wem_length = len(all_files) wem_length = len(all_files)
all_files = [f for f in os.listdir(path("temp/wav"))] all_files = [f for f in os.listdir(_p("temp", "wav"))]
diff_length = wem_length - len(all_files) diff_length = wem_length - len(all_files)
if diff_length > 0: if diff_length > 0:
@@ -219,38 +207,33 @@ def main():
############################################# #############################################
if skips[5] != "1": if skips[5] != "1":
curr += 1
# updates folders and progress bar # updates folders and progress bar
os.makedirs(path(f"temp/{audio_format}"), exist_ok=True) os.makedirs(_p("temp", audio_format), exist_ok=True)
bar = PixelBar( print(f"Converting to {audio_format}")
f"[{curr}/{steps}] Converting to {audio_format} ",
max=len(all_files),
suffix="%(percent).1f%% - %(eta)ds left",
)
# update file list # update file list
all_files = [f"{f.split('.')[0]}.wav" for f in all_files] all_files = [f"{f.split('.')[0]}.wav" for f in all_files]
pos = 0
# convert each file one by one # convert each file one by one
for file in all_files: for file in all_files:
pos += 1
args = [ args = [
path("tools/ffmpeg/ffmpeg.exe"), path("tools/ffmpeg/ffmpeg.exe"),
"-i", "-i",
path(f"temp/wav/{file}"), _p("temp", f"wav/{file}"),
"-acodec", "-acodec",
"libvorbis" if audio_format == "ogg" else "libmp3lame", "libvorbis" if audio_format == "ogg" else "libmp3lame",
"-b:a", "-b:a",
"192k", "192k", # 192k | 4k
path(f"temp/{audio_format}/{file.split('.')[0]}.{audio_format}"), _p("temp", f"{audio_format}/{file.split('.')[0]}.{audio_format}"),
] ]
call(args) call(args)
bar.next() self.progress(["total", (round(65 + (pos * 30) / len(all_files)))])
bar.finish() self.progress(["task", round((pos * 100) / len(all_files))])
# cleanup # cleanup
shutil.rmtree("temp/wav") shutil.rmtree(_p("temp", "wav"))
# update files list # update files list
all_files = [f"{f.split('.')[0]}.{audio_format}" for f in all_files] all_files = [f"{f.split('.')[0]}.{audio_format}" for f in all_files]
@@ -263,28 +246,24 @@ def main():
### 7 - Map filenames ### ### 7 - Map filenames ###
######################### #########################
if skips[6] != "1": if skips[6] != "1" or mapper != None:
curr += 1 print(f"Mapping names")
# update spinner os.makedirs(_p("temp", "map/unmapped"), exist_ok=True)
spinner.text = f"[{curr}/{steps}] Mapping names"
spinner.start()
os.makedirs(path(f"temp/map/unmapped"), exist_ok=True)
if not alone: if not alone:
os.makedirs(path(f"temp/map/new_files/unmapped"), exist_ok=True) os.makedirs(_p("temp", f"map/new_files/unmapped"), exist_ok=True)
os.makedirs(path(f"temp/map/changed_files/unmapped"), exist_ok=True) os.makedirs(_p("temp", f"map/changed_files/unmapped"), exist_ok=True)
lang = None lang = None
for file in all_files: for file in all_files:
file_name = file.split(".")[0] file_name = file.split(".")[0]
base_path = "temp/map" base_path = "map"
if not alone: if not alone:
if file in new_files: if file in new_files:
base_path = "temp/map/new_files" base_path = "map/new_files"
elif file in changed_files: elif file in changed_files:
base_path = "temp/map/changed_files" base_path = "map/changed_files"
key_data = mapper.get_key(file_name, lang is None) key_data = mapper.get_key(file_name, lang is None)
@@ -294,44 +273,31 @@ def main():
# TODO: use language for output path # TODO: use language for output path
print(f"\n: {lang} detected") print(f"\n: {lang} detected")
dir_path = path(f"{base_path}/{key_data[0]}.{audio_format}") dir_path = _p("temp", f"{base_path}/{key_data[0]}.{audio_format}")
os.makedirs(os.path.dirname(dir_path), exist_ok=True) os.makedirs(os.path.dirname(dir_path), exist_ok=True)
shutil.copy(path(f"temp/{audio_format}/{file}"), dir_path) shutil.copy(_p("temp", f"{audio_format}/{file}"), dir_path)
else: else:
shutil.copy(path(f"temp/{audio_format}/{file}"), path(f"{base_path}/unmapped/{file}")) shutil.copy(_p("temp", f"{audio_format}/{file}"), _p("temp", f"{base_path}/unmapped/{file}"))
# stop spinner
spinner.stop()
print(f"[{curr}/{steps}] Mapping names")
###################################################### ######################################################
### 8 - Clean everything and move result to output ### ### 8 - Clean everything and move result to output ###
###################################################### ######################################################
if skips[7] != "1": if skips[7] != "1":
curr += 1 print(f"Cleaning up")
# update spinner
spinner.text = f"[{curr}/{steps}] Cleaning up"
spinner.start()
filename = filename.split('.')[0] filename = filename.split('.')[0]
shutil.move(f"temp/map", f"output/{filename}") shutil.move(_p("temp", "map"), _p("output", filename))
spinner.stop() self.paths["temp"].cleanup()
print(f"[{curr}/{steps}] Cleaning up")
self.progress["total", 100]
except Exception as e: except Exception as e:
print("") print("")
print("An error occured while processing this file ! Skipping to the next one, details of the error bellow :") print("An error occured while processing this file ! Skipping... details of the error bellow :")
print(f"Line {sys.exc_info()[-1].tb_lineno}, {e}") 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("-"*30)
print("Done extracting everything !") 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>