Artist photos: fetch from Apple Music, cache, serve locally
Adds an artist portrait to the /artist/[key] header. Photos come from Apple Music's artist search, get downloaded and stored under ${DATA_PATH}/artist-photos/, and are served from a local /api/artist-art/[key] endpoint that mirrors the existing /api/artwork/[id] pattern.
What changes
-
artiststable (new) — keyed byartistKey, withname,photo_path(relative to DATA_PATH),photo_source,photo_fetched_at,photo_attempted_at. The attempt timestamp lets failed lookups back off for 7 days instead of retrying every scan. -
searchAppleMusicArtist(name)added to the existing Apple Music client. Hits?types=artistsacross the JP and US storefronts in parallel and picks the best name match. Returns the templated artwork URL ({w}x{h}intact) or null. -
fetchAndCacheArtistPhoto(key, name)— orchestrator: check cache, check back-off, search Apple Music, download to${DATA_PATH}/artist-photos/${slug}-${shortHash}.${ext}via.tmp+ atomic rename, single upsert intoartists. Slug for grep-ability, short-hash on the key to disambiguate slug collisions (matters more for romanized non-Western names). -
Sorted scan hook — after
scanSortedDirectoryfinishes its upsert + prune, walk distinct sorted artists, fetch missing photos viaPQueue({concurrency: 5}). Steady-state scans pay one DB lookup per artist because cache + back-off short-circuit inside the service. Skips theVarious ArtistsandUnknown Artistsentinels. -
/api/artist-art/[key]— readsartists.photo_path, serves the file with MIME sniffed from extension, 1h cache. -
/artist/[key]UI —<img>in the header pulling from the new endpoint, falling back to the existing person-icon placeholder on 404.
Verification
-
pnpm run buildpasses. - Set Apple Music creds in env and restart. The startup sorted scan runs
Fetching artist photos for N sorted artistsin the logs, followed by a summary tally. - Inspect
${DATA_PATH}/artist-photos/. Files haveslug-hash.jpgnames. - Open
/artist/<known-artist>— portrait renders in the header. - Open
/artist/<obscure-or-no-match>— placeholder icon renders;artistsrow exists withphoto_path NULLandphoto_attempted_atset. - Trigger another scan; in the log, that obscure artist now shows
backed-offand doesn't re-hit Apple Music. - Without Apple Music creds the service no-ops:
photo_attempted_atadvances butphoto_pathstays null, page falls back to placeholder.
Notes / future work
- Photo fetch is tied to the sorted scan only. Unsorted-only artists (someone whose albums haven't been sorted yet) won't get a photo until they appear on the sorted side. If that matters, add a parallel hook in
scanUnsortedDirectory. - No Discogs fallback yet — Apple Music coverage is fine for the common case; a Discogs source can layer in without schema changes by setting
photo_source = 'discogs'. - No 'Re-fetch this artist' button on the UI. Easy to add by hitting a
POST /api/artist-art/[key]action that callsfetchAndCacheArtistPhoto(key, name, {forceRefresh: true}).