From 77735228035c1c6c80c2eb23644b1a43a9285c84 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 18 Nov 2023 11:40:00 -0800 Subject: [PATCH] Expose OpenSubsonic fields Genres, MusicBrainzId, Bpm, Comment (#2597) * add Genres to subsonic responses * add genres in GetAlbum response * add musicBrainzId * add Bpm and Comment OpenSubsonic fields * remove omitempty on OpenSubsonic fields * add custom JSON marshalers to ensure genres attribute is non-nil * regenerate snapshots to capture now-mandatory fields --- server/subsonic/browsing.go | 2 + server/subsonic/helpers.go | 13 ++ ...ses AlbumList with data should match .JSON | 2 +- ...nses AlbumList with data should match .XML | 2 +- ...ses Bookmarks with data should match .JSON | 2 +- ...nses Bookmarks with data should match .XML | 2 +- ...sponses Child with data should match .JSON | 2 +- ...esponses Child with data should match .XML | 2 +- ...ses Directory with data should match .JSON | 2 +- ...nses Directory with data should match .XML | 2 +- ...ses PlayQueue with data should match .JSON | 2 +- ...nses PlayQueue with data should match .XML | 2 +- ...ponses Shares with data should match .JSON | 2 +- ...sponses Shares with data should match .XML | 2 +- ... SimilarSongs with data should match .JSON | 2 +- ...s SimilarSongs with data should match .XML | 2 +- ...SimilarSongs2 with data should match .JSON | 2 +- ... SimilarSongs2 with data should match .XML | 2 +- ...nses TopSongs with data should match .JSON | 2 +- ...onses TopSongs with data should match .XML | 2 +- server/subsonic/responses/responses.go | 130 ++++++++++++------ 21 files changed, 118 insertions(+), 63 deletions(-) diff --git a/server/subsonic/browsing.go b/server/subsonic/browsing.go index 53323db60..7c75c7427 100644 --- a/server/subsonic/browsing.go +++ b/server/subsonic/browsing.go @@ -423,6 +423,7 @@ func (api *Router) buildAlbum(ctx context.Context, album *model.Album, mfs model } dir.Year = int32(album.MaxYear) dir.Genre = album.Genre + dir.Genres = itemGenresFromGenres(album.Genres) dir.UserRating = int32(album.Rating) if !album.CreatedAt.IsZero() { dir.Created = &album.CreatedAt @@ -430,6 +431,7 @@ func (api *Router) buildAlbum(ctx context.Context, album *model.Album, mfs model if album.Starred { dir.Starred = &album.StarredAt } + dir.MusicBrainzId = album.MbzAlbumID dir.Song = childrenFromMediaFiles(ctx, mfs) return dir } diff --git a/server/subsonic/helpers.go b/server/subsonic/helpers.go index f7b13d10a..dd500c60c 100644 --- a/server/subsonic/helpers.go +++ b/server/subsonic/helpers.go @@ -110,6 +110,7 @@ func toArtistID3(r *http.Request, a model.Artist) responses.ArtistID3 { CoverArt: a.CoverArtID().String(), ArtistImageUrl: public.ImageURL(r, a.CoverArtID(), 600), UserRating: int32(a.Rating), + MusicBrainzId: a.MbzArtistID, } if a.Starred { artist.Starred = &a.StarredAt @@ -151,6 +152,7 @@ func childFromMediaFile(ctx context.Context, mf model.MediaFile) responses.Child child.Year = int32(mf.Year) child.Artist = mf.Artist child.Genre = mf.Genre + child.Genres = itemGenresFromGenres(mf.Genres) child.Track = int32(mf.TrackNumber) child.Duration = int32(mf.Duration) child.Size = mf.Size @@ -184,6 +186,8 @@ func childFromMediaFile(ctx context.Context, mf model.MediaFile) responses.Child child.TranscodedContentType = mime.TypeByExtension("." + format) } child.BookmarkPosition = mf.BookmarkPosition + child.Comment = mf.Comment + child.Bpm = int32(mf.Bpm) return child } @@ -217,6 +221,7 @@ func childFromAlbum(_ context.Context, al model.Album) responses.Child { child.Artist = al.AlbumArtist child.Year = int32(al.MaxYear) child.Genre = al.Genre + child.Genres = itemGenresFromGenres(al.Genres) child.CoverArt = al.CoverArtID().String() child.Created = &al.CreatedAt child.Parent = al.AlbumArtistID @@ -241,3 +246,11 @@ func childrenFromAlbums(ctx context.Context, als model.Albums) []responses.Child } return children } + +func itemGenresFromGenres(genres model.Genres) []responses.ItemGenre { + itemGenres := make([]responses.ItemGenre, len(genres)) + for i, g := range genres { + itemGenres[i] = responses.ItemGenre{Name: g.Name} + } + return itemGenres +} diff --git a/server/subsonic/responses/.snapshots/Responses AlbumList with data should match .JSON b/server/subsonic/responses/.snapshots/Responses AlbumList with data should match .JSON index 40c691954..e0e812900 100644 --- a/server/subsonic/responses/.snapshots/Responses AlbumList with data should match .JSON +++ b/server/subsonic/responses/.snapshots/Responses AlbumList with data should match .JSON @@ -1 +1 @@ -{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"albumList":{"album":[{"id":"1","isDir":false,"title":"title","isVideo":false}]}} +{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"albumList":{"album":[{"id":"1","isDir":false,"title":"title","genres":[],"isVideo":false,"bpm":0,"comment":""}]}} diff --git a/server/subsonic/responses/.snapshots/Responses AlbumList with data should match .XML b/server/subsonic/responses/.snapshots/Responses AlbumList with data should match .XML index e4f61a704..d6d09ace3 100644 --- a/server/subsonic/responses/.snapshots/Responses AlbumList with data should match .XML +++ b/server/subsonic/responses/.snapshots/Responses AlbumList with data should match .XML @@ -1 +1 @@ - + diff --git a/server/subsonic/responses/.snapshots/Responses Bookmarks with data should match .JSON b/server/subsonic/responses/.snapshots/Responses Bookmarks with data should match .JSON index 602279801..8eb6dd66f 100644 --- a/server/subsonic/responses/.snapshots/Responses Bookmarks with data should match .JSON +++ b/server/subsonic/responses/.snapshots/Responses Bookmarks with data should match .JSON @@ -1 +1 @@ -{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"bookmarks":{"bookmark":[{"entry":{"id":"1","isDir":false,"title":"title","isVideo":false},"position":123,"username":"user2","comment":"a comment","created":"0001-01-01T00:00:00Z","changed":"0001-01-01T00:00:00Z"}]}} +{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"bookmarks":{"bookmark":[{"entry":{"id":"1","isDir":false,"title":"title","genres":[],"isVideo":false,"bpm":0,"comment":""},"position":123,"username":"user2","comment":"a comment","created":"0001-01-01T00:00:00Z","changed":"0001-01-01T00:00:00Z"}]}} diff --git a/server/subsonic/responses/.snapshots/Responses Bookmarks with data should match .XML b/server/subsonic/responses/.snapshots/Responses Bookmarks with data should match .XML index 080d4fc48..ba1c967d1 100644 --- a/server/subsonic/responses/.snapshots/Responses Bookmarks with data should match .XML +++ b/server/subsonic/responses/.snapshots/Responses Bookmarks with data should match .XML @@ -1 +1 @@ - + diff --git a/server/subsonic/responses/.snapshots/Responses Child with data should match .JSON b/server/subsonic/responses/.snapshots/Responses Child with data should match .JSON index 0444d8e7c..8e1cf0d29 100644 --- a/server/subsonic/responses/.snapshots/Responses Child with data should match .JSON +++ b/server/subsonic/responses/.snapshots/Responses Child with data should match .JSON @@ -1 +1 @@ -{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"directory":{"child":[{"id":"1","isDir":true,"title":"title","album":"album","artist":"artist","track":1,"year":1985,"genre":"Rock","coverArt":"1","size":8421341,"contentType":"audio/flac","suffix":"flac","starred":"2016-03-02T20:30:00Z","transcodedContentType":"audio/mpeg","transcodedSuffix":"mp3","duration":146,"bitRate":320,"isVideo":false}],"id":"1","name":"N"}} +{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"directory":{"child":[{"id":"1","isDir":true,"title":"title","album":"album","artist":"artist","track":1,"year":1985,"genre":"Rock","genres":[],"coverArt":"1","size":8421341,"contentType":"audio/flac","suffix":"flac","starred":"2016-03-02T20:30:00Z","transcodedContentType":"audio/mpeg","transcodedSuffix":"mp3","duration":146,"bitRate":320,"isVideo":false,"bpm":0,"comment":""}],"id":"1","name":"N"}} diff --git a/server/subsonic/responses/.snapshots/Responses Child with data should match .XML b/server/subsonic/responses/.snapshots/Responses Child with data should match .XML index 1ce29024f..07d28bb5e 100644 --- a/server/subsonic/responses/.snapshots/Responses Child with data should match .XML +++ b/server/subsonic/responses/.snapshots/Responses Child with data should match .XML @@ -1 +1 @@ - + diff --git a/server/subsonic/responses/.snapshots/Responses Directory with data should match .JSON b/server/subsonic/responses/.snapshots/Responses Directory with data should match .JSON index 7b3f7de85..7cb1c7403 100644 --- a/server/subsonic/responses/.snapshots/Responses Directory with data should match .JSON +++ b/server/subsonic/responses/.snapshots/Responses Directory with data should match .JSON @@ -1 +1 @@ -{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"directory":{"child":[{"id":"1","isDir":false,"title":"title","isVideo":false}],"id":"1","name":"N"}} +{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"directory":{"child":[{"id":"1","isDir":false,"title":"title","genres":[],"isVideo":false,"bpm":0,"comment":""}],"id":"1","name":"N"}} diff --git a/server/subsonic/responses/.snapshots/Responses Directory with data should match .XML b/server/subsonic/responses/.snapshots/Responses Directory with data should match .XML index b11f3fce6..fb87a2d97 100644 --- a/server/subsonic/responses/.snapshots/Responses Directory with data should match .XML +++ b/server/subsonic/responses/.snapshots/Responses Directory with data should match .XML @@ -1 +1 @@ - + diff --git a/server/subsonic/responses/.snapshots/Responses PlayQueue with data should match .JSON b/server/subsonic/responses/.snapshots/Responses PlayQueue with data should match .JSON index 2603112b5..b9dba55c9 100644 --- a/server/subsonic/responses/.snapshots/Responses PlayQueue with data should match .JSON +++ b/server/subsonic/responses/.snapshots/Responses PlayQueue with data should match .JSON @@ -1 +1 @@ -{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"playQueue":{"entry":[{"id":"1","isDir":false,"title":"title","isVideo":false}],"current":"111","position":243,"username":"user1","changed":"0001-01-01T00:00:00Z","changedBy":"a_client"}} +{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"playQueue":{"entry":[{"id":"1","isDir":false,"title":"title","genres":[],"isVideo":false,"bpm":0,"comment":""}],"current":"111","position":243,"username":"user1","changed":"0001-01-01T00:00:00Z","changedBy":"a_client"}} diff --git a/server/subsonic/responses/.snapshots/Responses PlayQueue with data should match .XML b/server/subsonic/responses/.snapshots/Responses PlayQueue with data should match .XML index d96030df5..defcd4f58 100644 --- a/server/subsonic/responses/.snapshots/Responses PlayQueue with data should match .XML +++ b/server/subsonic/responses/.snapshots/Responses PlayQueue with data should match .XML @@ -1 +1 @@ - + diff --git a/server/subsonic/responses/.snapshots/Responses Shares with data should match .JSON b/server/subsonic/responses/.snapshots/Responses Shares with data should match .JSON index e35b2729b..0eac71e08 100644 --- a/server/subsonic/responses/.snapshots/Responses Shares with data should match .JSON +++ b/server/subsonic/responses/.snapshots/Responses Shares with data should match .JSON @@ -1 +1 @@ -{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"shares":{"share":[{"entry":[{"id":"1","isDir":false,"title":"title","album":"album","artist":"artist","duration":120,"isVideo":false},{"id":"2","isDir":false,"title":"title 2","album":"album","artist":"artist","duration":300,"isVideo":false}],"id":"ABC123","url":"http://localhost/p/ABC123","description":"Check it out!","username":"deluan","created":"0001-01-01T00:00:00Z","expires":"0001-01-01T00:00:00Z","lastVisited":"0001-01-01T00:00:00Z","visitCount":2}]}} +{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"shares":{"share":[{"entry":[{"id":"1","isDir":false,"title":"title","album":"album","artist":"artist","genres":[],"duration":120,"isVideo":false,"bpm":0,"comment":""},{"id":"2","isDir":false,"title":"title 2","album":"album","artist":"artist","genres":[],"duration":300,"isVideo":false,"bpm":0,"comment":""}],"id":"ABC123","url":"http://localhost/p/ABC123","description":"Check it out!","username":"deluan","created":"0001-01-01T00:00:00Z","expires":"0001-01-01T00:00:00Z","lastVisited":"0001-01-01T00:00:00Z","visitCount":2}]}} diff --git a/server/subsonic/responses/.snapshots/Responses Shares with data should match .XML b/server/subsonic/responses/.snapshots/Responses Shares with data should match .XML index 4fa5cda16..9a628c66b 100644 --- a/server/subsonic/responses/.snapshots/Responses Shares with data should match .XML +++ b/server/subsonic/responses/.snapshots/Responses Shares with data should match .XML @@ -1 +1 @@ - + diff --git a/server/subsonic/responses/.snapshots/Responses SimilarSongs with data should match .JSON b/server/subsonic/responses/.snapshots/Responses SimilarSongs with data should match .JSON index 1a09565e5..a67febe7f 100644 --- a/server/subsonic/responses/.snapshots/Responses SimilarSongs with data should match .JSON +++ b/server/subsonic/responses/.snapshots/Responses SimilarSongs with data should match .JSON @@ -1 +1 @@ -{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"similarSongs":{"song":[{"id":"1","isDir":false,"title":"title","isVideo":false}]}} +{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"similarSongs":{"song":[{"id":"1","isDir":false,"title":"title","genres":[],"isVideo":false,"bpm":0,"comment":""}]}} diff --git a/server/subsonic/responses/.snapshots/Responses SimilarSongs with data should match .XML b/server/subsonic/responses/.snapshots/Responses SimilarSongs with data should match .XML index f0a2e0163..39429cbea 100644 --- a/server/subsonic/responses/.snapshots/Responses SimilarSongs with data should match .XML +++ b/server/subsonic/responses/.snapshots/Responses SimilarSongs with data should match .XML @@ -1 +1 @@ - + diff --git a/server/subsonic/responses/.snapshots/Responses SimilarSongs2 with data should match .JSON b/server/subsonic/responses/.snapshots/Responses SimilarSongs2 with data should match .JSON index 2236dc35b..7887668a2 100644 --- a/server/subsonic/responses/.snapshots/Responses SimilarSongs2 with data should match .JSON +++ b/server/subsonic/responses/.snapshots/Responses SimilarSongs2 with data should match .JSON @@ -1 +1 @@ -{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"similarSongs2":{"song":[{"id":"1","isDir":false,"title":"title","isVideo":false}]}} +{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"similarSongs2":{"song":[{"id":"1","isDir":false,"title":"title","genres":[],"isVideo":false,"bpm":0,"comment":""}]}} diff --git a/server/subsonic/responses/.snapshots/Responses SimilarSongs2 with data should match .XML b/server/subsonic/responses/.snapshots/Responses SimilarSongs2 with data should match .XML index 090a59d73..1140d0985 100644 --- a/server/subsonic/responses/.snapshots/Responses SimilarSongs2 with data should match .XML +++ b/server/subsonic/responses/.snapshots/Responses SimilarSongs2 with data should match .XML @@ -1 +1 @@ - + diff --git a/server/subsonic/responses/.snapshots/Responses TopSongs with data should match .JSON b/server/subsonic/responses/.snapshots/Responses TopSongs with data should match .JSON index 889707ae2..6e5ea0992 100644 --- a/server/subsonic/responses/.snapshots/Responses TopSongs with data should match .JSON +++ b/server/subsonic/responses/.snapshots/Responses TopSongs with data should match .JSON @@ -1 +1 @@ -{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"topSongs":{"song":[{"id":"1","isDir":false,"title":"title","isVideo":false}]}} +{"status":"ok","version":"1.8.0","type":"navidrome","serverVersion":"v0.0.0","openSubsonic":true,"topSongs":{"song":[{"id":"1","isDir":false,"title":"title","genres":[],"isVideo":false,"bpm":0,"comment":""}]}} diff --git a/server/subsonic/responses/.snapshots/Responses TopSongs with data should match .XML b/server/subsonic/responses/.snapshots/Responses TopSongs with data should match .XML index bda5a0a60..34c8b82ef 100644 --- a/server/subsonic/responses/.snapshots/Responses TopSongs with data should match .XML +++ b/server/subsonic/responses/.snapshots/Responses TopSongs with data should match .XML @@ -1 +1 @@ - + diff --git a/server/subsonic/responses/responses.go b/server/subsonic/responses/responses.go index 495b7b2b0..8b48add8d 100644 --- a/server/subsonic/responses/responses.go +++ b/server/subsonic/responses/responses.go @@ -1,6 +1,7 @@ package responses import ( + "encoding/json" "encoding/xml" "time" ) @@ -106,42 +107,59 @@ type Indexes struct { } type Child struct { - Id string `xml:"id,attr" json:"id"` - Parent string `xml:"parent,attr,omitempty" json:"parent,omitempty"` - IsDir bool `xml:"isDir,attr" json:"isDir"` - Title string `xml:"title,attr,omitempty" json:"title,omitempty"` - Name string `xml:"name,attr,omitempty" json:"name,omitempty"` - Album string `xml:"album,attr,omitempty" json:"album,omitempty"` - Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"` - Track int32 `xml:"track,attr,omitempty" json:"track,omitempty"` - Year int32 `xml:"year,attr,omitempty" json:"year,omitempty"` - Genre string `xml:"genre,attr,omitempty" json:"genre,omitempty"` - CoverArt string `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty"` - Size int64 `xml:"size,attr,omitempty" json:"size,omitempty"` - ContentType string `xml:"contentType,attr,omitempty" json:"contentType,omitempty"` - Suffix string `xml:"suffix,attr,omitempty" json:"suffix,omitempty"` - Starred *time.Time `xml:"starred,attr,omitempty" json:"starred,omitempty"` - TranscodedContentType string `xml:"transcodedContentType,attr,omitempty" json:"transcodedContentType,omitempty"` - TranscodedSuffix string `xml:"transcodedSuffix,attr,omitempty" json:"transcodedSuffix,omitempty"` - Duration int32 `xml:"duration,attr,omitempty" json:"duration,omitempty"` - BitRate int32 `xml:"bitRate,attr,omitempty" json:"bitRate,omitempty"` - Path string `xml:"path,attr,omitempty" json:"path,omitempty"` - PlayCount int64 `xml:"playCount,attr,omitempty" json:"playCount,omitempty"` - Played *time.Time `xml:"played,attr,omitempty" json:"played,omitempty"` - DiscNumber int32 `xml:"discNumber,attr,omitempty" json:"discNumber,omitempty"` - Created *time.Time `xml:"created,attr,omitempty" json:"created,omitempty"` - AlbumId string `xml:"albumId,attr,omitempty" json:"albumId,omitempty"` - ArtistId string `xml:"artistId,attr,omitempty" json:"artistId,omitempty"` - Type string `xml:"type,attr,omitempty" json:"type,omitempty"` - UserRating int32 `xml:"userRating,attr,omitempty" json:"userRating,omitempty"` - SongCount int32 `xml:"songCount,attr,omitempty" json:"songCount,omitempty"` - IsVideo bool `xml:"isVideo,attr" json:"isVideo"` - BookmarkPosition int64 `xml:"bookmarkPosition,attr,omitempty" json:"bookmarkPosition,omitempty"` + Id string `xml:"id,attr" json:"id"` + Parent string `xml:"parent,attr,omitempty" json:"parent,omitempty"` + IsDir bool `xml:"isDir,attr" json:"isDir"` + Title string `xml:"title,attr,omitempty" json:"title,omitempty"` + Name string `xml:"name,attr,omitempty" json:"name,omitempty"` + Album string `xml:"album,attr,omitempty" json:"album,omitempty"` + Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"` + Track int32 `xml:"track,attr,omitempty" json:"track,omitempty"` + Year int32 `xml:"year,attr,omitempty" json:"year,omitempty"` + Genre string `xml:"genre,attr,omitempty" json:"genre,omitempty"` + Genres []ItemGenre `xml:"genres" json:"genres"` + CoverArt string `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty"` + Size int64 `xml:"size,attr,omitempty" json:"size,omitempty"` + ContentType string `xml:"contentType,attr,omitempty" json:"contentType,omitempty"` + Suffix string `xml:"suffix,attr,omitempty" json:"suffix,omitempty"` + Starred *time.Time `xml:"starred,attr,omitempty" json:"starred,omitempty"` + TranscodedContentType string `xml:"transcodedContentType,attr,omitempty" json:"transcodedContentType,omitempty"` + TranscodedSuffix string `xml:"transcodedSuffix,attr,omitempty" json:"transcodedSuffix,omitempty"` + Duration int32 `xml:"duration,attr,omitempty" json:"duration,omitempty"` + BitRate int32 `xml:"bitRate,attr,omitempty" json:"bitRate,omitempty"` + Path string `xml:"path,attr,omitempty" json:"path,omitempty"` + PlayCount int64 `xml:"playCount,attr,omitempty" json:"playCount,omitempty"` + Played *time.Time `xml:"played,attr,omitempty" json:"played,omitempty"` + DiscNumber int32 `xml:"discNumber,attr,omitempty" json:"discNumber,omitempty"` + Created *time.Time `xml:"created,attr,omitempty" json:"created,omitempty"` + AlbumId string `xml:"albumId,attr,omitempty" json:"albumId,omitempty"` + ArtistId string `xml:"artistId,attr,omitempty" json:"artistId,omitempty"` + Type string `xml:"type,attr,omitempty" json:"type,omitempty"` + UserRating int32 `xml:"userRating,attr,omitempty" json:"userRating,omitempty"` + SongCount int32 `xml:"songCount,attr,omitempty" json:"songCount,omitempty"` + IsVideo bool `xml:"isVideo,attr" json:"isVideo"` + BookmarkPosition int64 `xml:"bookmarkPosition,attr,omitempty" json:"bookmarkPosition,omitempty"` + Bpm int32 `xml:"bpm,attr" json:"bpm"` + Comment string `xml:"comment,attr" json:"comment"` /* */ } +func (c Child) MarshalJSON() ([]byte, error) { + // ensure fields that need custom default values are set properly + type Alias Child + a := struct { + Alias + }{ + Alias: (Alias)(c), + } + if a.Genres == nil { + a.Genres = make([]ItemGenre, 0) + } + return json.Marshal(a) +} + type Songs struct { Songs []Child `xml:"song" json:"song,omitempty"` } @@ -180,23 +198,40 @@ type ArtistID3 struct { Starred *time.Time `xml:"starred,attr,omitempty" json:"starred,omitempty"` UserRating int32 `xml:"userRating,attr,omitempty" json:"userRating,omitempty"` ArtistImageUrl string `xml:"artistImageUrl,attr,omitempty" json:"artistImageUrl,omitempty"` + MusicBrainzId string `xml:"musicBrainzId,attr,omitempty" json:"musicBrainzId,omitempty"` } type AlbumID3 struct { - Id string `xml:"id,attr" json:"id"` - Name string `xml:"name,attr" json:"name"` - Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"` - ArtistId string `xml:"artistId,attr,omitempty" json:"artistId,omitempty"` - CoverArt string `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty"` - SongCount int32 `xml:"songCount,attr,omitempty" json:"songCount,omitempty"` - Duration int32 `xml:"duration,attr,omitempty" json:"duration,omitempty"` - PlayCount int64 `xml:"playCount,attr,omitempty" json:"playCount,omitempty"` - Played *time.Time `xml:"played,attr,omitempty" json:"played,omitempty"` - Created *time.Time `xml:"created,attr,omitempty" json:"created,omitempty"` - Starred *time.Time `xml:"starred,attr,omitempty" json:"starred,omitempty"` - UserRating int32 `xml:"userRating,attr,omitempty" json:"userRating,omitempty"` - Year int32 `xml:"year,attr,omitempty" json:"year,omitempty"` - Genre string `xml:"genre,attr,omitempty" json:"genre,omitempty"` + Id string `xml:"id,attr" json:"id"` + Name string `xml:"name,attr" json:"name"` + Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"` + ArtistId string `xml:"artistId,attr,omitempty" json:"artistId,omitempty"` + CoverArt string `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty"` + SongCount int32 `xml:"songCount,attr,omitempty" json:"songCount,omitempty"` + Duration int32 `xml:"duration,attr,omitempty" json:"duration,omitempty"` + PlayCount int64 `xml:"playCount,attr,omitempty" json:"playCount,omitempty"` + Played *time.Time `xml:"played,attr,omitempty" json:"played,omitempty"` + Created *time.Time `xml:"created,attr,omitempty" json:"created,omitempty"` + Starred *time.Time `xml:"starred,attr,omitempty" json:"starred,omitempty"` + UserRating int32 `xml:"userRating,attr" json:"userRating"` + Year int32 `xml:"year,attr,omitempty" json:"year,omitempty"` + Genre string `xml:"genre,attr,omitempty" json:"genre,omitempty"` + Genres []ItemGenre `xml:"genres" json:"genres"` + MusicBrainzId string `xml:"musicBrainzId,attr" json:"musicBrainzId"` +} + +func (a AlbumID3) MarshalJSON() ([]byte, error) { + // ensure fields that need custom default values are set properly + type Alias AlbumID3 + x := struct { + Alias + }{ + Alias: (Alias)(a), + } + if x.Genres == nil { + x.Genres = make([]ItemGenre, 0) + } + return json.Marshal(x) } type ArtistWithAlbumsID3 struct { @@ -418,3 +453,8 @@ type JukeboxPlaylist struct { Entry []Child `xml:"entry,omitempty" json:"entry,omitempty"` } type OpenSubsonicExtensions struct{} + +// OpenSubsonic response type for multi-valued genres list +type ItemGenre struct { + Name string `xml:"name,attr" json:"name"` +}