qua2osu-gui.py

#
import os
import sys
import time
import webbrowser  # to open the explorer cross-platform

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

from conversion import convertMapset
#

gui.py is autogenerated from gui.ui (qt designer)

#

use “pyuic5 -x gui/gui.ui -o gui/gui.py” after editing gui.ui with qt designer

from gui.gui import Ui_MainWindow
#

Custom window class with all of the GUI functionality

class IceMainWindow(QMainWindow, Ui_MainWindow):
#

All of the objects themselves are managed by QT designer in the gui.ui (gui.py) file

#
    def __init__(self):
        QMainWindow.__init__(self)
        self.setupUi(self)
        self.inputToolButton.clicked.connect(self.openInputDirectoryDialog)
        self.outputToolButton.clicked.connect(self.openOutputDirectoryDialog)
        self.convertPushButton.clicked.connect(self.convertOnClick)

        self.updateStatus("Finished setup")
#

Opens the input directory dialog and sets the input path in the GUI

    def openInputDirectoryDialog(self):
#
        path = str(QFileDialog.getExistingDirectory(
            self, "Select Input Folder"))
        self.inputLineEdit.setText(path)
        self.updateStatus("Set input path to " + path)
#

Opens the output directory dialog and sets the input path in the GUI

    def openOutputDirectoryDialog(self):
#
        path = str(QFileDialog.getExistingDirectory(
            self, "Select Output Folder"))
        self.outputLineEdit.setText(path)
        self.updateStatus("Set output path to " + path)
#

Wrapper for updating the status label, always shows the two last lines

    def updateStatus(self, status):
#
        currentText = self.statusLabel.text()
        lastLine = currentText.split("\n")[-1]
        self.statusLabel.setText(lastLine + "\n" + status)
#

Wrapper for updating the progress bar maximum

    def updateProgressBarMax(self, maximum):
#
        self.progressBar.setMaximum(maximum)
#

Increments the progress bar by one unit total, adds a smooth animation

    def incrementProgressBarValue(self):
#
        self.progressBar.setValue(self.progressBar.value() + 1)
#

Sets up the converter thread

    def initiateConverterThread(self, inputPath, outputPath, options):
#
        converterThread = ConverterThread(inputPath, outputPath, options)
        converterThread.updateStatus.connect(self.updateStatus)
        converterThread.updateProgressbarMax.connect(
            self.updateProgressBarMax)
        converterThread.incrementProgressbarValue.connect(
            self.incrementProgressBarValue)

        return converterThread
#

Starts the map conversion of the folder

    def convertOnClick(self):
#
        inputPath = self.inputLineEdit.text()
        outputPath = self.outputLineEdit.text()

        if inputPath == "" or outputPath == "":
            self.updateStatus("Empty paths detected, using defaults")

            if inputPath == "":
                inputPath = "input"

            if outputPath == "":
                outputPath = "output"

        selectedOd = self.odDoubleSpinBox.value()
        selectedHp = self.hpDoubleSpinBox.value()
        selectedHitSoundVolume = self.hsVolumeSpinBox.value()
#

please tell me if there’s an easier way to lookup which radio button is checked because this is horrible

        selectedSampleSet = ""
        sampleSetButtons = [
            self.sampleSetNormalRadioButton,
            self.sampleSetSoftRadioButton,
            self.sampleSetDrumRadioButton
        ]

        for button in sampleSetButtons:
            if button.isChecked():
                selectedSampleSet = button.text()
                break

        options = {
            "od": selectedOd,
            "hp": selectedHp,
            "hitSoundVolume": selectedHitSoundVolume,
            "sampleSet": selectedSampleSet
        }

        self.progressBar.setValue(0)

        self.converterThread = self.initiateConverterThread(
            inputPath, outputPath, options)
        self.converterThread.start()
#

Using a different thread for the map conversion

class ConverterThread(QThread):
#

Otherwise the UI would freeze and become unresponsive during the conversion

    updateStatus = pyqtSignal(str)
    incrementProgressbarValue = pyqtSignal()
    updateProgressbarMax = pyqtSignal(int)
#
    def __init__(self, inputPath, outputPath, options):
        QThread.__init__(self)
        self.inputPath = inputPath
        self.outputPath = outputPath
        self.options = options
#
    def __del__(self):
        self.wait()
#
    def run(self):
        qpFilesInInputDir = []

        for file in os.listdir(self.inputPath):
            path = os.path.join(self.inputPath, file)
            if file.endswith('.qp') and os.path.isfile(path):
                qpFilesInInputDir.append(file)

        numberOfQpFiles = len(qpFilesInInputDir)

        if numberOfQpFiles == 0:
            self.updateStatus.emit("No mapsets found in " + self.inputPath)
            return

        self.updateProgressbarMax.emit(numberOfQpFiles)

        start = time.time()
        count = 1

        for file in qpFilesInInputDir:
            filePath = os.path.join(self.inputPath, file)

            self.updateStatus.emit(f"({count}/{numberOfQpFiles}) "
                                   f"Converting {filePath}")

            convertMapset(filePath, self.outputPath, self.options)
            count += 1
            self.incrementProgressbarValue.emit()

        end = time.time()
        timeElapsed = round(end - start, 2)

        self.updateStatus.emit(
            f"Finished converting all mapsets,"
            f"total time elapsed: {timeElapsed} seconds"
        )
#

Opens output folder in explorer

        absoluteOutputPath = os.path.realpath(self.outputPath)
        webbrowser.open("file:///" + absoluteOutputPath)

        return
#

Custom QApplication class for the sole purpose of applying the Fusion style

class IceApp(QApplication):
#
#
    def __init__(self):
        super().__init__(sys.argv)
        self.setStyle("Fusion")
#
def main():
    app = IceApp()
    iceApp = IceMainWindow()
    iceApp.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()