feat: 'In Library' badges, artist images on local pages, MBID-based Tier 3
Three features wired together: 1. 'In Library' badges on explore search results: CheckLibraryMBIDs Wails binding batch-checks which search result MBIDs exist in the local library. Green badges render on matching artist cards and album cards. 2. Artist images on local artist-details page: Local artist pages now call GetArtistMBID(name) to resolve the MBID from tags, then GetArtistImageURL(mbid) to fetch the cached Wikimedia photo. Falls back to initial-letter avatar. 3. Tier 3 search index uses direct MBIDs from tags: buildTier3Library now reads artists.mbid column (from audio tags) for direct MBID matching, falling back to name matching for untagged artists. Eliminates false matches and catches artists that name matching misses.
This commit is contained in:
@@ -24,6 +24,7 @@ type Service struct {
|
||||
index *SearchIndex
|
||||
artProxy *CoverArtProxy
|
||||
artistImg *ArtistImageProvider
|
||||
libMBID *LibraryMBIDIndex
|
||||
logger *slog.Logger
|
||||
ctx context.Context
|
||||
}
|
||||
@@ -41,6 +42,7 @@ func NewExploreService(logger *slog.Logger, db *database.DB) *Service {
|
||||
db, cache, NewRateLimiter(), logger.WithGroup("artist-image"),
|
||||
)
|
||||
index := NewSearchIndex(db, lb, artistImg, logger.WithGroup("search-index"))
|
||||
libMBID := NewLibraryMBIDIndex(db)
|
||||
|
||||
logger.Info("explore service created")
|
||||
|
||||
@@ -51,6 +53,7 @@ func NewExploreService(logger *slog.Logger, db *database.DB) *Service {
|
||||
index: index,
|
||||
artProxy: artProxy,
|
||||
artistImg: artistImg,
|
||||
libMBID: libMBID,
|
||||
logger: logger,
|
||||
ctx: context.Background(),
|
||||
}
|
||||
@@ -198,6 +201,19 @@ func (e *Service) GetArtistImageURL(artistMBID string) string {
|
||||
return e.artistImg.GetArtistImage(artistMBID)
|
||||
}
|
||||
|
||||
// CheckLibraryMBIDs returns which of the given MBIDs exist in the
|
||||
// local music library. Returns a map of MBID → entity type
|
||||
// ("artist", "release_group", "recording").
|
||||
func (e *Service) CheckLibraryMBIDs(mbids []string) map[string]string {
|
||||
return e.libMBID.CheckMBIDs(mbids)
|
||||
}
|
||||
|
||||
// GetArtistMBID returns the MusicBrainz ID for a local library
|
||||
// artist by name, or "" if not found or no MBID tagged.
|
||||
func (e *Service) GetArtistMBID(artistName string) string {
|
||||
return e.libMBID.GetArtistMBID(artistName)
|
||||
}
|
||||
|
||||
// Search concurrently queries MusicBrainz for artists, release
|
||||
// groups, and recordings matching the query, then boosts results
|
||||
// using ListenBrainz popularity data. The final score blends
|
||||
|
||||
@@ -683,8 +683,11 @@ func (si *SearchIndex) buildTier3Library(
|
||||
}
|
||||
}
|
||||
|
||||
// Read local library artist names.
|
||||
libRows, err := si.db.QueryContext("SELECT DISTINCT name FROM artists")
|
||||
// Read local library artists — prefer direct MBIDs from tags,
|
||||
// fall back to name matching against the sitewide/index map.
|
||||
libRows, err := si.db.QueryContext(
|
||||
"SELECT DISTINCT name, mbid FROM artists",
|
||||
)
|
||||
if err != nil {
|
||||
si.logger.Warn("search index: library artists query failed", "error", err)
|
||||
|
||||
@@ -693,18 +696,35 @@ func (si *SearchIndex) buildTier3Library(
|
||||
|
||||
defer func() { _ = libRows.Close() }()
|
||||
|
||||
// Collect MBIDs for matched library artists.
|
||||
var matched []lbSitewideArtist
|
||||
|
||||
var resolvedMBIDs []string
|
||||
|
||||
for libRows.Next() {
|
||||
var name string
|
||||
if err := libRows.Scan(&name); err != nil {
|
||||
|
||||
var mbidPtr *string
|
||||
|
||||
if err := libRows.Scan(&name, &mbidPtr); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Normalize: strip "feat." suffixes.
|
||||
// Direct MBID from tags — most reliable.
|
||||
if mbidPtr != nil && *mbidPtr != "" {
|
||||
mbid := *mbidPtr
|
||||
resolvedMBIDs = append(resolvedMBIDs, mbid)
|
||||
|
||||
if !indexed[mbid] {
|
||||
matched = append(matched, lbSitewideArtist{
|
||||
ArtistMBID: mbid,
|
||||
ArtistName: name,
|
||||
})
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Fall back to name matching.
|
||||
normalized := strings.ToLower(name)
|
||||
if idx := strings.Index(normalized, " feat."); idx >= 0 {
|
||||
normalized = normalized[:idx]
|
||||
@@ -719,7 +739,6 @@ func (si *SearchIndex) buildTier3Library(
|
||||
if a, ok := nameMap[normalized]; ok {
|
||||
resolvedMBIDs = append(resolvedMBIDs, a.ArtistMBID)
|
||||
|
||||
// Only index if not already in the index from Tier 2.
|
||||
if !indexed[a.ArtistMBID] {
|
||||
matched = append(matched, a)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user