From f74728fc73bfd45858ae195728eb5824a4b2f9aa Mon Sep 17 00:00:00 2001 From: Josh Patra Date: Tue, 10 Feb 2026 23:52:14 -0500 Subject: [PATCH] fix: use MBID lookup for MusicBrainz genre enrichment Search API doesn't return genres even with inc=genres parameter. Now doing search to get MBID, then lookup by MBID to get genres. --- .../MusicBrainz/MusicBrainzService.cs | 65 +++++++++++++++++-- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/allstarr/Services/MusicBrainz/MusicBrainzService.cs b/allstarr/Services/MusicBrainz/MusicBrainzService.cs index 025a2ee..164d26d 100644 --- a/allstarr/Services/MusicBrainz/MusicBrainzService.cs +++ b/allstarr/Services/MusicBrainz/MusicBrainzService.cs @@ -92,6 +92,7 @@ public class MusicBrainzService /// /// Searches for recordings by title and artist. + /// Note: Search API doesn't return genres, only MBIDs. Use LookupByMbidAsync to get genres. /// public async Task> SearchRecordingsAsync(string title, string artist, int limit = 5) { @@ -107,7 +108,8 @@ public class MusicBrainzService // Build Lucene query var query = $"recording:\"{title}\" AND artist:\"{artist}\""; var encodedQuery = Uri.EscapeDataString(query); - var url = $"{_settings.BaseUrl}/recording?query={encodedQuery}&fmt=json&limit={limit}&inc=genres+tags"; + // Note: Search API doesn't support inc=genres, only returns basic info + MBIDs + var url = $"{_settings.BaseUrl}/recording?query={encodedQuery}&fmt=json&limit={limit}"; _logger.LogDebug("MusicBrainz search: {Url}", url); @@ -140,9 +142,56 @@ public class MusicBrainzService } } + /// + /// Looks up a recording by MBID to get full details including genres. + /// + public async Task LookupByMbidAsync(string mbid) + { + if (!_settings.Enabled) + { + return null; + } + + await RateLimitAsync(); + + try + { + var url = $"{_settings.BaseUrl}/recording/{mbid}?fmt=json&inc=artists+releases+release-groups+genres+tags"; + _logger.LogDebug("MusicBrainz MBID lookup: {Url}", url); + + var response = await _httpClient.GetAsync(url); + + if (!response.IsSuccessStatusCode) + { + _logger.LogWarning("MusicBrainz MBID lookup failed: {StatusCode}", response.StatusCode); + return null; + } + + var json = await response.Content.ReadAsStringAsync(); + var recording = JsonSerializer.Deserialize(json, JsonOptions); + + if (recording == null) + { + _logger.LogDebug("No MusicBrainz recording found for MBID: {Mbid}", mbid); + return null; + } + + var genres = recording.Genres?.Select(g => g.Name).Where(n => !string.IsNullOrEmpty(n)).ToList() ?? new List(); + _logger.LogInformation("✓ Found MusicBrainz recording for MBID {Mbid}: {Title} by {Artist} (Genres: {Genres})", + mbid, recording.Title, recording.ArtistCredit?[0]?.Name ?? "Unknown", string.Join(", ", genres)); + + return recording; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error looking up MBID {Mbid} in MusicBrainz", mbid); + return null; + } + } + /// /// Enriches a song with genre information from MusicBrainz. - /// First tries ISRC lookup, then falls back to title/artist search. + /// First tries ISRC lookup, then falls back to title/artist search + MBID lookup. /// public async Task> GetGenresForSongAsync(string title, string artist, string? isrc = null) { @@ -153,17 +202,23 @@ public class MusicBrainzService MusicBrainzRecording? recording = null; - // Try ISRC lookup first (most accurate) + // Try ISRC lookup first (most accurate and includes genres) if (!string.IsNullOrEmpty(isrc)) { recording = await LookupByIsrcAsync(isrc); } - // Fall back to search if ISRC lookup failed or no ISRC provided + // Fall back to search + MBID lookup if ISRC lookup failed or no ISRC provided if (recording == null) { var recordings = await SearchRecordingsAsync(title, artist, limit: 1); - recording = recordings.FirstOrDefault(); + var searchResult = recordings.FirstOrDefault(); + + // If we found a recording from search, do a full lookup by MBID to get genres + if (searchResult != null && !string.IsNullOrEmpty(searchResult.Id)) + { + recording = await LookupByMbidAsync(searchResult.Id); + } } if (recording == null)