107 lines
4.5 KiB
Python
107 lines
4.5 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))
|
|
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())
|