diff --git a/gui/deploy.py b/gui/deploy.py index 4cad161..d244507 100644 --- a/gui/deploy.py +++ b/gui/deploy.py @@ -17,26 +17,6 @@ from distutils.dir_util import copy_tree from pathlib import Path from widgets import create_logger -def find_install_dir(): - library_folders = Path("C:\Program Files (x86)\Steam\steamapps\libraryfolders.vdf") - game_id = "602960" - data = vdf.load(open(library_folders)) - install_path = "" - for (id, folder) in data["libraryfolders"].items(): - if game_id in folder["apps"]: - install_path = Path(folder["path"]) / Path("steamapps\common\Barotrauma") - - return install_path.as_posix() - - -def download_via_requests(url_source, file_name): - - response = requests.get(url_source, stream=True, verify=certifi.where()) - - with open(file_name, 'wb') as out_file: - shutil.copyfileobj(response.raw, out_file) - del response - def rmfulldir(dirpath): try: @@ -45,48 +25,12 @@ def rmfulldir(dirpath): pass -def update(utils_dir: str): - logging.info(f"checking for updates via git pull.") - pull = [f"{utils_dir}/git/bin/git.exe", "pull"] - subprocess.call(pull) +def download_via_requests(url_source, file_name): + response = requests.get(url_source, stream=True, verify=certifi.where()) - -def download_and_extract(url_source, out_archive, utils_dir="./utils", subdir=""): - logger = create_logger() - logger.info(f"Downloading {url_source}, this may take a while.") - - download_via_requests(url_source, out_archive) - time.sleep(0.7) - logger.info("Download complete.") - - logger.info(f"Extracting {out_archive}") - extract = [f"{utils_dir}/7z/7za.exe", "x", out_archive, "-o" f"{utils_dir}{subdir}"] - subprocess.call(extract) - - time.sleep(0.7) - logger.info("Removing " + out_archive) - os.remove(out_archive) - - -def download_ffmpeg(utils_dir: str, clean=False): - - if clean: - rmfulldir(f"{utils_dir}/ffmpeg-" + get_ffmpeg_version() + "-full_build") - - url_ffmpeg_source = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z" - out_ffmpeg_archive = f"{utils_dir}/ffmpeg-release-full.7z.exe" - - download_and_extract(url_ffmpeg_source, out_ffmpeg_archive, utils_dir) - -def download_git(utils_dir: str, clean=False): - if clean: - rmfulldir(f"{utils_dir}/git") - - url_git_source = ("https://github.com/git-for-windows/git/releases/download/" - "v2.46.2.windows.1/PortableGit-2.46.2-64-bit.7z.exe") - out_git_archive = f"{utils_dir}/PortableGit-2.46.2-64-bit.7z.exe" - - download_and_extract(url_git_source, out_git_archive, utils_dir, subdir="/git") + with open(file_name, 'wb') as out_file: + shutil.copyfileobj(response.raw, out_file) + del response def get_ffmpeg_version(): @@ -99,194 +43,257 @@ def get_ffmpeg_version(): return ffmpeg_version -def fetch_and_cut_song(utils_dir: str, build_dir: str, tape, ffmpeg_version): - python_executable = f"{utils_dir}/python-3.9.7-embed-amd64/python.exe" - script = "./fetch_song.py" +class Deployer: + def __init__(self): + logging.basicConfig(stream=sys.stdout, level=logging.INFO) + self.logger = logging.getLogger() - if type(tape["source"]) == str: - shutil.copy(tape["source"], f"{build_dir}/music/{tape['identifier']}.ogg") - else: - fetch = [python_executable, script, tape["source"]["url"], "-x", "--audio-format", "vorbis", - "--audio-quality", "0", "-o", f"{build_dir}/tmp_music/{tape['identifier']}.ogg"] + self.utils_dir = "./utils" + self.build_dir = "./build" + self.music_dir = "./build/music" + self.source_dir = "./source" + self.install_dir = self.find_install_dir() - # this is done in a separate python script because subprocess.call makes sure that the - # download process is finished before trying to cut the song + def find_install_dir(self): + self.logger.info("Trying to find Barotraumma install folder") + library_folders = Path("C:\Program Files (x86)\Steam\steamapps\libraryfolders.vdf") + game_id = "602960" + data = vdf.load(open(library_folders)) + install_path = "" + for (id, folder) in data["libraryfolders"].items(): + if game_id in folder["apps"]: + install_path = Path(folder["path"]) / Path("steamapps\common\Barotrauma") - subprocess.call(fetch) + self.logger.info(f"Barotrauma is installed in {install_path.as_posix()}") - time.sleep(0.1) + return install_path.as_posix() - cut = [f"{utils_dir}/ffmpeg-" + ffmpeg_version + "-full_build/bin/ffmpeg.exe", - "-y", "-ss", f"{tape['source']['start']}", - "-i", f"{build_dir}/tmp_music/{tape['identifier']}.ogg", "-acodec", "libvorbis", - "-ac", "1", "-af", f"volume={tape['source']['volume']}dB", - f"{build_dir}/music/{tape['identifier']}.ogg"] + def download_ffmpeg(self, clean=False): + if clean: + rmfulldir(f"{self.utils_dir}/ffmpeg-" + get_ffmpeg_version() + "-full_build") - if tape["source"]["end"] != -1: - cut = cut[:-7] + ["-to", f"{tape['source']['end']}"] + cut[-7:] + url_ffmpeg_source = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z" + out_ffmpeg_archive = f"{self.utils_dir}/ffmpeg-release-full.7z.exe" + + self.download_and_extract(url_ffmpeg_source, out_ffmpeg_archive) + + def download_git(self, clean=False): + if clean: + rmfulldir(f"{self.utils_dir}/git") + + url_git_source = ("https://github.com/git-for-windows/git/releases/download/" + "v2.46.2.windows.1/PortableGit-2.46.2-64-bit.7z.exe") + out_git_archive = f"{self.utils_dir}/PortableGit-2.46.2-64-bit.7z.exe" + + self.download_and_extract(url_git_source, out_git_archive, subdir="/git") + + def download_and_extract(self, url_source, out_archive, subdir=""): + self.logger.info(f"Downloading {url_source}, this may take a while.") + + download_via_requests(url_source, out_archive) + time.sleep(0.7) + self.logger.info("Download complete.") + + self.logger.info(f"Extracting {out_archive}") + extract = [f"{self.utils_dir}/7z/7za.exe", "x", out_archive, "-o" f"{self.utils_dir}{subdir}"] + subprocess.call(extract) + + time.sleep(0.7) + self.logger.info("Removing " + out_archive) + os.remove(out_archive) + + def update(self): + self.logger.info(f"checking for updates via git pull.") + pull = [f"{self.utils_dir}/git/bin/git.exe", "pull"] + subprocess.call(pull) + + def fetch_and_cut_song(self, tape, ffmpeg_version): + python_executable = f"{self.utils_dir}/python-3.9.7-embed-amd64/python.exe" + script = "./fetch_song.py" + + if type(tape["source"]) == str: + shutil.copy(tape["source"], f"{self.music_dir}/{tape['identifier']}.ogg") + else: + fetch = [python_executable, script, tape["source"]["url"], "-x", "--audio-format", "vorbis", + "--audio-quality", "0", "-o", f"{self.build_dir}/tmp_music/{tape['identifier']}.ogg"] + + # this is done in a separate python script because subprocess.call makes sure that the + # download process is finished before trying to cut the song + + subprocess.call(fetch) + + time.sleep(0.1) + + cut = [f"{self.utils_dir}/ffmpeg-" + ffmpeg_version + "-full_build/bin/ffmpeg.exe", + "-y", "-ss", f"{tape['source']['start']}", + "-i", f"{self.build_dir}/tmp_music/{tape['identifier']}.ogg", "-acodec", "libvorbis", + "-ac", "1", "-af", f"volume={tape['source']['volume']}dB", + f"{self.music_dir}/{tape['identifier']}.ogg"] + + if tape["source"]["end"] != -1: + cut = cut[:-7] + ["-to", f"{tape['source']['end']}"] + cut[-7:] + + try: + subprocess.call(cut) + except FileNotFoundError: + print("ffmpeg not in utils directory. Run python install_dependencies.py " + "or download the latest release version manually.") + sys.exit() + + walkman = [f"{self.utils_dir}/ffmpeg-" + ffmpeg_version + "-full_build/bin/ffmpeg.exe", + "-i", f"{self.music_dir}/{tape['identifier']}.ogg", + "-af", f"highpass=f=300,lowpass=f=12000", + f"{self.music_dir}/{tape['identifier']}-walkman.ogg"] try: - subprocess.call(cut) + subprocess.call(walkman) except FileNotFoundError: - print("ffmpeg not in utils directory. Run python install_dependencies.py " - "or download the latest release version manually.") + self.logger.error("ffmpeg not in utils directory. Run python install_dependencies.py " + "or download the latest release version manually.") sys.exit() - walkman = [f"{utils_dir}/ffmpeg-" + ffmpeg_version + "-full_build/bin/ffmpeg.exe", - "-i", f"{build_dir}/music/{tape['identifier']}.ogg", - "-af", f"highpass=f=300,lowpass=f=12000", - f"{build_dir}/music/{tape['identifier']}-walkman.ogg"] - try: - subprocess.call(walkman) - except FileNotFoundError: - print("ffmpeg not in utils directory. Run python install_dependencies.py " - "or download the latest release version manually.") - sys.exit() + def assemble_png_images(self, tapes, outfile: str, resize=None): + img_names = [f"{self.source_dir}/images/{tape['identifier']}.png" for tape in tapes] + + images = [Image.open(x) for x in img_names] + + if resize is not None: + for i, (im, tape) in enumerate(zip(images, tapes)): + im.thumbnail((128, 82), Image.ANTIALIAS) + if resize == (64, 41) and tape["icon_resize"] == "blur": + im.thumbnail(resize, Image.ANTIALIAS) + else: + im.thumbnail(resize, Image.NEAREST) + images[i] = im + + widths, heights = zip(*(i.size for i in images)) + + columns = int((len(images)) ** 0.5) + rows = int(math.ceil(len(images) / columns)) + + total_width = max(widths) * columns + max_height = max(heights) * rows + + new_im = Image.new('RGBA', (total_width, max_height)) + + for i, im in enumerate(images): + x_offset = max(widths) * (i % columns) + y_offset = max(heights) * math.floor(i / columns) + new_im.paste(im, (x_offset, y_offset)) + + new_im.save(f"{self.build_dir}/{outfile}.png") -def assemble_png_images(tapes, outfile: str, resize=None): + def prepare_music(self, data): + try: + os.mkdir(f'{self.music_dir}') + except FileExistsError: + pass - img_names = [f"./source/images/{tape['identifier']}.png" for tape in tapes] + ffmpeg_version = get_ffmpeg_version() - images = [Image.open(x) for x in img_names] - - if resize is not None: - for i, (im, tape) in enumerate(zip(images, tapes)): - im.thumbnail((128, 82), Image.ANTIALIAS) - if resize == (64, 41) and tape["icon_resize"] == "blur": - im.thumbnail(resize, Image.ANTIALIAS) + self.logger.info("downloading and cutting the songs") + for i, tape in enumerate(data): + if not (os.path.exists(f"{self.music_dir}/{tape['identifier']}.ogg") or os.path.exists( + f"{self.music_dir}/{tape['identifier']}-walkman.ogg")): + self.logger.info(f"{i + 1}/{len(data)} Downloading: {tape['name']}") + self.fetch_and_cut_song(tape, ffmpeg_version) else: - im.thumbnail(resize, Image.NEAREST) - images[i] = im + self.logger.info(f"{i + 1}/{len(data)} Already exists: {tape['name']}") - widths, heights = zip(*(i.size for i in images)) + self.logger.info(f"removing temporary music folder") + rmfulldir(f"{self.build_dir}/tmp_music/") - columns = int((len(images))**0.5) - rows = int(math.ceil(len(images) / columns)) - - total_width = max(widths) * columns - max_height = max(heights) * rows - - new_im = Image.new('RGBA', (total_width, max_height)) - - for i, im in enumerate(images): - x_offset = max(widths) * (i % columns) - y_offset = max(heights) * math.floor(i / columns) - new_im.paste(im, (x_offset, y_offset)) - - new_im.save(f"./build/{outfile}.png") + self.logger.info(f"copying the sound effects to build") + copy_tree(f"{self.source_dir}/sound_effects", f"{self.build_dir}/sound_effects") -def prepare_music(data, music_dir: str, build_dir: str): - try: - os.mkdir(f'{music_dir}') - except FileExistsError: - pass + def prepare_images(self, data): + logging.info(f"assembling covers and icons into png files") + self.assemble_png_images(data, "covers") + self.assemble_png_images(data, "icons", resize=(64, 41)) + self.assemble_png_images(data, "sprites", resize=(33, 21)) - ffmpeg_version = get_ffmpeg_version() + logging.info(f"copying other images") + shutil.copy(f"{self.source_dir}/images/players_icons.png", f"{self.build_dir}/players_icons.png") + shutil.copy(f"{self.source_dir}/images/players_sprites.png", f"{self.build_dir}/players_sprites.png") + shutil.copy(f"{self.source_dir}/images/PreviewImage.png", f"{self.build_dir}/PreviewImage.png") - logging.info("downloading and cutting the songs") - for i, tape in enumerate(data): - if not (os.path.exists(f"./build/music/{tape['identifier']}.ogg") or os.path.exists(f"./build/music/{tape['identifier']}-walkman.ogg")): - logging.info(f"{i + 1}/{len(data)} Downloading: {tape['name']}") - fetch_and_cut_song(tape, ffmpeg_version) + + def build_xml_code(self, data, config): + self.logger.info(f"calculate the value that lets you use the songs n-times") + song_lengths = [OggVorbis(f"{self.music_dir}/{tape['identifier']}.ogg").info.length + for tape in data] + + use_lengths = [song_length * n for song_length, n in + zip(song_lengths, [tape["no_of_uses"] for tape in data])] + + condition_delta = [f"{1 / use_length:0.5f}" for use_length in use_lengths] + affliction_delta = [100 / song_length for song_length in song_lengths] + + columns = int((len(data)) ** 0.5) + positions = [{"column": i % columns, "row": math.floor(i / columns)} for i in range(len(data))] + + self.logger.info(f"creating jinja environment") + # create jinja2 environment + j2env = j2.Environment(loader=j2.FileSystemLoader(Path("."))) + j2env.globals.update(zip=zip) + + # load the template file + template0 = j2env.get_template(f"{self.source_dir}/filelist_template.xml") + template1 = j2env.get_template(f"{self.source_dir}/sunken_tapes_template.xml") + template2 = j2env.get_template(f"{self.source_dir}/sunken_tapes_style_template.xml") + + self.logger.info(f"rendering the xml files") + with open(f"{self.build_dir}/filelist.xml", "w+", encoding="utf8") as output_file: + # render the template + output_file.write(template0.render(config=config, tapes=data)) + + with open(f"{self.build_dir}/{config['slug']}.xml", "w+", encoding="utf8") as output_file: + # render the template + output_file.write(template1.render(tapes=data, config=config, + condition_delta=condition_delta, + affliction_delta=affliction_delta, + song_lengths=song_lengths, + positions=positions)) + + with open(f"{self.build_dir}/{config['slug']}_style.xml", "w+", encoding="utf8") as output_file: + # render the template + output_file.write(template2.render(tapes=data, config=config, positions=positions)) + + + def deploy(self, local_mod_name: str, config): + try: + os.mkdir(self.build_dir) + except FileExistsError: + self.logger.info(f"removing old XML files in ./build/:") + for f in Path(self.build_dir).glob("*.xml"): + self.logger.info(f" {f}") + os.remove(f) + pass + + self.logger.info("Reading tapes.yaml") + data_file = Path("./source/tapes.yaml") + + # load yaml file + with data_file.open(encoding='utf-8') as fr: + data = yaml.load(fr, Loader=yaml.SafeLoader) + + self.prepare_music(data) + self.prepare_images(data) + self.build_xml_code(data, config) + + mod_directory = f"{self.install_dir}/LocalMods/{local_mod_name}/" + + self.logger.info(f"removing the old installed mod directory {mod_directory}") + rmfulldir(mod_directory) + + self.logger.info(f"copying the new build") + if Path(f"{self.install_dir}").is_dir(): + copy_tree(self.build_dir, mod_directory) else: - logging.info(f"{i + 1}/{len(data)} Already exists: {tape['name']}") + raise FileNotFoundError( + f"{self.install_dir} does not exist. Set up the correct Barotrauma installation directory") - logging.info(f"removing temporary music folder") - rmfulldir(f"{build_dir}/tmp_music/") - - logging.info(f"copying the sound effects to build") - copy_tree("./source/sound_effects", f"{build_dir}/sound_effects") - - -def prepare_images(data, build_dir:str): - logging.info(f"assembling covers and icons into png files") - assemble_png_images(data, "covers") - assemble_png_images(data, "icons", resize=(64, 41)) - assemble_png_images(data, "sprites", resize=(33, 21)) - - logging.info(f"copying other images") - shutil.copy("./source/images/players_icons.png", f"{build_dir}/players_icons.png") - shutil.copy("./source/images/players_sprites.png", f"{build_dir}/players_sprites.png") - shutil.copy("./source/images/PreviewImage.png", f"{build_dir}/PreviewImage.png") - - -def build_xml_code(data, build_dir:str, config): - logging.info(f"calculate the value that lets you use the songs n-times") - song_lengths = [OggVorbis(f"./build/music/{tape['identifier']}.ogg").info.length - for tape in data] - - use_lengths = [song_length * n for song_length, n in - zip(song_lengths, [tape["no_of_uses"] for tape in data])] - - condition_delta = [f"{1 / use_length:0.5f}" for use_length in use_lengths] - affliction_delta = [100 / song_length for song_length in song_lengths] - - columns = int((len(data))**0.5) - positions = [{"column": i % columns, "row": math.floor(i / columns)} for i in range(len(data))] - - logging.info(f"creating jinja environment") - # create jinja2 environment - j2env = j2.Environment(loader=j2.FileSystemLoader(Path("."))) - j2env.globals.update(zip=zip) - - # load the template file - template0 = j2env.get_template("./source/filelist_template.xml") - template1 = j2env.get_template("./source/sunken_tapes_template.xml") - - template2 = j2env.get_template("./source/sunken_tapes_style_template.xml") - - logging.info(f"rendering the xml files") - with open(f"{build_dir}/filelist.xml", "w+", encoding="utf8") as output_file: - # render the template - output_file.write(template0.render(config=config, tapes=data)) - - with open(f"{build_dir}/{config['slug']}.xml", "w+", encoding="utf8") as output_file: - # render the template - output_file.write(template1.render(tapes=data, config=config, - condition_delta=condition_delta, - affliction_delta=affliction_delta, - song_lengths=song_lengths, - positions=positions)) - - with open(f"{build_dir}/{config['slug']}_style.xml", "w+", encoding="utf8") as output_file: - # render the template - output_file.write(template2.render(tapes=data, config=config, positions=positions)) - - -def deploy(build_dir: str, install_dir: str, local_mod_name: str, config): - try: - os.mkdir(build_dir) - except FileExistsError: - logging.info(f"removing old XML files in ./build/:") - for f in Path(build_dir).glob("*.xml"): - logging.info(f" {f}") - os.remove(f) - pass - - logging.info("Reading tapes.yaml") - data_file = Path("./source/tapes.yaml") - - # load yaml file - with data_file.open(encoding='utf-8') as fr: - data = yaml.load(fr, Loader=yaml.SafeLoader) - - prepare_music(data) - prepare_images(data, build_dir) - build_xml_code(data, build_dir, config) - - mod_directory = f"{install_dir}/LocalMods/{local_mod_name}/" - - logging.info(f"removing the old installed mod directory {mod_directory}") - rmfulldir(mod_directory) - - logging.info(f"copying the new build") - if Path(f"{install_dir}").is_dir(): - copy_tree(build_dir, mod_directory) - else: - raise FileNotFoundError( - f"{install_dir} does not exist. Set up the correct Barotrauma installation directory") - - logging.info(f"Done!") + self.logger.info(f"Done!") diff --git a/gui/installer.py b/gui/installer.py index 99b5d58..3f18d51 100644 --- a/gui/installer.py +++ b/gui/installer.py @@ -4,9 +4,10 @@ import getpass import logging import time +from gui.deploy import Deployer from tape_editor_widgets import * from widgets import LabelWebLink -from deploy import download_ffmpeg, get_ffmpeg_version, download_git, find_install_dir +from deploy import Deployer, get_ffmpeg_version from PIL import ImageGrab @@ -19,6 +20,7 @@ class OptionsWidget(QWidget): super().__init__() self.parent = parent + self.deployer = Deployer() options = [("Buffs", "Some songs affect the characters with strengthen\nand haste afflictions."), ("De-buffs", "Some songs affect the characters with psychosis \naffliction."), @@ -92,7 +94,7 @@ class OptionsWidget(QWidget): # Update widget self.update_widget = UpdateWidget(self) # Install widget - self.install_widget = InstallWidget(self) + self.install_widget = InstallWidget(self, self.deployer) # Console widget self.console_widget = ConsoleWidget(self) @@ -421,7 +423,7 @@ class UpdateWidget(QGroupBox): self.does_git_exist() def download_git_action(self): - download_git(self.git_dir.directory) + self.parent.deployer.download_git() def does_git_exist(self): executable_path = Path("git/bin/git.exe") @@ -447,10 +449,11 @@ class ConsoleWidget(QGroupBox): class InstallWidget(QGroupBox): - def __init__(self, parent: QWidget): + def __init__(self, parent: QWidget, deployer: Deployer): super().__init__("Install steps") self.parent = parent + self.deployer = deployer self.grid = QGridLayout(self) hr_html_string = "