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

1
go.mod
View file

@ -57,6 +57,7 @@ require (
github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/pflag v1.0.6 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uploadedlobster.com/musicbrainzws2 v0.13.1 // indirect
golang.org/x/image v0.26.0 // indirect golang.org/x/image v0.26.0 // indirect
golang.org/x/mod v0.24.0 // indirect golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.39.0 // indirect golang.org/x/net v0.39.0 // indirect

2
go.sum
View file

@ -132,6 +132,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uploadedlobster.com/mbtypes v0.4.0 h1:D5asCgHsRWufj4Yn5u0IuH2J9z1UuYImYkYIp1Z1Q7s= go.uploadedlobster.com/mbtypes v0.4.0 h1:D5asCgHsRWufj4Yn5u0IuH2J9z1UuYImYkYIp1Z1Q7s=
go.uploadedlobster.com/mbtypes v0.4.0/go.mod h1:Bu1K1Hl77QTAE2Z7QKiW/JAp9KqYWQebkRRfG02dlZM= go.uploadedlobster.com/mbtypes v0.4.0/go.mod h1:Bu1K1Hl77QTAE2Z7QKiW/JAp9KqYWQebkRRfG02dlZM=
go.uploadedlobster.com/musicbrainzws2 v0.13.1 h1:34GKI7l9eTCyh9ozNOHmlwAAUTDK9WVRsFZK5trxcwQ=
go.uploadedlobster.com/musicbrainzws2 v0.13.1/go.mod h1:TVln70Fzp/++fw0/jCP1xXwgilVwDkzTwRbV8GwUYLA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=

View file

@ -22,6 +22,7 @@ import (
"time" "time"
"go.uploadedlobster.com/mbtypes" "go.uploadedlobster.com/mbtypes"
"go.uploadedlobster.com/musicbrainzws2"
"go.uploadedlobster.com/scotty/internal/config" "go.uploadedlobster.com/scotty/internal/config"
"go.uploadedlobster.com/scotty/internal/i18n" "go.uploadedlobster.com/scotty/internal/i18n"
"go.uploadedlobster.com/scotty/internal/models" "go.uploadedlobster.com/scotty/internal/models"
@ -31,6 +32,7 @@ import (
type ListenBrainzApiBackend struct { type ListenBrainzApiBackend struct {
client Client client Client
mbClient musicbrainzws2.Client
username string username string
checkDuplicates bool checkDuplicates bool
existingMBIDs map[mbtypes.MBID]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 { func (b *ListenBrainzApiBackend) FromConfig(config *config.ServiceConfig) models.Backend {
b.client = NewClient(config.GetString("token")) b.client = NewClient(config.GetString("token"))
b.mbClient = *musicbrainzws2.NewClient(version.AppName, version.AppVersion)
b.client.MaxResults = MaxItemsPerGet b.client.MaxResults = MaxItemsPerGet
b.username = config.GetString("username") b.username = config.GetString("username")
b.checkDuplicates = config.GetBool("check-duplicate-listens", false) b.checkDuplicates = config.GetBool("check-duplicate-listens", false)
@ -228,6 +231,16 @@ out:
} }
for _, feedback := range result.Feedback { 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() love := feedback.AsLove()
if love.Created.Unix() > oldestTimestamp.Unix() { if love.Created.Unix() > oldestTimestamp.Unix() {
loves = append(loves, love) loves = append(loves, love)
@ -265,6 +278,12 @@ func (b *ListenBrainzApiBackend) ImportLoves(export models.LovesResult, importRe
for _, love := range existingLoves.Items { for _, love := range existingLoves.Items {
b.existingMBIDs[love.RecordingMBID] = true 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 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 { func (lbListen Listen) AsListen() models.Listen {
listen := models.Listen{ listen := models.Listen{
ListenedAt: time.Unix(lbListen.ListenedAt, 0), ListenedAt: time.Unix(lbListen.ListenedAt, 0),