"""Catalog: classification rules, naming, character labels, search; the shipped CSVs.""" import csv import os import pytest from ackit import banks, classify from ackit.catalog import Catalog, read_csv, load_characters from ackit.game import Game from ackit.paths import catalog_dir @pytest.mark.parametrize("name,group,category", [ ("Speech_TAPE_JOKER_1_4", "Speech", "Interview tapes"), ("Speech_CINEMATIC_CH1_CATWOMAN_SAVED_9", "Speech", "Cinematics"), ("Speech_PRED_THUG1_FULL_Nervous_112", "Speech", "Predator chatter"), ("CHR_RadioScanner_12", "Speech", "Radio & TV"), ("AMB_Steel_B3", "Ambience", "Ambience"), ("Music_393BC53A", "Music", "Music"), ("6C151539_1", "Unknown", "Unknown"), ]) def test_classify(name, group, category): assert classify.classify(name) == (group, category) def test_pack_media_are_music(): assert classify.classify("003CB311", pack="Music") == ("Music", "Music") assert classify.classify("X", pack="Music_PDLC")[1] == "Music (DLC)" def test_infer_characters(): assert classify.infer_characters("Speech_TAPE_MADHATTER_5_10") == ["Hugo Strange", "Mad Hatter"] assert "TYGER guard" in classify.infer_characters("Speech_PRED_TYGER2_Generic_7") assert classify.infer_characters("Speech_COMBAT_BARKS_BANE_Hit_batman_3")[:2] == ["Bane", "Batman"] def test_synthetic_catalog_names_and_search(synth_game): root, _ = synth_game cat = Catalog(Game(root), ["INT", "SFX"]) names = {s.name for s in cat.sounds} assert {"CHR_RadioScanner_1", "Speech_Synth_Radio_02", "CHR_RadioScanner_3", "Music_393BC53A"} <= names s = cat.resolve("CHR_RadioScanner#2") assert s.target.endswith(":1") and cat.resolve("speech_synth_radio_02.wav") is s assert s.line and s.name == "Speech_Synth_Radio_02" # the game's line: named after its event assert cat.resolve(s.key) is s assert [x.name for x in cat.search("radio scanner 3")] == ["CHR_RadioScanner_3"] with pytest.raises(LookupError) as e: cat.resolve("CHR_RadioScaner_2") assert "did you mean" in str(e.value) def test_characters_csv_covers_user_lists(): chars = load_characters(lang="ITA") assert len(chars) > 3000 and len(load_characters()) > 2500 # most labels apply in every language # the lists name ITA sounds by position (wemscan): NAME_i = variation i of the ITA WAD entry (alias NAME#i) src = os.path.join(catalog_dir(), "source", "ita", "Joker.txt") first = open(src).readline().strip()[:-4] base, i = first.rsplit("_", 1) ita = read_csv(os.path.join(catalog_dir(), "ITA.csv")) s = next(s for s in ita if first.lower() in {n.lower() for n in s.names()} or "%s#%s" % (base, i) in s.aliases) assert "Joker" in s.character # every labelled ITA sound keeps its label (takes of one line, NAME#k, share it), and most labels reach INT assert sum(1 for x in ita if x.character) >= 3366 assert sum(1 for x in read_csv(os.path.join(catalog_dir(), "INT.csv")) if x.character) > 2900 def test_line_names_agree_across_languages_and_subtitles(): """Speech sounds carry the game's line name: the same names in every language, and a linked subtitle's sound has the subtitle's line name (positions inside WAD entries differ per language: findings 20).""" from ackit import subtitles ita = {s.name for s in read_csv(os.path.join(catalog_dir(), "ITA.csv")) if s.group == "Speech"} intl = {s.name for s in read_csv(os.path.join(catalog_dir(), "INT.csv")) if s.group == "Speech"} assert len(ita & intl) > 0.97 * len(ita) snd = {s.key: s for s in read_csv(os.path.join(catalog_dir(), "ITA.csv"))} rows = [r for r in subtitles.read_texts(os.path.join(catalog_dir(), "SUB_ITA.csv"), "ITA") if r.sound in snd] same = sum(1 for r in rows if snd[r.sound].name.split("#")[0] == r.speech) assert len(rows) > 8000 and same > 0.99 * len(rows) hdr = next(csv.reader(open(os.path.join(catalog_dir(), "characters.csv"), encoding="utf-8"))) assert hdr == ["name", "character", "language"] def test_shipped_csvs_are_consistent(): from ackit import subtitles for f in os.listdir(catalog_dir()): if f.startswith("SUB_") and f.endswith(".csv"): lines = subtitles.read_texts(os.path.join(catalog_dir(), f), f[4:7]) assert len({(s.speech, s.line) for s in lines}) == len(lines), f assert all(len(s.text) <= s.room for s in lines), f continue if f.endswith(".csv") and f != "characters.csv": rows = read_csv(os.path.join(catalog_dir(), f)) keys = {r.key for r in rows} assert len(keys) == len(rows), f assert all(r.group in classify.GROUPS for r in rows) assert len({(r.language, r.name.lower()) for r in rows}) == len(rows), f # per language # no checksum only where the copies of a BANK sound differ (1C898731) assert sum(len(r.sha1) != 16 for r in rows) <= (1 if f == "BANK.csv" else 0), f hdr = next(csv.reader(open(os.path.join(catalog_dir(), "ITA.csv"), encoding="utf-8"))) assert hdr[:3] == ["archive", "target", "name"] locs = banks.read_locations(os.path.join(catalog_dir(), banks.LOCATIONS)) bank = {r.target for r in read_csv(os.path.join(catalog_dir(), "BANK.csv"))} assert {"%08X" % m for m, ls in locs.items() if ls[0].kind == "m"} == bank assert all(l.sound for ls in locs.values() for l in ls if l.kind == "p") @pytest.mark.game def test_real_game_uses_shipped_catalog(real_game_dir): g = Game(real_game_dir) cat = Catalog(g, ["ITA", "SFX", "BANK"]) bank = read_csv(os.path.join(catalog_dir(), "BANK.csv")) assert len(cat.sounds) == 10816 + 651 + sum(1 for s in bank if s.in_language("ITA")) assert all(s.in_language("ITA") for s in cat.sounds if s.archive == "BANK") assert all(s.line for s in cat.sounds if s.archive == "ITA" and s.group == "Speech") # every voice line s = cat.resolve("Speech_TAPE_JOKER_1#4") # position 4 in the ITA entry (the user's list name) assert s.category == "Interview tapes" and s.size == 44985 and s.name.startswith("Speech_TAPE_JOKER_1_") assert len(cat.heads) == 396 and sum(len(v) for v in cat.heads.values()) == 2101 b = cat.resolve("Play_BatmanDeviceImpact_Wood_1") assert b.archive == "BANK" and b.group == "SFX"