From db8c21f4fcd5e203e7052aeb96e41ef3d0f3d9b1 Mon Sep 17 00:00:00 2001 From: VocalFan <45863583+VocalFan@users.noreply.github.com> Date: Thu, 23 May 2024 15:53:29 -0400 Subject: [PATCH] FINALLY XD --- .github/workflows/pr-builds.yml | 51 ++++++++++++++++++ psychtobase/main.py | 72 ++++++++++++------------- psychtobase/src/Paths.py | 8 +-- psychtobase/src/files.py | 9 ++-- psychtobase/src/log.py | 16 ++++-- psychtobase/src/tools/CharacterTools.py | 6 +-- psychtobase/src/tools/ChartTools.py | 21 ++++---- psychtobase/src/window.py | 11 ++-- setup.py | 4 +- 9 files changed, 130 insertions(+), 68 deletions(-) create mode 100644 .github/workflows/pr-builds.yml diff --git a/.github/workflows/pr-builds.yml b/.github/workflows/pr-builds.yml new file mode 100644 index 0000000..25f934f --- /dev/null +++ b/.github/workflows/pr-builds.yml @@ -0,0 +1,51 @@ +name: FNF Porter Build + +on: + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest, macos-latest, ubuntu-latest] + include: + - os: windows-latest + icon: icon.ico + ext: .exe + - os: macos-latest + icon: icon.icns + ext: + - os: ubuntu-latest + icon: "" + ext: + + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Cache pip + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pyinstaller numpy pydub luaparser pyqt6 + - name: Build with PyInstaller + run: | + pyinstaller --onefile setup.py --icon=${{ matrix.icon }} --noconsole -n "FNF Porter PR-${{ github.event.pull_request.number }}" + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: FNF_Porter_Build_${{ matrix.os }} + path: dist/*${{ matrix.ext }} diff --git a/psychtobase/main.py b/psychtobase/main.py index 8503f88..76dd74c 100644 --- a/psychtobase/main.py +++ b/psychtobase/main.py @@ -1,9 +1,9 @@ from base64 import b64decode import json import logging -import os import shutil import time +from pathlib import Path from psychtobase.src import Constants from psychtobase.src.tools import ModConvertTools as ModTools import threading @@ -20,14 +20,14 @@ vocalSplitMasterToggle = True def folderMake(folder_path): - if not os.path.exists(folder_path): + if not Path(folder_path).exists(): try: - os.makedirs(folder_path) + Path(folder_path).mkdir(parents=True, exist_ok=True) except Exception as e: logging.error(f'Something went wrong: {e}') def fileCopy(source, destination): - if os.path.exists(source): + if Path(source).exists(): try: shutil.copyfile(source, destination) except Exception as e: @@ -36,12 +36,12 @@ def fileCopy(source, destination): logging.warn(f'Path {source} doesn\'t exist.') def treeCopy(source, destination): - if not os.path.exists(destination) and os.path.exists(source): + if not Path(destination).exists() and Path(source).exists(): try: shutil.copytree(source, destination) except Exception as e: logging.error(f'Something went wrong: {e}') - elif not os.path.exists(source): + elif not Path(source).exists(): logging.warn(f'Path {source} does not exist.') def convert(psych_mod_folder, result_folder, options): @@ -58,7 +58,7 @@ def convert(psych_mod_folder, result_folder, options): logging.info(options) modName = psych_mod_folder # MOD FOLDER PSYCH ENGINE - modFoldername = os.path.basename(psych_mod_folder) + modFoldername = Path(psych_mod_folder).name logging.info(f'Converting from{psych_mod_folder} to {result_folder}') @@ -69,7 +69,7 @@ def convert(psych_mod_folder, result_folder, options): psychPackJson = dir[0] polymodMetaDir = dir[1] - if os.path.exists(f'{modName}{psychPackJson}'): + if Path(f'{modName}{psychPackJson}').exists(): polymod_meta = ModTools.convertPack(json.loads(open(f'{modName}{psychPackJson}', 'r').read())) folderMake(f'{result_folder}/{modFoldername}/') open(f'{result_folder}/{modFoldername}/{polymodMetaDir}', 'w').write(json.dumps(polymod_meta, indent=4)) @@ -85,7 +85,7 @@ def convert(psych_mod_folder, result_folder, options): psychPackPng = dir[0] polymodIcon = dir[1] - if os.path.exists(f'{modName}{psychPackPng}'): + if Path(f'{modName}{psychPackPng}').exists(): folderMake(f'{result_folder}/{modFoldername}/') try: fileCopy(f'{modName}{psychPackPng}', f'{result_folder}/{modFoldername}/{polymodIcon}') @@ -108,7 +108,7 @@ def convert(psych_mod_folder, result_folder, options): psychCredits = dir[0] modCredits = dir[1] - if os.path.exists(f'{modName}{psychCredits}'): + if Path(f'{modName}{psychCredits}').exists(): folderMake(f'{result_folder}/{modFoldername}/') resultCredits = ModTools.convertCredits(open(f'{modName}{psychCredits}', 'r').read()) open(f'{result_folder}/{modFoldername}/{modCredits}', 'w').write(resultCredits) @@ -126,7 +126,7 @@ def convert(psych_mod_folder, result_folder, options): for song in songs: logging.info(f'Checking if {song} is a valid chart directory...') - if os.path.isdir(song): + if Path(song).is_dir(): logging.info(f'Loading charts in {song}') outputpath = f'{result_folder}/{modFoldername}' @@ -159,10 +159,10 @@ def convert(psych_mod_folder, result_folder, options): folderMake(f'{result_folder}/{modFoldername}{bgCharacterAssets}') for character in files.findAll(f'{psychCharacterAssets}*'): - if not os.path.isdir(character): + if Path(character).is_file(): logging.info(f'Copying asset {character}') try: - fileCopy(character, result_folder + f'/{modFoldername}' + bgCharacterAssets + os.path.basename(character)) + fileCopy(character, result_folder + f'/{modFoldername}' + bgCharacterAssets + Path(character).name) except Exception as e: logging.error(f'Could not copy asset {character}: {e}') else: @@ -183,7 +183,7 @@ def convert(psych_mod_folder, result_folder, options): for character in files.findAll(f'{psychCharacters}*'): logging.info(f'Checking if {character} is a file...') - if not os.path.isdir(character) and character.endswith('.json'): + if Path(character).is_file() and character.endswith('.json'): converted_char = CharacterObject(character, result_folder + f'/{modFoldername}' + bgCharacters) converted_char.convert() @@ -203,10 +203,10 @@ def convert(psych_mod_folder, result_folder, options): folderMake(f'{result_folder}/{modFoldername}{bgCharacterAssets}') for character in files.findAll(f'{psychCharacterAssets}*'): - if not os.path.isdir(character): + if Path(character).is_file(): logging.info(f'Copying asset {character}') try: - fileCopy(character, result_folder + f'/{modFoldername}' + bgCharacterAssets + os.path.basename(character)) + fileCopy(character, result_folder + f'/{modFoldername}' + bgCharacterAssets + Path(character).name) except Exception as e: logging.error(f'Could not copy asset {character}: {e}') else: @@ -227,25 +227,25 @@ def convert(psych_mod_folder, result_folder, options): _allSongFiles = files.findAll(f'{psychSongs}*') for song in _allSongFiles: - _songKeyUnformatted = os.path.basename(song) + _songKeyUnformatted = Path(song).name songKeyFormatted = _songKeyUnformatted.replace(' ', '-').lower() - _allSongFilesClear = [os.path.basename(__song) for __song in _allSongFiles] + _allSongFilesClear = [Path(__song).name for __song in _allSongFiles] isPsych073Song = 'Voices-Opponent.ogg' in _allSongFilesClear and 'Voices-Player.ogg' in _allSongFilesClear logging.info(f'Checking if {song} is a valid song directory...') - if os.path.isdir(song): + if Path(song).is_dir(): logging.info(f'Copying files in {song}') for songFile in files.findAll(f'{song}/*'): - if os.path.basename(songFile) == 'Inst.ogg' and songOptions['inst']: + if Path(songFile).name == 'Inst.ogg' and songOptions['inst']: logging.info(f'Copying asset {songFile}') try: folderMake(f'{result_folder}/{modFoldername}{bgSongs}{songKeyFormatted}') fileCopy(songFile, - f'{result_folder}/{modFoldername}{bgSongs}{songKeyFormatted}/{os.path.basename(songFile)}') + f'{result_folder}/{modFoldername}{bgSongs}{songKeyFormatted}/{Path(songFile).name}') except Exception as e: logging.error(f'Could not copy asset {songFile}: {e}') - elif os.path.basename(songFile) == 'Voices.ogg' and songOptions['split'] and vocalSplitMasterToggle and not isPsych073Song: + elif Path(songFile).name == 'Voices.ogg' and songOptions['split'] and vocalSplitMasterToggle and not isPsych073Song: # Vocal Split songKey = _songKeyUnformatted @@ -277,7 +277,7 @@ def convert(psych_mod_folder, result_folder, options): try: folderMake(f'{result_folder}/{modFoldername}{bgSongs}{songKeyFormatted}') fileCopy(songFile, - f'{result_folder}/{modFoldername}{bgSongs}{songKeyFormatted}/{os.path.basename(songFile)}') + f'{result_folder}/{modFoldername}{bgSongs}{songKeyFormatted}/{Path(songFile).name}') except Exception as e: logging.error(f'Could not copy asset {songFile}: {e}') elif isPsych073Song: @@ -292,9 +292,9 @@ def convert(psych_mod_folder, result_folder, options): if chart != None: try: - if os.path.basename(songFile) == 'Voices-Player.ogg': + if Path(songFile).name == 'Voices-Player.ogg': fileCopy(songFile, f"{result_folder}/{modFoldername}{bgSongs}{songKeyFormatted}/Voices-{chart.metadata['playData']['characters'].get('player')}.ogg") - elif os.path.basename(songFile) == 'Voices-Opponent.ogg': + elif Path(songFile).name == 'Voices-Opponent.ogg': fileCopy(songFile, f"{result_folder}/{modFoldername}{bgSongs}{songKeyFormatted}/Voices-{chart.metadata['playData']['characters'].get('opponent')}.ogg") except Exception as e: @@ -303,7 +303,7 @@ def convert(psych_mod_folder, result_folder, options): logging.warning(f'{songKeyFormatted} is a Psych Engine 0.7.3 song with separated vocals. Copy rename was attempted, however your chart was not found. These files will be copied instead.') ## Psst! If you were taken here, your chart is needed to set your character to the file! fileCopy(songFile, - f'{result_folder}/{modFoldername}{bgSongs}{songKeyFormatted}/{os.path.basename(songFile)}') + f'{result_folder}/{modFoldername}{bgSongs}{songKeyFormatted}/{Path(songFile).name}') elif songOptions['voices']: logging.info(f'Copying asset {songFile}') @@ -313,7 +313,7 @@ def convert(psych_mod_folder, result_folder, options): try: folderMake(f'{result_folder}/{modFoldername}{bgSongs}{songKeyFormatted}') fileCopy(songFile, - f'{result_folder}/{modFoldername}{bgSongs}{songKeyFormatted}/{os.path.basename(songFile)}') + f'{result_folder}/{modFoldername}{bgSongs}{songKeyFormatted}/{Path(songFile).name}') except Exception as e: logging.error(f'Could not copy asset {songFile}: {e}') weekCOptions = options.get('weeks', { @@ -334,7 +334,7 @@ def convert(psych_mod_folder, result_folder, options): logging.info(f'Loading {week} into the converter...') weekJSON = json.loads(open(week, 'r').read()) - week_filename = os.path.basename(week) + week_filename = Path(week).name converted_week = WeekTools.convert(weekJSON, modName, week_filename) open(f'{result_folder}/{modFoldername}{baseLevels}{week_filename}', 'w').write(json.dumps(converted_week, indent=4)) @@ -352,7 +352,7 @@ def convert(psych_mod_folder, result_folder, options): try: folderMake(f'{result_folder}/{modFoldername}{baseLevels}') fileCopy(asset, - f'{result_folder}/{modFoldername}{baseLevels}{os.path.basename(asset)}') + f'{result_folder}/{modFoldername}{baseLevels}{Path(asset).name}') except Exception as e: logging.error(f'Could not copy asset {asset}: {e}') @@ -369,7 +369,7 @@ def convert(psych_mod_folder, result_folder, options): try: folderMake(f'{result_folder}/{modFoldername}{baseLevels}') fileCopy(asset, - f'{result_folder}/{modFoldername}{baseLevels}{os.path.basename(asset)}') + f'{result_folder}/{modFoldername}{baseLevels}{Path(asset).name}') except Exception as e: logging.error(f'Could not copy asset {asset}: {e}') @@ -385,13 +385,13 @@ def convert(psych_mod_folder, result_folder, options): logging.info(f'Converting {asset}') folderMake(f'{result_folder}/{modFoldername}{baseStages}') stageJSON = json.loads(open(asset, 'r').read()) - assetPath = f'{result_folder}/{modFoldername}{baseStages}{os.path.basename(asset)}' + assetPath = f'{result_folder}/{modFoldername}{baseStages}{Path(asset).name}' stageLua = asset.replace('.json', '.lua') logging.info(f'Parsing .lua with matching .json name: {stageLua}') luaProps = [] - if os.path.exists(stageLua): + if Path(stageLua).exists(): logging.info(f'Parsing {stageLua} and attempting to extract methods and calls') try: luaProps = StageLuaParse.parseStage(stageLua) @@ -399,7 +399,7 @@ def convert(psych_mod_folder, result_folder, options): logging.error(f'Could not complete parsing of {stageLua}: {e}') logging.info(f'Converting Stage JSON') - stageJSONConverted = json.dumps(StageTool.convert(stageJSON, os.path.basename(asset), luaProps), indent=4) + stageJSONConverted = json.dumps(StageTool.convert(stageJSON, Path(asset).name, luaProps), indent=4) open(assetPath, 'w').write(stageJSONConverted) if options.get('images'): # Images include XMLs @@ -413,9 +413,9 @@ def convert(psych_mod_folder, result_folder, options): for asset in allimagesandfolders: logging.info(f'Checking on {asset}') - if os.path.isdir(asset): + if Path(asset).is_dir(): logging.info(f'{asset} is directory, checking if it should be excluded...') - folderName = os.path.basename(asset) + folderName = Path(asset).name if not folderName in Constants.EXCLUDE_FOLDERS_IMAGES['PsychEngine']: logging.info(f'{asset} is not excluded... attempting to copy.') try: @@ -433,7 +433,7 @@ def convert(psych_mod_folder, result_folder, options): logging.info(f'{asset} is file, copying') try: folderMake(f'{result_folder}/{modFoldername}{baseImages}') - fileCopy(asset, f'{result_folder}/{modFoldername}{baseImages}{os.path.basename(asset)}') + fileCopy(asset, f'{result_folder}/{modFoldername}{baseImages}{Path(asset).name}') except Exception as e: logging.error(f'Failed to copy {asset}: {e}') diff --git a/psychtobase/src/Paths.py b/psychtobase/src/Paths.py index b397ca3..1d86628 100644 --- a/psychtobase/src/Paths.py +++ b/psychtobase/src/Paths.py @@ -1,5 +1,5 @@ import json -import os +from pathlib import Path class Paths: assetsDir = '' @@ -17,8 +17,8 @@ def getLibraryPath(file:str, library:str = 'preload'): return Paths.getPreloadPath(file) return Paths.getLibraryPathForce(file, library) - getLibraryPathForce = staticmethod(lambda file, library : os.path.join(Paths.assetsDir, library, file)) - getPreloadPath = staticmethod(lambda file : os.path.join(Paths.assetsDir, file)) + getLibraryPathForce = staticmethod(lambda file, library: Paths.assetsDir / library / file) + getPreloadPath = staticmethod(lambda file: Paths.assetsDir / Path(file)) txt = staticmethod(lambda key, library=None: Paths.getPath(f'{key}.txt', library)) xml = staticmethod(lambda key, library=None: Paths.getPath(f'{key}.xml', library)) @@ -50,4 +50,4 @@ def openTextFile(file: str): @staticmethod def join(*path) -> str: - return os.path.join(*path) \ No newline at end of file + return str(Path(path[0]).joinpath(*path[1:])) \ No newline at end of file diff --git a/psychtobase/src/files.py b/psychtobase/src/files.py index dfe1787..4d1ce7d 100644 --- a/psychtobase/src/files.py +++ b/psychtobase/src/files.py @@ -1,6 +1,6 @@ from glob import glob import logging -from os import path, makedirs +from pathlib import Path def removeTrail(filename): return filename.replace('.json', '') @@ -12,6 +12,7 @@ def findAll(folder): logging.info(f'Finding all files or directories with glob: {folder}') return glob(folder) -def folderMake(folder_path): #Sorry Tom but I'm dumb and not patient! - if not path.exists(folder_path): - makedirs(folder_path) \ No newline at end of file +def folderMake(folder_path): + folder = Path(folder_path) + if not folder.exists(): + folder.mkdir(parents=True) \ No newline at end of file diff --git a/psychtobase/src/log.py b/psychtobase/src/log.py index df08693..42c36d7 100644 --- a/psychtobase/src/log.py +++ b/psychtobase/src/log.py @@ -2,9 +2,10 @@ import logging import psychtobase.src.window as window +import sys from time import strftime -from os import mkdir +from pathlib import Path class CustomHandler(logging.StreamHandler): def emit(self, record): @@ -28,7 +29,7 @@ def setup() -> logging.RootLogger: # log format log_format = logging.Formatter("%(asctime)s: [%(filename)s:%(lineno)d] [%(levelname)s] %(message)s", "%H:%M:%S") - try: mkdir("logs") + try: Path("logs").mkdir(exist_ok=True) except: pass # file handler @@ -49,4 +50,13 @@ def setup() -> logging.RootLogger: logger.addHandler(console_handler) logger.info("Logger initialized!") - return logger \ No newline at end of file + return logger + +def log_exception(exc_type, exc_value, exc_traceback): + """Log uncaught exceptions.""" + logger = logging.getLogger() + if not issubclass(exc_type, KeyboardInterrupt): + logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)) + +# Configure global exception handler to use the logger +sys.excepthook = log_exception \ No newline at end of file diff --git a/psychtobase/src/tools/CharacterTools.py b/psychtobase/src/tools/CharacterTools.py index a731327..fb0f82e 100644 --- a/psychtobase/src/tools/CharacterTools.py +++ b/psychtobase/src/tools/CharacterTools.py @@ -1,13 +1,13 @@ import json -import os import logging import copy +from pathlib import Path from psychtobase.src import window, Constants from psychtobase.src import files class CharacterObject: def __init__(self, path: str, resultPath) -> None: - self.charName:str = os.path.basename(path) + self.charName:str = Path(path).name self.resultPath = resultPath self.pathName:str = path self.psychChar = {} @@ -51,7 +51,7 @@ def convert(self): logging.info(f'Character {englishCharacterName} successfully converted') def save(self): - savePath = os.path.join(self.resultPath, self.characterJson) + savePath = Path(self.resultPath) / self.characterJson logging.info(f'Character {self.characterName} saved to {savePath}.json') diff --git a/psychtobase/src/tools/ChartTools.py b/psychtobase/src/tools/ChartTools.py index 57280c1..381d71a 100644 --- a/psychtobase/src/tools/ChartTools.py +++ b/psychtobase/src/tools/ChartTools.py @@ -1,5 +1,6 @@ from psychtobase.src import Utils, Constants, files, window -import os, logging +import logging +from pathlib import Path from psychtobase.src.Paths import Paths class ChartObject: @@ -10,10 +11,10 @@ class ChartObject: path (str): The path where the song's chart data is stored. """ def __init__(self, path: str, output:str) -> None: - self.songPath = path - self.songNameRaw:str = os.path.basename(path) + self.songPath = Path(path) + self.songNameRaw:str = self.songPath.name self.songNamePath = self.songNameRaw.replace(' ', '-').lower() - self.outputpath = output + self.outputpath = Path(output) self.startingBpm = 0 @@ -41,19 +42,19 @@ def initCharts(self): difficulties = self.difficulties unorderedDiffs = set() - dirFiles = os.listdir(self.songPath) + dirFiles = list(self.songPath.iterdir()) chartFiles = [] for _f in dirFiles: - if not _f.endswith(".json"): + if not _f.suffix == ".json": continue - if _f.endswith("events.json"): + if _f.name.endswith("events.json"): logging.warn(f'[{self.songNameRaw}] events.json not supported yet! Sorry!') continue chartFiles.append(_f) for file in chartFiles: - fileName = file[:-5] + fileName = file.stem nameSplit = fileName.split("-") nameLength = len(nameSplit) @@ -64,7 +65,7 @@ def initCharts(self): elif nameLength > 1 and fileName != self.songNameRaw: difficulty = nameSplit[1] - filePath = Paths.join(self.songPath, fileName) + filePath = self.songPath / fileName fileJson = Paths.parseJson(filePath).get("song") if fileJson != None: @@ -173,7 +174,7 @@ def convert(self): logging.info(f"Chart conversion for {self.metadata.get('songName')} was completed!") def save(self): - folder = os.path.join(Constants.FILE_LOCS.get('CHARTFOLDER')[1], self.songNamePath) + folder = Path(Constants.FILE_LOCS.get('CHARTFOLDER')[1]) / self.songNamePath saveDir = f'{self.outputpath}{folder}' files.folderMake(saveDir) diff --git a/psychtobase/src/window.py b/psychtobase/src/window.py index cc8f79f..2b9c3cd 100644 --- a/psychtobase/src/window.py +++ b/psychtobase/src/window.py @@ -1,6 +1,5 @@ from base64 import b64decode -import os -import os.path as ospath +from pathlib import Path import platform import subprocess import time @@ -104,7 +103,7 @@ def __init__(self): # thingDefaultPath modDP = '' bGDP = '' - if ospath.exists(_defaultsFile): + if Path(_defaultsFile).exists(): try: parse = open(_defaultsFile, 'r').read() for index, line in enumerate(parse.split('\n')): @@ -385,7 +384,7 @@ def convertCallback(self, what): # the code below should go on the callback when the person presses the convert button psych_mod_folder_path = self.modLineEdit.text() result_path = self.baseGameLineEdit.text() - if os.path.exists(result_path): + if Path(result_path).exists(): logging.warn(f'Folder {result_path} already existed before porting, files may have been overwritten.') #i was trying to get this to be a window but it wasnt working options = Constants.DEFAULT_OPTIONS @@ -427,9 +426,9 @@ def goToGB(self): def openLogFile(self): file = log.logRetain.log - realLogPath = os.path.abspath(file) + realLogPath = Path(file).resolve() logging.info(f'Attempting to open file: {realLogPath}') - os.startfile(realLogPath, "open") + subprocess.Popen(['open', str(realLogPath)]) def open_dialog(self, title, inputs, button, body): self.dialog = SimpleDialog(title, inputs, button, body) self.dialog.show() diff --git a/setup.py b/setup.py index 7c71244..214b8a8 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,10 @@ -import os import logging +from pathlib import Path from psychtobase import main from psychtobase.src import log, window def makePath(path, path2): - return str(os.path.join(f"{path}/", f"{path2}")) + return str(Path(f"{path}/") / f"{path2}") def askMode(): # lets rethink this for a moment