ListenBrainz: Handle missing loves metadata for merged recordings

If a loved recording MBID got merged into another recording, the love on
ListenBrainz has no metadata. Lookup the metadata directly from
MusicBrainz.
This commit is contained in:
Philipp Wolfer 2025-04-27 18:56:09 +02:00
parent 9e1c2d8435
commit 91f78d04dd
No known key found for this signature in database
GPG key ID: 8FDF744D4919943B
3 changed files with 47 additions and 0 deletions

View file

@ -22,6 +22,7 @@ import (
"time"
"go.uploadedlobster.com/mbtypes"
"go.uploadedlobster.com/musicbrainzws2"
"go.uploadedlobster.com/scotty/internal/config"
"go.uploadedlobster.com/scotty/internal/i18n"
"go.uploadedlobster.com/scotty/internal/models"
@ -31,6 +32,7 @@ import (
type ListenBrainzApiBackend struct {
client Client
mbClient musicbrainzws2.Client
username string
checkDuplicates bool
existingMBIDs map[mbtypes.MBID]bool
@ -56,6 +58,7 @@ func (b *ListenBrainzApiBackend) Options() []models.BackendOption {
func (b *ListenBrainzApiBackend) FromConfig(config *config.ServiceConfig) models.Backend {
b.client = NewClient(config.GetString("token"))
b.mbClient = *musicbrainzws2.NewClient(version.AppName, version.AppVersion)
b.client.MaxResults = MaxItemsPerGet
b.username = config.GetString("username")
b.checkDuplicates = config.GetBool("check-duplicate-listens", false)
@ -228,6 +231,16 @@ out:
}
for _, feedback := range result.Feedback {
// Missing track metadata indicates that the recording MBID is no
// longer available and might have been merged. Try fetching details
// from MusicBrainz.
if feedback.TrackMetadata == nil {
track, err := b.lookupRecording(feedback.RecordingMBID)
if err == nil {
feedback.TrackMetadata = track
}
}
love := feedback.AsLove()
if love.Created.Unix() > oldestTimestamp.Unix() {
loves = append(loves, love)
@ -265,6 +278,12 @@ func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importRe
for _, love := range existingLoves.Items {
b.existingMBIDs[love.RecordingMBID] = true
// In case the loved MBID got merged the track MBID represents the
// actual recording MBID.
if love.Track.RecordingMBID != "" &&
love.Track.RecordingMBID != love.RecordingMBID {
b.existingMBIDs[love.Track.RecordingMBID] = true
}
}
}
}
@ -347,6 +366,31 @@ func (b *ListenBrainzApiBackend) checkDuplicateListen(listen models.Listen) (boo
return false, nil
}
func (b *ListenBrainzApiBackend) lookupRecording(mbid mbtypes.MBID) (*Track, error) {
filter := musicbrainzws2.IncludesFilter{
Includes: []string{"artist-credits"},
}
recording, err := b.mbClient.LookupRecording(mbid, filter)
if err != nil {
return nil, err
}
artistMBIDs := make([]mbtypes.MBID, 0, len(recording.ArtistCredit))
for _, artist := range recording.ArtistCredit {
artistMBIDs = append(artistMBIDs, artist.Artist.ID)
}
track := Track{
TrackName: recording.Title,
ArtistName: recording.ArtistCredit.String(),
MBIDMapping: &MBIDMapping{
// In case of redirects this MBID differs from the looked up MBID
RecordingMBID: recording.ID,
ArtistMBIDs: artistMBIDs,
},
}
return &track, nil
}
func (lbListen Listen) AsListen() models.Listen {
listen := models.Listen{
ListenedAt: time.Unix(lbListen.ListenedAt, 0),