Files
UnheardCity/app/main.py
2026-10-09 02:48:24 +02:00

92 lines
3.6 KiB
Python

"""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))
wem, info = ws.prepare(s, x[::-1].copy(), 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)))
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())