Files
aetherbound-guild/tools/audio/normalize_sfx_names.py
T

87 lines
3.2 KiB
Python

#!/usr/bin/env python3
"""Delete failed (near-silent) generations and rename every SFX candidate to a
canonical `SFX-<family>-<number>-<event_name>.v<NN>.mp3` scheme taken from the
contract's SFX ledger, keeping processed files, sidecars and raw archives aligned.
"""
from __future__ import annotations
import json
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
CANDIDATES = ROOT / "docs/audio/sfx/candidates/sfx"
RAW = ROOT / "docs/audio/sfx/candidates/sfx/raw"
CONTRACT = ROOT / "docs/04_ART_ANIMATION_AUDIO.md"
FAILED = [
"SFX-EQP-015-cloth_wrap_drawn_back_from_an_artifact.v01.mp3",
"SFX-ORD-002-tile_moved_a_short_distance_across_felt.v01.mp3",
"SFX-ORD-004-tile_bumped_against_a_blocked_edge_and_r.v01.mp3",
"SFX-ORD-015-new_tile_added_to_the_board_with_a_firm_.v01.mp3",
"SFX-RES-010-rope_snapped_taut_and_the_channel_releas.v01.mp3",
"SFX-UI-003-muted_cloth-covered_tap_that_goes_nowher.v01.mp3",
"SFX-UI-019-brass_stamp_lifted_away_before_contact_a.v01.mp3",
"SFX-UI-023-cloth_wiped_across_a_wooden_desk.v01.mp3",
]
PATTERN = re.compile(r"^(SFX-(?:UI|SHP|EQP|ORD|RES|RUN)-\d{3})-.*?\.(v\d+)\.mp3$")
def load_event_names() -> dict[str, str]:
names: dict[str, str] = {}
for line in CONTRACT.read_text().splitlines():
match = re.match(r"^(SFX-(?:UI|SHP|EQP|ORD|RES|RUN)-\d{3})\s+(\S+)\s*$", line.strip())
if match:
names[match.group(1)] = match.group(2)
return names
def main() -> int:
names = load_event_names()
removed = 0
for name in FAILED:
for path in (CANDIDATES / name, CANDIDATES / f"{name}.meta.json", RAW / name):
if path.exists():
path.unlink()
if path.parent == CANDIDATES:
removed += 1
renamed = 0
for path in sorted(CANDIDATES.glob("*.mp3")):
match = PATTERN.match(path.name)
if not match:
continue
event_id, variant = match.group(1), match.group(2)
event_name = names.get(event_id)
if not event_name:
print(f"WARN no contract name for {event_id}")
continue
target = path.with_name(f"{event_id}-{event_name}.{variant}.mp3")
if target == path:
continue
if target.exists():
print(f"SKIP collision {target.name}")
continue
path.rename(target)
sidecar = path.with_name(path.name + ".meta.json")
if sidecar.exists():
sidecar.rename(target.with_name(target.name + ".meta.json"))
raw_source = RAW / path.name
if raw_source.exists():
raw_source.rename(RAW / target.name)
renamed += 1
for sidecar in sorted(CANDIDATES.glob("*.meta.json")):
text = sidecar.read_text()
try:
record = json.loads(text)
except json.JSONDecodeError:
continue
record["path"] = str(CANDIDATES / sidecar.name.removesuffix(".meta.json"))
sidecar.write_text(json.dumps(record, ensure_ascii=False, indent=1))
print(f"removed_failed={removed} renamed={renamed} total={len(list(CANDIDATES.glob('*.mp3')))}")
return 0
if __name__ == "__main__":
raise SystemExit(main())