From 31f6da9bc1856c28b994807de699bdf1272dff55 Mon Sep 17 00:00:00 2001 From: sentriz Date: Sat, 28 Oct 2023 18:27:17 +0100 Subject: [PATCH] feat: store and expose individual track artists a --- README.md | 1 + cmd/gonic/gonic.go | 4 +- db/db.go | 30 +++++++----- db/migrations.go | 10 ++++ mockfs/mockfs.go | 2 + scanner/scanner.go | 47 ++++++++++++++----- scanner/scanner_test.go | 8 ++-- ...fa903114a127d16fbf11dbd52256541a17c9f60b07 | 3 -- ...49f288aff8610f2cac65b7cf65b8708eb0ea104519 | 3 -- ...97f28d6e1e447100179409d69a827c6651370d96cc | 3 -- ...bf537d4d81f389524539f402d13aa01f93a65ac7e9 | 3 -- server/ctrlsubsonic/handlers_by_folder.go | 6 ++- server/ctrlsubsonic/handlers_by_tags.go | 7 +++ server/ctrlsubsonic/handlers_common.go | 3 ++ server/ctrlsubsonic/handlers_playlist.go | 2 +- .../ctrlsubsonic/spec/construct_by_folder.go | 3 ++ server/ctrlsubsonic/spec/construct_by_tags.go | 3 ++ server/ctrlsubsonic/spec/spec.go | 47 ++++++++++--------- .../testdata/test_get_album_with_cover | 3 ++ .../test_get_music_directory_with_tracks | 3 ++ .../testdata/test_search_three_q_tra | 20 ++++++++ .../testdata/test_search_two_q_tra | 20 ++++++++ tags/tagcommon/tagcommmon.go | 8 ++++ tags/taglib/taglib.go | 1 + 24 files changed, 176 insertions(+), 64 deletions(-) delete mode 100644 scanner/testdata/fuzz/FuzzScanner/22023bdee30f39396809ddfa903114a127d16fbf11dbd52256541a17c9f60b07 delete mode 100644 scanner/testdata/fuzz/FuzzScanner/50640a7a67794863f262b749f288aff8610f2cac65b7cf65b8708eb0ea104519 delete mode 100644 scanner/testdata/fuzz/FuzzScanner/b1f8e3fd8087149215418297f28d6e1e447100179409d69a827c6651370d96cc delete mode 100644 scanner/testdata/fuzz/FuzzScanner/caf81e9797b19c76c1fc4dbf537d4d81f389524539f402d13aa01f93a65ac7e9 diff --git a/README.md b/README.md index 7c3a8572..0e4c94ed 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ password can then be changed from the web interface | `GONIC_PODCAST_PURGE_AGE` | `-podcast-purge-age` | **optional** age (in days) to purge podcast episodes if not accessed | | `GONIC_EXCLUDE_PATTERN` | `-exclude-pattern` | **optional** files matching this regex pattern will not be imported | | `GONIC_MULTI_VALUE_GENRE` | `-multi-value-genre` | **optional** setting for multi-valued genre tags when scanning ([see more](#multi-valued-tags)) | +| `GONIC_MULTI_VALUE_ARTIST` | `-multi-value-artist` | **optional** setting for multi-valued artist tags when scanning ([see more](#multi-valued-tags)) | | `GONIC_MULTI_VALUE_ALBUM_ARTIST` | `-multi-value-album-artist` | **optional** setting for multi-valued album artist tags when scanning ([see more](#multi-valued-tags)) | | `GONIC_EXPVAR` | `-expvar` | **optional** enable the /debug/vars endpoint (exposes useful debugging attributes as well as database stats) | diff --git a/cmd/gonic/gonic.go b/cmd/gonic/gonic.go index 9edba3f6..dd2b4ae6 100644 --- a/cmd/gonic/gonic.go +++ b/cmd/gonic/gonic.go @@ -81,8 +81,9 @@ func main() { confExcludePatterns := set.String("exclude-pattern", "", "regex pattern to exclude files from scan (optional)") - var confMultiValueGenre, confMultiValueAlbumArtist multiValueSetting + var confMultiValueGenre, confMultiValueArtist, confMultiValueAlbumArtist multiValueSetting set.Var(&confMultiValueGenre, "multi-value-genre", "setting for mutli-valued genre scanning (optional)") + set.Var(&confMultiValueArtist, "multi-value-artist", "setting for mutli-valued track artist scanning (optional)") set.Var(&confMultiValueAlbumArtist, "multi-value-album-artist", "setting for mutli-valued album artist scanning (optional)") confExpvar := set.Bool("expvar", false, "enable the /debug/vars endpoint (optional)") @@ -184,6 +185,7 @@ func main() { dbc, map[scanner.Tag]scanner.MultiValueSetting{ scanner.Genre: scanner.MultiValueSetting(confMultiValueGenre), + scanner.Artist: scanner.MultiValueSetting(confMultiValueArtist), scanner.AlbumArtist: scanner.MultiValueSetting(confMultiValueAlbumArtist), }, tagReader, diff --git a/db/db.go b/db/db.go index 94e06795..2a93b569 100644 --- a/db/db.go +++ b/db/db.go @@ -201,17 +201,18 @@ type Track struct { Filename string `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null"` FilenameUDec string `sql:"default: null"` Album *Album - AlbumID int `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"` - Genres []*Genre `gorm:"many2many:track_genres"` - Size int `sql:"default: null"` - Length int `sql:"default: null"` - Bitrate int `sql:"default: null"` - TagTitle string `sql:"default: null"` - TagTitleUDec string `sql:"default: null"` - TagTrackArtist string `sql:"default: null"` - TagTrackNumber int `sql:"default: null"` - TagDiscNumber int `sql:"default: null"` - TagBrainzID string `sql:"default: null"` + AlbumID int `gorm:"not null; unique_index:idx_folder_filename" sql:"default: null; type:int REFERENCES albums(id) ON DELETE CASCADE"` + Artists []*Artist `gorm:"many2many:track_artists"` + Genres []*Genre `gorm:"many2many:track_genres"` + Size int `sql:"default: null"` + Length int `sql:"default: null"` + Bitrate int `sql:"default: null"` + TagTitle string `sql:"default: null"` + TagTitleUDec string `sql:"default: null"` + TagTrackArtist string `sql:"default: null"` + TagTrackNumber int `sql:"default: null"` + TagDiscNumber int `sql:"default: null"` + TagBrainzID string `sql:"default: null"` TrackStar *TrackStar TrackRating *TrackRating AverageRating float64 `sql:"default: null"` @@ -372,6 +373,13 @@ type AlbumArtist struct { ArtistID int `gorm:"not null; unique_index:idx_album_id_artist_id" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"` } +type TrackArtist struct { + Track *Track + TrackID int `gorm:"not null; unique_index:idx_track_id_artist_id" sql:"default: null; type:int REFERENCES tracks(id) ON DELETE CASCADE"` + Artist *Artist + ArtistID int `gorm:"not null; unique_index:idx_track_id_artist_id" sql:"default: null; type:int REFERENCES artists(id) ON DELETE CASCADE"` +} + type TrackGenre struct { Track *Track TrackID int `gorm:"not null; unique_index:idx_track_id_genre_id" sql:"default: null; type:int REFERENCES tracks(id) ON DELETE CASCADE"` diff --git a/db/migrations.go b/db/migrations.go index 587d4f6e..8e9d6c62 100644 --- a/db/migrations.go +++ b/db/migrations.go @@ -67,6 +67,7 @@ func (db *DB) Migrate(ctx MigrationContext) error { construct(ctx, "202309131743", migrateArtistInfo), construct(ctx, "202309161411", migratePlaylistsPaths), construct(ctx, "202310252205", migrateAlbumTagArtistString), + construct(ctx, "202310281803", migrateTrackArtists), } return gormigrate. @@ -734,3 +735,12 @@ func backupDBPre016(tx *gorm.DB, ctx MigrationContext) error { func migrateAlbumTagArtistString(tx *gorm.DB, _ MigrationContext) error { return tx.AutoMigrate(Album{}).Error } + +func migrateTrackArtists(tx *gorm.DB, _ MigrationContext) error { + // gorms seems to want to create the table automatically without ON DELETE rules + step := tx.DropTableIfExists(TrackArtist{}) + if err := step.Error; err != nil { + return fmt.Errorf("step drop prev: %w", err) + } + return tx.AutoMigrate(TrackArtist{}).Error +} diff --git a/mockfs/mockfs.go b/mockfs/mockfs.go index ea332eef..23eabc54 100644 --- a/mockfs/mockfs.go +++ b/mockfs/mockfs.go @@ -339,6 +339,7 @@ func (m *tagReader) Read(absPath string) (tagcommon.Info, error) { type TagInfo struct { RawTitle string RawArtist string + RawArtists []string RawAlbum string RawAlbumArtist string RawAlbumArtists []string @@ -351,6 +352,7 @@ type TagInfo struct { func (i *TagInfo) Title() string { return i.RawTitle } func (i *TagInfo) BrainzID() string { return "" } func (i *TagInfo) Artist() string { return i.RawArtist } +func (i *TagInfo) Artists() []string { return i.RawArtists } func (i *TagInfo) Album() string { return i.RawAlbum } func (i *TagInfo) AlbumArtist() string { return i.RawAlbumArtist } func (i *TagInfo) AlbumArtists() []string { return i.RawAlbumArtists } diff --git a/scanner/scanner.go b/scanner/scanner.go index 3c471c70..24d814a2 100644 --- a/scanner/scanner.go +++ b/scanner/scanner.go @@ -296,7 +296,7 @@ func (s *Scanner) scanDir(tx *db.DB, c *Context, absPath string) error { sort.Strings(tracks) for i, basename := range tracks { absPath := filepath.Join(musicDir, relPath, basename) - if err := s.populateTrackAndAlbumArtists(tx, c, i, &album, basename, absPath); err != nil { + if err := s.populateTrackAndArtists(tx, c, i, &album, basename, absPath); err != nil { return fmt.Errorf("populate track %q: %w", basename, err) } } @@ -304,7 +304,7 @@ func (s *Scanner) scanDir(tx *db.DB, c *Context, absPath string) error { return nil } -func (s *Scanner) populateTrackAndAlbumArtists(tx *db.DB, c *Context, i int, album *db.Album, basename string, absPath string) error { +func (s *Scanner) populateTrackAndArtists(tx *db.DB, c *Context, i int, album *db.Album, basename string, absPath string) error { stat, err := os.Stat(absPath) if err != nil { return fmt.Errorf("stating %q: %w", basename, err) @@ -362,6 +362,19 @@ func (s *Scanner) populateTrackAndAlbumArtists(tx *db.DB, c *Context, i int, alb return fmt.Errorf("populate track genres: %w", err) } + trackArtistNames := parseMulti(trags, s.multiValueSettings[Artist], tagcommon.MustArtists, tagcommon.MustArtist) + var trackArtistIDs []int + for _, trackArtistName := range trackArtistNames { + trackArtist, err := populateArtist(tx, trackArtistName) + if err != nil { + return fmt.Errorf("populate track artist: %w", err) + } + trackArtistIDs = append(trackArtistIDs, trackArtist.ID) + } + if err := populateTrackArtists(tx, &track, trackArtistIDs); err != nil { + return fmt.Errorf("populate track artists: %w", err) + } + c.seenTracks[track.ID] = struct{}{} c.seenTracksNew++ @@ -498,6 +511,17 @@ func populateAlbumArtists(tx *db.DB, album *db.Album, albumArtistIDs []int) erro return nil } +func populateTrackArtists(tx *db.DB, track *db.Track, trackArtistIDs []int) error { + if err := tx.Where("track_id=?", track.ID).Delete(db.TrackArtist{}).Error; err != nil { + return fmt.Errorf("delete old track artists: %w", err) + } + + if err := tx.InsertBulkLeftMany("track_artists", []string{"track_id", "artist_id"}, track.ID, trackArtistIDs); err != nil { + return fmt.Errorf("insert bulk track artists: %w", err) + } + return nil +} + func (s *Scanner) cleanTracks(c *Context) error { start := time.Now() defer func() { log.Printf("finished clean tracks in %s, %d removed", durSince(start), c.TracksMissing()) }() @@ -546,15 +570,15 @@ func (s *Scanner) cleanArtists(c *Context) error { start := time.Now() defer func() { log.Printf("finished clean artists in %s, %d removed", durSince(start), c.ArtistsMissing()) }() - sub := s.db. - Select("artists.id"). - Model(&db.Artist{}). - Joins("LEFT JOIN album_artists ON album_artists.artist_id=artists.id"). - Where("album_artists.artist_id IS NULL"). - SubQuery() - q := s.db. - Where("artists.id IN ?", sub). - Delete(&db.Artist{}) + // gorm doesn't seem to support subqueries without parens for UNION + q := s.db.Exec(` + DELETE FROM artists + WHERE id NOT IN ( + SELECT artist_id FROM track_artists + UNION + SELECT artist_id FROM album_artists + ) + `) if err := q.Error; err != nil { return err } @@ -654,6 +678,7 @@ type Tag uint8 const ( Genre Tag = iota + Artist AlbumArtist ) diff --git a/scanner/scanner_test.go b/scanner/scanner_test.go index bf49a389..0fd24549 100644 --- a/scanner/scanner_test.go +++ b/scanner/scanner_test.go @@ -562,7 +562,7 @@ func TestCompilationAlbumWithoutAlbumArtist(t *testing.T) { assert.Equal(t, 5, trackCount) var artists []*db.Artist - assert.NoError(t, m.DB().Find(&artists).Error) + assert.NoError(t, m.DB().Joins("JOIN album_artists ON album_artists.artist_id=artists.id").Group("artists.id").Find(&artists).Error) assert.Equal(t, 1, len(artists)) // we only have one album artist assert.Equal(t, "artist 0", artists[0].Name) // it came from the first track's fallback to artist tag @@ -656,7 +656,7 @@ func TestMultiArtistSupport(t *testing.T) { m.ScanAndClean() var artists []*db.Artist - assert.NoError(t, m.DB().Find(&artists).Error) + assert.NoError(t, m.DB().Joins("JOIN album_artists ON album_artists.artist_id=artists.id").Group("artists.id").Find(&artists).Error) assert.Len(t, artists, 3) // alan, liz, mercury var albumArtists []*db.AlbumArtist @@ -695,7 +695,7 @@ func TestMultiArtistSupport(t *testing.T) { m.ScanAndClean() - assert.NoError(t, m.DB().Find(&artists).Error) + assert.NoError(t, m.DB().Joins("JOIN album_artists ON album_artists.artist_id=artists.id").Group("artists.id").Find(&artists).Error) assert.Len(t, artists, 2) // alan, liz assert.NoError(t, m.DB().Find(&albumArtists).Error) @@ -745,7 +745,7 @@ func TestMultiArtistPreload(t *testing.T) { } var artists []*db.Artist - assert.NoError(t, m.DB().Preload("Albums").Find(&artists).Error) + assert.NoError(t, m.DB().Preload("Albums").Joins("JOIN album_artists ON album_artists.artist_id=artists.id").Group("artists.id").Find(&artists).Error) assert.Equal(t, 3, len(artists)) for _, artist := range artists { diff --git a/scanner/testdata/fuzz/FuzzScanner/22023bdee30f39396809ddfa903114a127d16fbf11dbd52256541a17c9f60b07 b/scanner/testdata/fuzz/FuzzScanner/22023bdee30f39396809ddfa903114a127d16fbf11dbd52256541a17c9f60b07 deleted file mode 100644 index 7865f865..00000000 --- a/scanner/testdata/fuzz/FuzzScanner/22023bdee30f39396809ddfa903114a127d16fbf11dbd52256541a17c9f60b07 +++ /dev/null @@ -1,3 +0,0 @@ -go test fuzz v1 -[]byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") -int64(0) diff --git a/scanner/testdata/fuzz/FuzzScanner/50640a7a67794863f262b749f288aff8610f2cac65b7cf65b8708eb0ea104519 b/scanner/testdata/fuzz/FuzzScanner/50640a7a67794863f262b749f288aff8610f2cac65b7cf65b8708eb0ea104519 deleted file mode 100644 index e35970af..00000000 --- a/scanner/testdata/fuzz/FuzzScanner/50640a7a67794863f262b749f288aff8610f2cac65b7cf65b8708eb0ea104519 +++ /dev/null @@ -1,3 +0,0 @@ -go test fuzz v1 -[]byte("001") -int64(3472329395739373616) diff --git a/scanner/testdata/fuzz/FuzzScanner/b1f8e3fd8087149215418297f28d6e1e447100179409d69a827c6651370d96cc b/scanner/testdata/fuzz/FuzzScanner/b1f8e3fd8087149215418297f28d6e1e447100179409d69a827c6651370d96cc deleted file mode 100644 index e35970af..00000000 --- a/scanner/testdata/fuzz/FuzzScanner/b1f8e3fd8087149215418297f28d6e1e447100179409d69a827c6651370d96cc +++ /dev/null @@ -1,3 +0,0 @@ -go test fuzz v1 -[]byte("001") -int64(3472329395739373616) diff --git a/scanner/testdata/fuzz/FuzzScanner/caf81e9797b19c76c1fc4dbf537d4d81f389524539f402d13aa01f93a65ac7e9 b/scanner/testdata/fuzz/FuzzScanner/caf81e9797b19c76c1fc4dbf537d4d81f389524539f402d13aa01f93a65ac7e9 deleted file mode 100644 index ffa7e5c5..00000000 --- a/scanner/testdata/fuzz/FuzzScanner/caf81e9797b19c76c1fc4dbf537d4d81f389524539f402d13aa01f93a65ac7e9 +++ /dev/null @@ -1,3 +0,0 @@ -go test fuzz v1 -[]byte("") -int64(0) diff --git a/server/ctrlsubsonic/handlers_by_folder.go b/server/ctrlsubsonic/handlers_by_folder.go index b542d303..7ea90ec8 100644 --- a/server/ctrlsubsonic/handlers_by_folder.go +++ b/server/ctrlsubsonic/handlers_by_folder.go @@ -91,6 +91,7 @@ func (c *Controller) ServeGetMusicDirectory(r *http.Request) *spec.Response { Where("album_id=?", id.Value). Preload("Album"). Preload("Album.Artists"). + Preload("Artists"). Preload("TrackStar", "user_id=?", user.ID). Preload("TrackRating", "user_id=?", user.ID). Order("filename"). @@ -255,7 +256,9 @@ func (c *Controller) ServeSearchTwo(r *http.Request) *spec.Response { for _, s := range queries { q = q.Where(`filename LIKE ? OR filename LIKE ?`, s, s) } - q = q.Preload("TrackStar", "user_id=?", user.ID). + q = q. + Preload("Artists"). + Preload("TrackStar", "user_id=?", user.ID). Preload("TrackRating", "user_id=?", user.ID). Offset(params.GetOrInt("songOffset", 0)). Limit(params.GetOrInt("songCount", 20)) @@ -338,6 +341,7 @@ func (c *Controller) ServeGetStarred(r *http.Request) *spec.Response { Preload("Album"). Joins("JOIN track_stars ON tracks.id=track_stars.track_id"). Where("track_stars.user_id=?", user.ID). + Preload("Artists"). Preload("TrackStar", "user_id=?", user.ID). Preload("TrackRating", "user_id=?", user.ID) if m := getMusicFolder(c.musicPaths, params); m != "" { diff --git a/server/ctrlsubsonic/handlers_by_tags.go b/server/ctrlsubsonic/handlers_by_tags.go index c3830985..bf5ab1f6 100644 --- a/server/ctrlsubsonic/handlers_by_tags.go +++ b/server/ctrlsubsonic/handlers_by_tags.go @@ -108,6 +108,7 @@ func (c *Controller) ServeGetAlbum(r *http.Request) *spec.Response { Preload("Tracks", func(db *gorm.DB) *gorm.DB { return db. Order("tracks.tag_disc_number, tracks.tag_track_number"). + Preload("Artists"). Preload("TrackStar", "user_id=?", user.ID). Preload("TrackRating", "user_id=?", user.ID) }). @@ -272,6 +273,7 @@ func (c *Controller) ServeSearchThree(r *http.Request) *spec.Response { Preload("Album"). Preload("Album.Artists"). Preload("Genres"). + Preload("Artists"). Preload("TrackStar", "user_id=?", user.ID). Preload("TrackRating", "user_id=?", user.ID) for _, s := range queries { @@ -409,6 +411,7 @@ func (c *Controller) ServeGetSongsByGenre(r *http.Request) *spec.Response { Joins("JOIN genres ON track_genres.genre_id=genres.id AND genres.name=?", genre). Preload("Album"). Preload("Album.Artists"). + Preload("Artists"). Preload("TrackStar", "user_id=?", user.ID). Preload("TrackRating", "user_id=?", user.ID). Offset(params.GetOrInt("offset", 0)). @@ -490,6 +493,7 @@ func (c *Controller) ServeGetStarredTwo(r *http.Request) *spec.Response { Order("track_stars.star_date DESC"). Preload("Album"). Preload("Album.Artists"). + Preload("Artists"). Preload("TrackStar", "user_id=?", user.ID). Preload("TrackRating", "user_id=?", user.ID) if m := getMusicFolder(c.musicPaths, params); m != "" { @@ -562,6 +566,7 @@ func (c *Controller) ServeGetTopSongs(r *http.Request) *spec.Response { Joins("JOIN album_artists ON album_artists.album_id=albums.id"). Where("album_artists.artist_id=? AND tracks.tag_title IN (?)", artist.ID, topTrackNames). Limit(count). + Preload("Artists"). Preload("TrackStar", "user_id=?", user.ID). Preload("TrackRating", "user_id=?", user.ID). Group("tracks.id"). @@ -622,6 +627,7 @@ func (c *Controller) ServeGetSimilarSongs(r *http.Request) *spec.Response { err = c.dbc. Select("tracks.*"). Preload("Album"). + Preload("Artists"). Preload("TrackStar", "user_id=?", user.ID). Preload("TrackRating", "user_id=?", user.ID). Where("tracks.tag_title IN (?)", similarTrackNames). @@ -685,6 +691,7 @@ func (c *Controller) ServeGetSimilarSongsTwo(r *http.Request) *spec.Response { var tracks []*db.Track err = c.dbc. Preload("Album"). + Preload("Artists"). Preload("TrackStar", "user_id=?", user.ID). Preload("TrackRating", "user_id=?", user.ID). Joins("JOIN album_artists ON album_artists.album_id=tracks.album_id"). diff --git a/server/ctrlsubsonic/handlers_common.go b/server/ctrlsubsonic/handlers_common.go index d42caf9c..b435f533 100644 --- a/server/ctrlsubsonic/handlers_common.go +++ b/server/ctrlsubsonic/handlers_common.go @@ -212,6 +212,7 @@ func (c *Controller) ServeGetPlayQueue(r *http.Request) *spec.Response { c.dbc. Where("id=?", id.Value). Preload("Album"). + Preload("Artists"). Preload("TrackStar", "user_id=?", user.ID). Preload("TrackRating", "user_id=?", user.ID). Find(&track) @@ -268,6 +269,7 @@ func (c *Controller) ServeGetSong(r *http.Request) *spec.Response { Where("id=?", id.Value). Preload("Album"). Preload("Album.Artists"). + Preload("Artists"). Preload("TrackStar", "user_id=?", user.ID). Preload("TrackRating", "user_id=?", user.ID). First(&track). @@ -294,6 +296,7 @@ func (c *Controller) ServeGetRandomSongs(r *http.Request) *spec.Response { Limit(params.GetOrInt("size", 10)). Preload("Album"). Preload("Album.Artists"). + Preload("Artists"). Preload("TrackStar", "user_id=?", user.ID). Preload("TrackRating", "user_id=?", user.ID). Joins("JOIN albums ON tracks.album_id=albums.id"). diff --git a/server/ctrlsubsonic/handlers_playlist.go b/server/ctrlsubsonic/handlers_playlist.go index d196b70e..de03430f 100644 --- a/server/ctrlsubsonic/handlers_playlist.go +++ b/server/ctrlsubsonic/handlers_playlist.go @@ -218,7 +218,7 @@ func playlistRender(c *Controller, params params.Params, playlistID string, play switch id := file.SID(); id.Type { case specid.Track: var track db.Track - if err := c.dbc.Where("id=?", id.Value).Preload("Album").Preload("Album.Artists").Preload("TrackStar", "user_id=?", user.ID).Preload("TrackRating", "user_id=?", user.ID).Find(&track).Error; errors.Is(err, gorm.ErrRecordNotFound) { + if err := c.dbc.Where("id=?", id.Value).Preload("Album").Preload("Album.Artists").Preload("Artists").Preload("TrackStar", "user_id=?", user.ID).Preload("TrackRating", "user_id=?", user.ID).Find(&track).Error; errors.Is(err, gorm.ErrRecordNotFound) { return nil, fmt.Errorf("load track by id: %w", err) } trch = spec.NewTCTrackByFolder(&track, track.Album) diff --git a/server/ctrlsubsonic/spec/construct_by_folder.go b/server/ctrlsubsonic/spec/construct_by_folder.go index f1c7c203..fb42786e 100644 --- a/server/ctrlsubsonic/spec/construct_by_folder.go +++ b/server/ctrlsubsonic/spec/construct_by_folder.go @@ -96,6 +96,9 @@ func NewTCTrackByFolder(t *db.Track, parent *db.Album) *TrackChild { for _, g := range t.Genres { trCh.Genres = append(trCh.Genres, &GenreRef{Name: g.Name}) } + for _, a := range t.Artists { + trCh.Artists = append(trCh.Artists, &ArtistRef{ID: a.SID(), Name: a.Name}) + } return trCh } diff --git a/server/ctrlsubsonic/spec/construct_by_tags.go b/server/ctrlsubsonic/spec/construct_by_tags.go index 01b9c6cc..ea59b68d 100644 --- a/server/ctrlsubsonic/spec/construct_by_tags.go +++ b/server/ctrlsubsonic/spec/construct_by_tags.go @@ -94,6 +94,9 @@ func NewTrackByTags(t *db.Track, album *db.Album) *TrackChild { for _, g := range t.Genres { ret.Genres = append(ret.Genres, &GenreRef{Name: g.Name}) } + for _, a := range t.Artists { + ret.Artists = append(ret.Artists, &ArtistRef{ID: a.SID(), Name: a.Name}) + } return ret } diff --git a/server/ctrlsubsonic/spec/spec.go b/server/ctrlsubsonic/spec/spec.go index 1f9c055d..a47c0074 100644 --- a/server/ctrlsubsonic/spec/spec.go +++ b/server/ctrlsubsonic/spec/spec.go @@ -160,29 +160,30 @@ type TranscodeMeta struct { } type TrackChild struct { - ID *specid.ID `xml:"id,attr,omitempty" json:"id,omitempty"` - Album string `xml:"album,attr,omitempty" json:"album,omitempty"` - AlbumID *specid.ID `xml:"albumId,attr,omitempty" json:"albumId,omitempty"` - Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"` - ArtistID *specid.ID `xml:"artistId,attr,omitempty" json:"artistId,omitempty"` - Bitrate int `xml:"bitRate,attr,omitempty" json:"bitRate,omitempty"` - ContentType string `xml:"contentType,attr,omitempty" json:"contentType,omitempty"` - CoverID *specid.ID `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty"` - CreatedAt time.Time `xml:"created,attr,omitempty" json:"created,omitempty"` - Duration int `xml:"duration,attr,omitempty" json:"duration,omitempty"` - Genre string `xml:"genre,attr,omitempty" json:"genre,omitempty"` - Genres []*GenreRef `xml:"genres,omitempty" json:"genres,omitempty"` - IsDir bool `xml:"isDir,attr" json:"isDir"` - IsVideo bool `xml:"isVideo,attr" json:"isVideo"` - ParentID *specid.ID `xml:"parent,attr,omitempty" json:"parent,omitempty"` - Path string `xml:"path,attr,omitempty" json:"path,omitempty"` - Size int `xml:"size,attr,omitempty" json:"size,omitempty"` - Suffix string `xml:"suffix,attr,omitempty" json:"suffix,omitempty"` - Title string `xml:"title,attr" json:"title"` - TrackNumber int `xml:"track,attr,omitempty" json:"track,omitempty"` - DiscNumber int `xml:"discNumber,attr,omitempty" json:"discNumber,omitempty"` - Type string `xml:"type,attr,omitempty" json:"type,omitempty"` - Year int `xml:"year,attr,omitempty" json:"year,omitempty"` + ID *specid.ID `xml:"id,attr,omitempty" json:"id,omitempty"` + Album string `xml:"album,attr,omitempty" json:"album,omitempty"` + AlbumID *specid.ID `xml:"albumId,attr,omitempty" json:"albumId,omitempty"` + Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"` + ArtistID *specid.ID `xml:"artistId,attr,omitempty" json:"artistId,omitempty"` + Artists []*ArtistRef `xml:"artists,omitempty" json:"artists,omitempty"` + Bitrate int `xml:"bitRate,attr,omitempty" json:"bitRate,omitempty"` + ContentType string `xml:"contentType,attr,omitempty" json:"contentType,omitempty"` + CoverID *specid.ID `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty"` + CreatedAt time.Time `xml:"created,attr,omitempty" json:"created,omitempty"` + Duration int `xml:"duration,attr,omitempty" json:"duration,omitempty"` + Genre string `xml:"genre,attr,omitempty" json:"genre,omitempty"` + Genres []*GenreRef `xml:"genres,omitempty" json:"genres,omitempty"` + IsDir bool `xml:"isDir,attr" json:"isDir"` + IsVideo bool `xml:"isVideo,attr" json:"isVideo"` + ParentID *specid.ID `xml:"parent,attr,omitempty" json:"parent,omitempty"` + Path string `xml:"path,attr,omitempty" json:"path,omitempty"` + Size int `xml:"size,attr,omitempty" json:"size,omitempty"` + Suffix string `xml:"suffix,attr,omitempty" json:"suffix,omitempty"` + Title string `xml:"title,attr" json:"title"` + TrackNumber int `xml:"track,attr,omitempty" json:"track,omitempty"` + DiscNumber int `xml:"discNumber,attr,omitempty" json:"discNumber,omitempty"` + Type string `xml:"type,attr,omitempty" json:"type,omitempty"` + Year int `xml:"year,attr,omitempty" json:"year,omitempty"` // star / rating Starred *time.Time `xml:"starred,attr,omitempty" json:"starred,omitempty"` UserRating int `xml:"userRating,attr,omitempty" json:"userRating,omitempty"` diff --git a/server/ctrlsubsonic/testdata/test_get_album_with_cover b/server/ctrlsubsonic/testdata/test_get_album_with_cover index 0481e27e..1e24d68e 100644 --- a/server/ctrlsubsonic/testdata/test_get_album_with_cover +++ b/server/ctrlsubsonic/testdata/test_get_album_with_cover @@ -27,6 +27,7 @@ "albumId": "al-3", "artist": "artist-0", "artistId": "ar-1", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-3", @@ -49,6 +50,7 @@ "albumId": "al-3", "artist": "artist-0", "artistId": "ar-1", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-3", @@ -71,6 +73,7 @@ "albumId": "al-3", "artist": "artist-0", "artistId": "ar-1", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-3", diff --git a/server/ctrlsubsonic/testdata/test_get_music_directory_with_tracks b/server/ctrlsubsonic/testdata/test_get_music_directory_with_tracks index 32430f7f..ddd27204 100644 --- a/server/ctrlsubsonic/testdata/test_get_music_directory_with_tracks +++ b/server/ctrlsubsonic/testdata/test_get_music_directory_with_tracks @@ -14,6 +14,7 @@ "id": "tr-1", "album": "album-0", "artist": "artist-0", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-3", @@ -34,6 +35,7 @@ "id": "tr-2", "album": "album-0", "artist": "artist-0", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-3", @@ -54,6 +56,7 @@ "id": "tr-3", "album": "album-0", "artist": "artist-0", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-3", diff --git a/server/ctrlsubsonic/testdata/test_search_three_q_tra b/server/ctrlsubsonic/testdata/test_search_three_q_tra index 5ac9afe0..a3df7198 100644 --- a/server/ctrlsubsonic/testdata/test_search_three_q_tra +++ b/server/ctrlsubsonic/testdata/test_search_three_q_tra @@ -13,6 +13,7 @@ "albumId": "al-3", "artist": "artist-0", "artistId": "ar-1", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-3", @@ -37,6 +38,7 @@ "albumId": "al-3", "artist": "artist-0", "artistId": "ar-1", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-3", @@ -61,6 +63,7 @@ "albumId": "al-3", "artist": "artist-0", "artistId": "ar-1", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-3", @@ -85,6 +88,7 @@ "albumId": "al-4", "artist": "artist-0", "artistId": "ar-1", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-4", @@ -109,6 +113,7 @@ "albumId": "al-4", "artist": "artist-0", "artistId": "ar-1", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-4", @@ -133,6 +138,7 @@ "albumId": "al-4", "artist": "artist-0", "artistId": "ar-1", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-4", @@ -157,6 +163,7 @@ "albumId": "al-5", "artist": "artist-0", "artistId": "ar-1", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-5", @@ -181,6 +188,7 @@ "albumId": "al-5", "artist": "artist-0", "artistId": "ar-1", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-5", @@ -205,6 +213,7 @@ "albumId": "al-5", "artist": "artist-0", "artistId": "ar-1", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-5", @@ -229,6 +238,7 @@ "albumId": "al-7", "artist": "artist-1", "artistId": "ar-2", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-7", @@ -253,6 +263,7 @@ "albumId": "al-7", "artist": "artist-1", "artistId": "ar-2", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-7", @@ -277,6 +288,7 @@ "albumId": "al-7", "artist": "artist-1", "artistId": "ar-2", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-7", @@ -301,6 +313,7 @@ "albumId": "al-8", "artist": "artist-1", "artistId": "ar-2", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-8", @@ -325,6 +338,7 @@ "albumId": "al-8", "artist": "artist-1", "artistId": "ar-2", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-8", @@ -349,6 +363,7 @@ "albumId": "al-8", "artist": "artist-1", "artistId": "ar-2", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-8", @@ -373,6 +388,7 @@ "albumId": "al-9", "artist": "artist-1", "artistId": "ar-2", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-9", @@ -397,6 +413,7 @@ "albumId": "al-9", "artist": "artist-1", "artistId": "ar-2", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-9", @@ -421,6 +438,7 @@ "albumId": "al-9", "artist": "artist-1", "artistId": "ar-2", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-9", @@ -445,6 +463,7 @@ "albumId": "al-11", "artist": "artist-2", "artistId": "ar-3", + "artists": [{ "id": "ar-3", "name": "artist-2" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-11", @@ -469,6 +488,7 @@ "albumId": "al-11", "artist": "artist-2", "artistId": "ar-3", + "artists": [{ "id": "ar-3", "name": "artist-2" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-11", diff --git a/server/ctrlsubsonic/testdata/test_search_two_q_tra b/server/ctrlsubsonic/testdata/test_search_two_q_tra index 6ea777e5..18e27d89 100644 --- a/server/ctrlsubsonic/testdata/test_search_two_q_tra +++ b/server/ctrlsubsonic/testdata/test_search_two_q_tra @@ -11,6 +11,7 @@ "id": "tr-1", "album": "album-0", "artist": "artist-0", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-3", @@ -31,6 +32,7 @@ "id": "tr-2", "album": "album-0", "artist": "artist-0", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-3", @@ -51,6 +53,7 @@ "id": "tr-3", "album": "album-0", "artist": "artist-0", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-3", @@ -71,6 +74,7 @@ "id": "tr-4", "album": "album-1", "artist": "artist-0", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-4", @@ -91,6 +95,7 @@ "id": "tr-5", "album": "album-1", "artist": "artist-0", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-4", @@ -111,6 +116,7 @@ "id": "tr-6", "album": "album-1", "artist": "artist-0", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-4", @@ -131,6 +137,7 @@ "id": "tr-7", "album": "album-2", "artist": "artist-0", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-5", @@ -151,6 +158,7 @@ "id": "tr-8", "album": "album-2", "artist": "artist-0", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-5", @@ -171,6 +179,7 @@ "id": "tr-9", "album": "album-2", "artist": "artist-0", + "artists": [{ "id": "ar-1", "name": "artist-0" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-5", @@ -191,6 +200,7 @@ "id": "tr-10", "album": "album-0", "artist": "artist-1", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-7", @@ -211,6 +221,7 @@ "id": "tr-11", "album": "album-0", "artist": "artist-1", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-7", @@ -231,6 +242,7 @@ "id": "tr-12", "album": "album-0", "artist": "artist-1", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-7", @@ -251,6 +263,7 @@ "id": "tr-13", "album": "album-1", "artist": "artist-1", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-8", @@ -271,6 +284,7 @@ "id": "tr-14", "album": "album-1", "artist": "artist-1", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-8", @@ -291,6 +305,7 @@ "id": "tr-15", "album": "album-1", "artist": "artist-1", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-8", @@ -311,6 +326,7 @@ "id": "tr-16", "album": "album-2", "artist": "artist-1", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-9", @@ -331,6 +347,7 @@ "id": "tr-17", "album": "album-2", "artist": "artist-1", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-9", @@ -351,6 +368,7 @@ "id": "tr-18", "album": "album-2", "artist": "artist-1", + "artists": [{ "id": "ar-2", "name": "artist-1" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-9", @@ -371,6 +389,7 @@ "id": "tr-19", "album": "album-0", "artist": "artist-2", + "artists": [{ "id": "ar-3", "name": "artist-2" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-11", @@ -391,6 +410,7 @@ "id": "tr-20", "album": "album-0", "artist": "artist-2", + "artists": [{ "id": "ar-3", "name": "artist-2" }], "bitRate": 100, "contentType": "audio/flac", "coverArt": "al-11", diff --git a/tags/tagcommon/tagcommmon.go b/tags/tagcommon/tagcommmon.go index df20553a..eb7b657c 100644 --- a/tags/tagcommon/tagcommmon.go +++ b/tags/tagcommon/tagcommmon.go @@ -15,6 +15,7 @@ type Info interface { Title() string BrainzID() string Artist() string + Artists() []string Album() string AlbumArtist() string AlbumArtists() []string @@ -42,6 +43,13 @@ func MustArtist(p Info) string { return "Unknown Artist" } +func MustArtists(p Info) []string { + if r := p.Artists(); len(r) > 0 { + return r + } + return []string{MustArtist(p)} +} + func MustAlbumArtist(p Info) string { if r := p.AlbumArtist(); r != "" { return r diff --git a/tags/taglib/taglib.go b/tags/taglib/taglib.go index 87a4c3f6..ff660165 100644 --- a/tags/taglib/taglib.go +++ b/tags/taglib/taglib.go @@ -34,6 +34,7 @@ type info struct { func (i *info) Title() string { return first(find(i.raw, "title")) } func (i *info) BrainzID() string { return first(find(i.raw, "musicbrainz_trackid")) } // musicbrainz recording ID func (i *info) Artist() string { return first(find(i.raw, "artist")) } +func (i *info) Artists() []string { return find(i.raw, "artists") } func (i *info) Album() string { return first(find(i.raw, "album")) } func (i *info) AlbumArtist() string { return first(find(i.raw, "albumartist", "album artist")) } func (i *info) AlbumArtists() []string { return find(i.raw, "albumartists", "album_artists") }