feat: artist images from MusicBrainz/Wikidata/Wikimedia Commons
Add ArtistImageProvider that resolves artist MBIDs to photo URLs: 1. MB url-rels 'image' type → extract Commons filename → thumb URL 2. MB url-rels 'wikidata' type → Wikidata P18 property → thumb URL 3. No image → falls back to initial-letter avatar Wikimedia Commons thumb URLs constructed via MD5 hash bucketing (standard Commons URL scheme). Results cached in explore_cache with 30-day TTL — subsequent lookups are instant. Frontend: search results and artist detail page show artist photos in the circular avatar when available. Images load async and replace the initial-letter fallback on arrival. Artist detail page fires the image fetch alongside the other 4 parallel data loads. Architecture supports adding more sources (fanart.tv, etc.) by extending the resolve() method's source chain.
This commit is contained in:
+25
-14
@@ -18,13 +18,14 @@ import (
|
||||
// response cache. Its exported methods form the binding surface
|
||||
// that the frontend calls via generated TypeScript stubs.
|
||||
type Service struct {
|
||||
mb *MusicBrainzClient
|
||||
lb *ListenBrainzClient
|
||||
cache *Cache
|
||||
index *SearchIndex
|
||||
artProxy *CoverArtProxy
|
||||
logger *slog.Logger
|
||||
ctx context.Context
|
||||
mb *MusicBrainzClient
|
||||
lb *ListenBrainzClient
|
||||
cache *Cache
|
||||
index *SearchIndex
|
||||
artProxy *CoverArtProxy
|
||||
artistImg *ArtistImageProvider
|
||||
logger *slog.Logger
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewExploreService creates a Service backed by the given
|
||||
@@ -37,17 +38,19 @@ func NewExploreService(logger *slog.Logger, db *database.DB) *Service {
|
||||
lb := NewListenBrainzClient(limiter, cache, logger.WithGroup("listenbrainz"))
|
||||
index := NewSearchIndex(db, lb, logger.WithGroup("search-index"))
|
||||
artProxy := NewCoverArtProxy(db, limiter)
|
||||
artistImg := NewArtistImageProvider(mb, cache, logger.WithGroup("artist-image"))
|
||||
|
||||
logger.Info("explore service created")
|
||||
|
||||
return &Service{
|
||||
mb: mb,
|
||||
lb: lb,
|
||||
cache: cache,
|
||||
index: index,
|
||||
artProxy: artProxy,
|
||||
logger: logger,
|
||||
ctx: context.Background(),
|
||||
mb: mb,
|
||||
lb: lb,
|
||||
cache: cache,
|
||||
index: index,
|
||||
artProxy: artProxy,
|
||||
artistImg: artistImg,
|
||||
logger: logger,
|
||||
ctx: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,6 +188,14 @@ func (e *Service) GetThumbnails(requests []ThumbnailRequest) map[string]string {
|
||||
return result
|
||||
}
|
||||
|
||||
// GetArtistImageURL returns a Wikimedia Commons thumbnail URL for
|
||||
// the given artist MBID. Resolved via MB url-rels → Wikidata P18
|
||||
// → Commons thumb URL. Cached for 30 days. Returns "" if no
|
||||
// image is available.
|
||||
func (e *Service) GetArtistImageURL(artistMBID string) string {
|
||||
return e.artistImg.GetArtistImageURL(artistMBID)
|
||||
}
|
||||
|
||||
// Search concurrently queries MusicBrainz for artists, release
|
||||
// groups, and recordings matching the query, then boosts results
|
||||
// using ListenBrainz popularity data. The final score blends
|
||||
|
||||
Reference in New Issue
Block a user