Full correctness check against the docs: safer writes, reverts and texts
Safety - Writes are serialized (Workspace.lock): two background jobs rebuilding one package (a sound and its subtitle applied side by side) lost an edit. - A store copies a bank file or package as "original" only if it matches the shipped catalog (Workspace.unoriginal_files: in-memory sounds, bank copies of voice lines, subtitles); the editor refuses to switch stores while changes are applied. A second store could otherwise resurrect a reverted sound. - Mod import in the editor asks before overwriting files changed outside the tool (it forced silently). - Revert all in the editor reverts texts too; the uninstaller counts text edits before offering to delete the store. - ackit restore-backup: the full-backup restore decisions.md described; it keeps the sounds of WADs without a full copy recorded. - build_catalog also stops when a subtitle differs from the catalog. Languages and texts - Game.language() returns languages without voices (JPN, RUS, ...) and Game.voice_archive() their INT voices; ackit text --lang JPN works. - Menu lookups parse one cached file (find & replace: minutes -> ms). - Subtitles decode cp1252 as Windows does: the UTF-8-in-cp1252 line Speech_TAPE_J_Answer_1_00 (KOR/POL/POR/RUS) is written back byte-for-byte; SUB_KOR/SUB_RUS rebuilt from the original game (only those rows changed). - Mods exported while another language is loaded keep name and line. Other fixes - Down-mix folds extra channels in at -3 dB (a 5.1 centre voice was dropped). - e2e_editor no longer overwrites the user's settings.json; kill_game waits for the game to exit before reverts. - Recorder handles Int32/UInt8 input and split frames; package block cache locked; conflict messages, CLI help examples, stale docstrings and doc references fixed; tests no longer read this PC's game language. Docs (decisions, user guide, README, subtitles.md, banks.md, catalog README, REPORT, NOTES, CLAUDE.md) updated; check_docs: 102 claims match. 148 tests; e2e_features 9/9, e2e_editor and verify PASS; game original afterwards.
This commit is contained in:
49
ackit/cli.py
49
ackit/cli.py
@@ -1,15 +1,17 @@
|
||||
"""ackit - Batman: Arkham City audio toolkit (command line).
|
||||
|
||||
Everyday use, by sound name (see `ackit find`):
|
||||
ackit find joker tape search names, categories, characters
|
||||
ackit info Speech_TAPE_JOKER_1_4 details, size limit, modified state
|
||||
ackit extract Speech_TAPE_JOKER_1_4 orig.wav save the original (or --current)
|
||||
ackit replace Speech_TAPE_JOKER_1_4 line.wav encode + install a replacement
|
||||
ackit revert Speech_TAPE_JOKER_1_4 put the original back (--all for everything)
|
||||
ackit status what you have changed
|
||||
ackit find joker tape search names, categories, characters
|
||||
ackit info Speech_TAPE_JOKER_1_04 details, size limit, modified state
|
||||
ackit extract Speech_TAPE_JOKER_1_04 orig.wav save the original (or --current)
|
||||
ackit replace Speech_TAPE_JOKER_1_04 line.wav encode + install a replacement
|
||||
ackit revert Speech_TAPE_JOKER_1_04 put the original back (--all: every sound and text)
|
||||
ackit status what you have changed
|
||||
ackit text find --lang JPN batman subtitles and menu text (any of the 11 languages)
|
||||
ackit export-mod my.acmod / import-mod my.acmod share your changes
|
||||
|
||||
The game folder is detected from Steam (Windows, Linux, Proton); override with --game DIR.
|
||||
The game folder is detected from Steam (Windows, Linux, Proton); override with --game DIR. --lang picks the
|
||||
language (default: the game's); languages without their own voices use the INT voice lines, like the game.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
@@ -36,7 +38,7 @@ def _workspace(a, archives=None):
|
||||
st = settings.load()
|
||||
game = _game(a)
|
||||
if archives is None and getattr(a, "lang", None):
|
||||
archives = [a.lang, "SFX", "BANK"]
|
||||
archives = [game.voice_archive(a.lang), "SFX", "BANK"]
|
||||
return Workspace(game, a.store or st.get("store_root"), archives,
|
||||
full_backup=not getattr(a, "no_full_backup", False) and st.get("full_backup", True),
|
||||
progress=lambda n, t: None)
|
||||
@@ -73,7 +75,8 @@ def cmd_list(a):
|
||||
c = a.character.lower()
|
||||
rows = [s for s in rows if c in s.character.lower() or (a.inferred and c in s.characters_inferred.lower())]
|
||||
if a.modified:
|
||||
rows = [s for s in rows if s.key in ws.modified_keys()]
|
||||
mod = set(ws.modified_keys())
|
||||
rows = [s for s in rows if s.key in mod]
|
||||
if a.csv:
|
||||
from .catalog import write_csv
|
||||
write_csv(a.csv, rows)
|
||||
@@ -178,6 +181,23 @@ def cmd_revert(a):
|
||||
print("reverted %d sound(s)" % len(done))
|
||||
|
||||
|
||||
def cmd_restore_backup(a):
|
||||
ws = _workspace(a)
|
||||
full = sorted(ws.store.data.get("full", {}))
|
||||
if not full:
|
||||
print("no full copies in this store (%s)" % ws.store.root)
|
||||
return
|
||||
if not a.yes:
|
||||
print("would copy back (or rebuild) %d file(s) from the full copies in %s:" % (len(full), ws.store.root))
|
||||
for rel in full:
|
||||
print(" " + rel)
|
||||
print("Sounds and texts in these files are forgotten; sounds of WADs without a full copy stay recorded.\n"
|
||||
"Use this only if the originals store is damaged (normally: ackit revert --all). Run again with --yes.")
|
||||
return
|
||||
done = ws.restore_from_full_backup(progress=print)
|
||||
print("restored %d file(s); %d sound(s) still recorded" % (len(done), len(ws.store.entries)))
|
||||
|
||||
|
||||
def cmd_status(a):
|
||||
ws = _workspace(a)
|
||||
keys = ws.modified_keys()
|
||||
@@ -286,7 +306,7 @@ def cmd_game(a):
|
||||
from .workspace import default_store_root
|
||||
print("game folder: ", g.root)
|
||||
print("archives: ", ", ".join("%s=%s" % (k, g.rel(v)) for k, v in g.archives.items()))
|
||||
print("speech language: ", g.language())
|
||||
print("language: ", "%s (voices: %s)" % (g.language(), g.voice_archive(g.language())))
|
||||
print("originals store: ", a.store or st.get("store_root") or default_store_root(g))
|
||||
print("settings file: ", settings.path())
|
||||
|
||||
@@ -311,7 +331,7 @@ def cmd_verify(a):
|
||||
ok = selfcheck.run(ws, log=print)
|
||||
sys.exit(0 if ok else 1)
|
||||
from .verify import ingame
|
||||
if a.lang and a.lang != ws.game.language():
|
||||
if a.lang and a.lang != ws.game.language(): # voices and texts follow the game's setting
|
||||
raise GameError("the game plays its own language (%s); set it in Steam (game properties > Language) to "
|
||||
"test %s in the game" % (ws.game.language(), a.lang))
|
||||
if a.banks:
|
||||
@@ -371,9 +391,12 @@ def build_parser():
|
||||
s.add_argument("name"); s.add_argument("audio")
|
||||
s = add("apply", cmd_apply, "replace every sound named by the files in a folder", (enc,))
|
||||
s.add_argument("folder")
|
||||
s = add("revert", cmd_revert, "put original sounds back", aliases=["restore"])
|
||||
s = add("revert", cmd_revert, "put original sounds back (--all: every sound and text)", aliases=["restore"])
|
||||
s.add_argument("names", nargs="*"); s.add_argument("--all", action="store_true")
|
||||
add("status", cmd_status, "list the sounds you modified")
|
||||
add("status", cmd_status, "list the sounds and texts you modified")
|
||||
s = add("restore-backup", cmd_restore_backup, "last resort if the originals store is damaged: put back the "
|
||||
"full file copies it keeps (lists them; --yes to do it)")
|
||||
s.add_argument("--yes", action="store_true")
|
||||
s = add("text", cmd_text, "subtitles and menu text: find WORDS | show KEY | set KEY TEXT | revert KEY/--all")
|
||||
s.add_argument("action", choices=["find", "show", "set", "revert"])
|
||||
s.add_argument("args", nargs="*")
|
||||
|
||||
Reference in New Issue
Block a user