Add cover art fetcher; letterbox covers instead of cropping

tools/cover-art/fetch_art.py matches each game against libretro-thumbnails
by title + system and attaches the result through the app's own
POST /api/images, so fetched art goes through the same validation and WebP
re-encoding as a manual upload. Standard library only.

Matching bridges a personal catalogue and a ROM-naming one:
  * accents stripped, so "Pokemon Yellow" reaches "Pokémon"
  * roman numerals folded to digits, so the SNES "Final Fantasy 2" lands on
    "Final Fantasy II" and the PS1 "Final Fantasy V" on its own entry
  * trailing articles unwound ("Sims 2, The" -> "The Sims 2")
  * subtitle containment in both directions, since our rows sometimes omit
    what the catalogue carries ("Wave Race 64" vs "... - Kawasaki Jet Ski")
    and sometimes carry what it omits ("Donkey Kong Country 2: Diddy's Kong
    Quest" vs the GBA set's "Donkey Kong Country 2")
  * a sequel guard, so containment cannot collapse "Donkey Kong Country 2"
    onto "Donkey Kong Country"
  * fuzzy enough to absorb typos: "Brett Hull Hocky 95" finds "Hockey 95"

93 of 105 games now have art. The remainder: 10 Xbox 360 titles, which
libretro has no thumbnail set for, and two rows whose platform looks wrong
in the source data (a Game Boy "Donkey Kong Country 2", which was never
released on that system, and a DS "Donkey Kong Country Returns", which was
Wii and later 3DS).

Real art also invalidated a layout assumption: the grid used object-fit:
cover, which was fine for uniform placeholders but crops actual boxes, whose
aspect ratios run from near-square SNES to tall N64. Switched the grid and
the editor preview to object-fit: contain so the whole cover is visible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-04 11:41:02 -04:00
co-authored by Claude Opus 5
parent a99c8381b1
commit 55182a4da7
6 changed files with 439 additions and 2 deletions
+32
View File
@@ -75,6 +75,38 @@ cd frontend && npm test # vitest
cd backend && dotnet build # 0 warnings expected
```
### Cover art
`tools/cover-art/fetch_art.py` fills in box art from
[libretro-thumbnails](https://thumbnails.libretro.com), matching on title +
system and pushing each image through the app's own `POST /api/images`, so it
gets the same validation and WebP re-encoding as a manual upload. Standard
library only — no virtualenv needed.
```bash
cd tools/cover-art
python3 fetch_art.py --password '...' --dry-run # report matches, change nothing
python3 fetch_art.py --password '...' # download and attach
python3 fetch_art.py --password '...' --overwrite # also replace existing art
```
Always dry-run first; it prints every match with a similarity score and flags
anything below 0.95 for eyeballing.
Matching handles the gaps between a personal catalogue and a ROM-naming one:
accents (`Pokemon` → `Pokémon`), roman numerals (our SNES `Final Fantasy 2` is
the catalogue's `Final Fantasy II`), trailing articles (`Sims 2, The`), missing
subtitles in either direction, and outright typos — `Brett Hull Hocky 95` finds
`Brett Hull Hockey 95`. A sequel guard stops `Donkey Kong Country 2` from
silently taking `Donkey Kong Country`'s box.
**Xbox 360 is not covered** — libretro has no thumbnail set for it, so those 10
titles are reported as unsupported. They need a source such as IGDB, which
requires a free Twitch developer client ID and secret.
Art is publisher copyright. Fetching it for a private collection is ordinary
practice for library software; redistributing it is a different question.
### Database changes
```bash