"""Unheard City - entry point. UnheardCity [--game DIR] start the editor UnheardCity --smoke-test [--game DIR] headless self-test of the bundled pipeline (no window, never writes the game: injects into a temporary copy of a WAD) UnheardCity --quit-after 5 --screenshot shot.png (start, wait, screenshot, quit; for build checks) """ import argparse import os import sys def smoke_test(game_dir=None, report=None): """Prove the packaged app can decode, encode and inject without any external program.""" lines = [] def print(*a): # noqa: A001 - also captured into the report (windowed builds have no console) lines.append(" ".join(str(x) for x in a)) if sys.stdout: sys.stdout.write(lines[-1] + "\n") if report: with open(report, "w", encoding="utf-8") as f: f.write("\n".join(lines) + "\n") import shutil import tempfile import numpy as np from ackit import audio, settings from ackit.game import Game, default_game_dir from ackit.inject import inject, read_target from ackit.workspace import Workspace gd = game_dir or default_game_dir(settings.load()) print("ffmpeg on PATH:", shutil.which("ffmpeg"), "| vgmstream on PATH:", shutil.which("vgmstream-cli")) if not gd: print("no game found; testing the codec only") from ackit.encoder import encode t = np.arange(48000) / 48000.0 x = (0.3 * np.sin(2 * np.pi * 440 * t)).astype(np.float32)[:, None] wem, info = encode(x, 48000, 24000, 1) y, sr = audio.decode_wem(wem) print("codec OK: %d bytes, decoded %.2f s at %d Hz" % (len(wem), len(y) / sr, sr)) return 0 with tempfile.TemporaryDirectory() as td: ws = Workspace(Game(gd), store_root=os.path.join(td, "store")) s = next(x for x in ws.catalog.sounds if x.group == "Speech" and 1.0 < x.seconds < 4.0) x, sr = audio.decode_wem(ws.original_bytes(s)) from ackit import fx # effects (pedalboard) on the way, as the Edit tab does st = fx.defaults() for k in ("pitch", "eq", "reverb"): st[k]["on"] = True st["pitch"]["shift"] = -3.0 y = fx.render(x[::-1].copy(), sr, st, s.channels) print("effects OK: %s (pedalboard %s), %.2f s -> %.2f s" % (fx.summary(st), __import__("pedalboard").__version__, len(x) / sr, len(y) / sr)) wem, info = ws.prepare(s, y, sr) out = os.path.join(td, os.path.basename(ws.wad_path(s))) inject(ws.wad_path(s), {s.target: wem}, {s.target: ws.limit(s)}, out_path=out) assert read_target(out, s.target) == wem print("pipeline OK: %s (%s), %d sounds loaded, re-encoded %d bytes (limit %d), injected into a temp copy" % (s.name, s.archive, len(ws.catalog.sounds), len(wem), ws.limit(s))) lang = ws.game.language() b = next((x for x in ws.catalog.sounds if x.archive == "BANK"), None) if b: audio.decode_wem(ws.current_bytes(b)) subs = ws.texts.subtitles(lang) if lang in ws.texts.subtitle_languages() else [] print("texts OK: %d subtitles, %d menu texts (%s); in-memory sound decoded: %s" % (len(subs), len(ws.texts.menu(lang)), lang, b.name if b else "none")) return 0 def main(argv=None): ap = argparse.ArgumentParser(prog="UnheardCity") ap.add_argument("--game", help="Arkham City folder") ap.add_argument("--smoke-test", action="store_true") ap.add_argument("--quit-after", type=float) ap.add_argument("--screenshot") ap.add_argument("--report", help="write the smoke-test output to this file") a, rest = ap.parse_known_args(argv) if a.smoke_test: return smoke_test(a.game, a.report) from PySide6.QtCore import QTimer from PySide6.QtWidgets import QApplication from ackit import settings as settings_mod from . import icons, theme from .mainwindow import MainWindow app = QApplication([sys.argv[0]] + rest) app.setApplicationName("Unheard City") app.setOrganizationName("UnheardCity") app.setWindowIcon(icons.app_icon()) st = settings_mod.load() theme.apply(app) w = MainWindow(settings=st, game_dir=a.game) w.show() if a.quit_after: def finish(): if a.screenshot: w.grab().save(a.screenshot) app.quit() QTimer.singleShot(int(a.quit_after * 1000), finish) return app.exec() if __name__ == "__main__": sys.exit(main())