187 lines
9.2 KiB
Python
187 lines
9.2 KiB
Python
import json
|
|
import os
|
|
import re
|
|
import unicodedata
|
|
|
|
COVERART_DIR = "/Users/matt/Desktop/VideoGameList/Coverart"
|
|
GAMES_JSON = "/Users/matt/Desktop/VideoGameList/games.json"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Manual overrides: games.json title -> coverart stem (without -01.png)
|
|
# ---------------------------------------------------------------------------
|
|
MANUAL_OVERRIDES = {
|
|
"1080° Snowboarding": "1080 Snowboarding",
|
|
"Aero Fighters Assault": "AeroFighters Assault",
|
|
"All Star Tennis '99": "All Star Tennis 99",
|
|
"All-Star Baseball '99": "All-Star Baseball 99",
|
|
"Bass Hunter 64": "In-Fisherman Bass Hunter 64",
|
|
"Beetle Adventure Racing": "Beetle Adventure Racing!",
|
|
"Bomberman 64 1997": "Bomberman 64",
|
|
"Bust-A-Move 3 DX": "Bust-A-Move _99",
|
|
"ClayFighter 63\u2153": "Clay Fighter 63 1_3",
|
|
"ClayFighter: Sculptor's Cut": "Clay Fighter_ Sculptor_s Cut",
|
|
"Daffy Duck Starring as Duck Dodgers": "Looney Tunes_ Duck Dodgers_ Starring Daffy Duck",
|
|
"Daikatana": "John Romero_s Daikatana",
|
|
"Disney's Tarzan": "Tarzan",
|
|
"Donald Duck: Goin Quackers": "Donald Duck_ Goin_ Quackers",
|
|
"Duke Nukem Zero Hour": "Duke Nukem_ Zero Hour",
|
|
"Extreme-G 2": "Extreme-G_ XG2",
|
|
"FIFA '99": "FIFA 99",
|
|
"FIFA 64": "FIFA Soccer 64",
|
|
"FIFA: Road to World Cup '98": "FIFA_ Road to World Cup 98",
|
|
"G.A.S.P!! Fighters' NEXTream": "Deadly Arts",
|
|
"Holy Magic Century": "Quest 64",
|
|
"Lego Racers": "LEGO Racers",
|
|
"Madden NFL '99": "Madden NFL 99",
|
|
"Michael Owens WLS 2000": "Mia Hamm Soccer 64",
|
|
"Ms. Pac-Man: Maze Madness": "Ms. Pac-Man Maze Madness",
|
|
"Mystical Ninja Starring Goemon 2": "Goemon_s Great Adventure",
|
|
"NASCAR '99": "Nascar 99",
|
|
"NBA In The Zone '98": "NBA in the Zone _98",
|
|
"NBA In The Zone 2000": "NBA in the Zone 2000",
|
|
"NBA Jam '99": "NBA Jam 99",
|
|
"NBA Live '99": "NBA Live 99",
|
|
"NBA Pro '99": "NBA Hangtime",
|
|
"NFL Quarterback Club '98": "NFL Quarterback Club 98",
|
|
"NFL Quarterback Club '99": "NFL Quarterback Club 99",
|
|
"NFL Quarterback Club 2001": "NFL QB Club 2001",
|
|
"NHL '99": "NHL 99",
|
|
"NHL Breakaway '98": "NHL Breakaway 98",
|
|
"NHL Breakaway '99": "NHL Breakaway 99",
|
|
"NHL Pro '99": "NHL Blades of Steel _99",
|
|
"Olympic Hockey '98": "Olympic Hockey 98",
|
|
"Operation WinBack": "WinBack_ Covert Operations",
|
|
"Pokémon Puzzle League": "Pokemon Puzzle League",
|
|
"Pokémon Snap": "Pokemon Snap",
|
|
"Pokémon Stadium": "Pokemon Stadium",
|
|
"Pokémon Stadium 2": "Pokemon Stadium 2",
|
|
"Power Rangers Lightspeed Rescue": "Power Rangers_ Lightspeed Rescue",
|
|
"Quake 64": "Quake",
|
|
"Rainbow Six": "Tom Clancy_s Rainbow Six",
|
|
"Rampage World Tour": "Rampage_ World Tour",
|
|
"Ridge Racer 64": "RR64_ Ridge Racer 64",
|
|
"Rugrats: Treasure Hunt": "Rugrats_ Scavenger Hunt",
|
|
"Rush 2: Extreme Racing": "Rush 2_ Extreme Racing USA",
|
|
"San Francisco Rush": "San Francisco Rush_ Extreme Racing",
|
|
"Shadowgate 64: Trials of the Four Towers": "ShadowGate 64_ Trials of the Four Towers",
|
|
"Star Wars Episode I: Racer": "Star Wars Episode I_ Racer",
|
|
"Star Wars: Episode I Battle for Naboo": "Star Wars_ Episode I_ Battle for Naboo",
|
|
"Starcraft 64": "StarCraft 64",
|
|
"Supercross 2000": "SuperCross 2000",
|
|
"Superman 64": "Superman",
|
|
"The Powerpuff Girls: Chemical X-Traction": "The Powerpuff Girls_ Chemical X-traction",
|
|
"Tony Hawk's Skateboarding": "Tony Hawk_s Pro Skater",
|
|
"Toy Story 2: Buzz Lightyear to the Rescue":"Toy Story 2_ Buzz Lightyear to the Rescue!",
|
|
"Twisted Edge: Extreme Snowboarding": "Twisted Edge Extreme Snowboarding",
|
|
"V-Rally Edition '99": "V-Rally Edition 99",
|
|
"WCW vs. nWo: World Tour": "WCW Vs. nWo_ World Tour",
|
|
"WCW/nWo Revenge": "WCW_nWo Revenge",
|
|
"WipeOut 64": "Wipeout 64",
|
|
"World Cup '98": "World Cup 98",
|
|
"Xena: Warrior Princess": "Xena_ Warrior Princess_ The Talisman of Fate",
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Normalisation helper
|
|
# ---------------------------------------------------------------------------
|
|
def normalize(s):
|
|
"""Strip accents, remove degree/punctuation, lowercase, collapse spaces."""
|
|
# Decompose unicode (é -> e + combining accent) then strip combining chars
|
|
s = unicodedata.normalize("NFD", s)
|
|
s = "".join(c for c in s if unicodedata.category(c) != "Mn")
|
|
# Remove degree symbol and vulgar fraction one-third
|
|
s = s.replace("°", "").replace("\u2153", "")
|
|
# Replace underscores (used in filenames instead of ' : etc.)
|
|
s = s.replace("_", " ")
|
|
# Remove punctuation / special chars; keep alphanumerics and spaces
|
|
s = re.sub(r"[^a-z0-9 ]", "", s.lower())
|
|
# Collapse whitespace
|
|
s = re.sub(r"\s+", "", s)
|
|
return s
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Build coverart lookup { stem -> filename_with_extension }
|
|
# ---------------------------------------------------------------------------
|
|
coverart_files = {} # stem (without -01.png) -> full filename
|
|
for fname in os.listdir(COVERART_DIR):
|
|
if fname.endswith("-01.png"):
|
|
stem = fname[:-len("-01.png")]
|
|
coverart_files[stem] = fname
|
|
|
|
# Normalised lookup: normalized_stem -> stem
|
|
norm_to_stem = {normalize(stem): stem for stem in coverart_files}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Load games
|
|
# ---------------------------------------------------------------------------
|
|
with open(GAMES_JSON, "r", encoding="utf-8") as f:
|
|
games = json.load(f)
|
|
|
|
unmatched_before = [g for g in games if not g.get("cover_art_local")]
|
|
print(f"Games without cover_art_local before fix: {len(unmatched_before)}")
|
|
print()
|
|
|
|
matched = []
|
|
unmatched = []
|
|
|
|
for game in games:
|
|
if game.get("cover_art_local"):
|
|
continue # already set, skip
|
|
|
|
title = game.get("title", "")
|
|
|
|
# --- 1. Manual override ---
|
|
if title in MANUAL_OVERRIDES:
|
|
stem = MANUAL_OVERRIDES[title]
|
|
if stem in coverart_files:
|
|
game["cover_art_local"] = f"Coverart/{coverart_files[stem]}"
|
|
matched.append((title, coverart_files[stem], "manual override"))
|
|
continue
|
|
else:
|
|
print(f" WARNING: override stem not found in Coverart: {stem!r}")
|
|
|
|
# --- 2. Fuzzy / normalised match ---
|
|
norm_title = normalize(title)
|
|
if norm_title in norm_to_stem:
|
|
stem = norm_to_stem[norm_title]
|
|
game["cover_art_local"] = f"Coverart/{coverart_files[stem]}"
|
|
matched.append((title, coverart_files[stem], "fuzzy match"))
|
|
continue
|
|
|
|
# --- 3. Try stripping trailing year (4 digits) ---
|
|
title_no_year = re.sub(r"\s+\d{4}$", "", title).strip()
|
|
norm_no_year = normalize(title_no_year)
|
|
if norm_no_year != norm_title and norm_no_year in norm_to_stem:
|
|
stem = norm_to_stem[norm_no_year]
|
|
game["cover_art_local"] = f"Coverart/{coverart_files[stem]}"
|
|
matched.append((title, coverart_files[stem], "fuzzy match (no year)"))
|
|
continue
|
|
|
|
unmatched.append(title)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Report
|
|
# ---------------------------------------------------------------------------
|
|
print("=" * 70)
|
|
print(f"MATCHED ({len(matched)}):")
|
|
print("=" * 70)
|
|
for title, fname, method in matched:
|
|
print(f" [{method}]")
|
|
print(f" Game: {title!r}")
|
|
print(f" File: {fname!r}")
|
|
|
|
print()
|
|
print("=" * 70)
|
|
print(f"STILL UNMATCHED ({len(unmatched)}):")
|
|
print("=" * 70)
|
|
for t in unmatched:
|
|
print(f" {t!r}")
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Save updated games.json
|
|
# ---------------------------------------------------------------------------
|
|
with open(GAMES_JSON, "w", encoding="utf-8") as f:
|
|
json.dump(games, f, ensure_ascii=False, indent=2)
|
|
|
|
print()
|
|
print(f"Saved updated games.json (matched {len(matched)}/{len(unmatched_before)} previously unset entries)")
|