feat(subsonic): add groupings field to OpenSubsonic Child response

Include the ID3 grouping tag in OpenSubsonic responses as an array of
strings, per opensubsonic/open-subsonic-api#232. The grouping tag was
already being extracted and stored in MediaFile.Tags via mappings.yaml
aliases (GRP1, GROUPING, ©grp, wm/contentgroupdescription), so this
change only adds the field to the response struct and populates it in
both song and album child builders.

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan 2026-05-10 18:34:45 -03:00
parent 569de4cd23
commit f12e75aa11
10 changed files with 31 additions and 6 deletions

View file

@ -266,6 +266,7 @@ func osChildFromMediaFile(ctx context.Context, mf model.MediaFile) *responses.Op
child.BitDepth = int32(mf.BitDepth) child.BitDepth = int32(mf.BitDepth)
child.Genres = toItemGenres(mf.Genres) child.Genres = toItemGenres(mf.Genres)
child.Moods = mf.Tags.Values(model.TagMood) child.Moods = mf.Tags.Values(model.TagMood)
child.Groupings = mf.Tags.Values(model.TagGrouping)
child.DisplayArtist = mf.Artist child.DisplayArtist = mf.Artist
child.Artists = artistRefs(mf.Participants[model.RoleArtist]) child.Artists = artistRefs(mf.Participants[model.RoleArtist])
child.DisplayAlbumArtist = mf.AlbumArtist child.DisplayAlbumArtist = mf.AlbumArtist
@ -375,6 +376,7 @@ func osChildFromAlbum(ctx context.Context, al model.Album) *responses.OpenSubson
child.MusicBrainzId = al.MbzAlbumID child.MusicBrainzId = al.MbzAlbumID
child.Genres = toItemGenres(al.Genres) child.Genres = toItemGenres(al.Genres)
child.Moods = al.Tags.Values(model.TagMood) child.Moods = al.Tags.Values(model.TagMood)
child.Groupings = al.Tags.Values(model.TagGrouping)
child.DisplayArtist = al.AlbumArtist child.DisplayArtist = al.AlbumArtist
child.Artists = artistRefs(al.Participants[model.RoleAlbumArtist]) child.Artists = artistRefs(al.Participants[model.RoleAlbumArtist])
child.DisplayAlbumArtist = al.AlbumArtist child.DisplayAlbumArtist = al.AlbumArtist

View file

@ -56,7 +56,10 @@
"displayAlbumArtist": "Display album artist", "displayAlbumArtist": "Display album artist",
"contributors": [], "contributors": [],
"displayComposer": "", "displayComposer": "",
"explicitStatus": "explicit" "explicitStatus": "explicit",
"groupings": [
"Soundtrack"
]
} }
] ]
} }

View file

@ -9,6 +9,7 @@
<artists id="artist-2" name="Artist 2"></artists> <artists id="artist-2" name="Artist 2"></artists>
<albumArtists id="album-artist-1" name="Artist 1"></albumArtists> <albumArtists id="album-artist-1" name="Artist 1"></albumArtists>
<albumArtists id="album-artist-2" name="Artist 2"></albumArtists> <albumArtists id="album-artist-2" name="Artist 2"></albumArtists>
<groupings>Soundtrack</groupings>
</album> </album>
</albumList> </albumList>
</subsonic-response> </subsonic-response>

View file

@ -165,7 +165,11 @@
} }
], ],
"displayComposer": "composer 1 \u0026 composer 2", "displayComposer": "composer 1 \u0026 composer 2",
"explicitStatus": "clean" "explicitStatus": "clean",
"groupings": [
"Soundtrack",
"Live"
]
}, },
{ {
"id": "2", "id": "2",
@ -210,7 +214,8 @@
"displayAlbumArtist": "", "displayAlbumArtist": "",
"contributors": [], "contributors": [],
"displayComposer": "", "displayComposer": "",
"explicitStatus": "" "explicitStatus": "",
"groupings": []
} }
] ]
} }

View file

@ -32,6 +32,8 @@
<contributors role="role2" subRole="subrole4"> <contributors role="role2" subRole="subrole4">
<artist id="2" name="artist2"></artist> <artist id="2" name="artist2"></artist>
</contributors> </contributors>
<groupings>Soundtrack</groupings>
<groupings>Live</groupings>
</song> </song>
<song id="2" 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"> <song id="2" 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">
<replayGain trackGain="0" albumGain="0" trackPeak="0" albumPeak="0" baseGain="0" fallbackGain="0"></replayGain> <replayGain trackGain="0" albumGain="0" trackPeak="0" albumPeak="0" baseGain="0" fallbackGain="0"></replayGain>

View file

@ -110,7 +110,11 @@
} }
], ],
"displayComposer": "composer 1 \u0026 composer 2", "displayComposer": "composer 1 \u0026 composer 2",
"explicitStatus": "clean" "explicitStatus": "clean",
"groupings": [
"Soundtrack",
"Live"
]
}, },
{ {
"id": "", "id": "",
@ -141,7 +145,8 @@
"displayAlbumArtist": "", "displayAlbumArtist": "",
"contributors": [], "contributors": [],
"displayComposer": "", "displayComposer": "",
"explicitStatus": "" "explicitStatus": "",
"groupings": []
} }
], ],
"id": "1", "id": "1",

View file

@ -24,6 +24,8 @@
<contributors role="composer"> <contributors role="composer">
<artist id="4" name="composer2"></artist> <artist id="4" name="composer2"></artist>
</contributors> </contributors>
<groupings>Soundtrack</groupings>
<groupings>Live</groupings>
</child> </child>
<child id="" isDir="false" title=""> <child id="" isDir="false" title="">
<replayGain trackGain="0" albumGain="0" trackPeak="0" albumPeak="0" baseGain="0" fallbackGain="0"></replayGain> <replayGain trackGain="0" albumGain="0" trackPeak="0" albumPeak="0" baseGain="0" fallbackGain="0"></replayGain>

View file

@ -28,7 +28,8 @@
"displayAlbumArtist": "", "displayAlbumArtist": "",
"contributors": [], "contributors": [],
"displayComposer": "", "displayComposer": "",
"explicitStatus": "" "explicitStatus": "",
"groupings": []
} }
], ],
"id": "", "id": "",

View file

@ -189,6 +189,7 @@ type OpenSubsonicChild struct {
Contributors Array[Contributor] `xml:"contributors,omitempty" json:"contributors"` Contributors Array[Contributor] `xml:"contributors,omitempty" json:"contributors"`
DisplayComposer string `xml:"displayComposer,attr,omitempty" json:"displayComposer"` DisplayComposer string `xml:"displayComposer,attr,omitempty" json:"displayComposer"`
ExplicitStatus string `xml:"explicitStatus,attr,omitempty" json:"explicitStatus"` ExplicitStatus string `xml:"explicitStatus,attr,omitempty" json:"explicitStatus"`
Groupings Array[string] `xml:"groupings,omitempty" json:"groupings"`
} }
type Songs struct { type Songs struct {

View file

@ -224,6 +224,7 @@ var _ = Describe("Responses", func() {
Isrc: []string{"ISRC-1", "ISRC-2"}, Isrc: []string{"ISRC-1", "ISRC-2"},
BPM: 127, ChannelCount: 2, SamplingRate: 44100, BitDepth: 16, BPM: 127, ChannelCount: 2, SamplingRate: 44100, BitDepth: 16,
Moods: []string{"happy", "sad"}, Moods: []string{"happy", "sad"},
Groupings: []string{"Soundtrack", "Live"},
ReplayGain: ReplayGain{TrackGain: gg.P(1.0), AlbumGain: gg.P(2.0), TrackPeak: gg.P(3.0), AlbumPeak: gg.P(4.0), BaseGain: gg.P(5.0), FallbackGain: gg.P(6.0)}, ReplayGain: ReplayGain{TrackGain: gg.P(1.0), AlbumGain: gg.P(2.0), TrackPeak: gg.P(3.0), AlbumPeak: gg.P(4.0), BaseGain: gg.P(5.0), FallbackGain: gg.P(6.0)},
DisplayArtist: "artist 1 & artist 2", DisplayArtist: "artist 1 & artist 2",
Artists: []ArtistID3Ref{ Artists: []ArtistID3Ref{
@ -320,6 +321,7 @@ var _ = Describe("Responses", func() {
Comment: "a comment", MediaType: MediaTypeSong, MusicBrainzId: "4321", SortName: "sorted song", Comment: "a comment", MediaType: MediaTypeSong, MusicBrainzId: "4321", SortName: "sorted song",
Isrc: []string{"ISRC-1"}, Isrc: []string{"ISRC-1"},
Moods: []string{"happy", "sad"}, Moods: []string{"happy", "sad"},
Groupings: []string{"Soundtrack", "Live"},
ReplayGain: ReplayGain{TrackGain: gg.P(1.0), AlbumGain: gg.P(2.0), TrackPeak: gg.P(3.0), AlbumPeak: gg.P(4.0), BaseGain: gg.P(5.0), FallbackGain: gg.P(6.0)}, ReplayGain: ReplayGain{TrackGain: gg.P(1.0), AlbumGain: gg.P(2.0), TrackPeak: gg.P(3.0), AlbumPeak: gg.P(4.0), BaseGain: gg.P(5.0), FallbackGain: gg.P(6.0)},
BPM: 127, ChannelCount: 2, SamplingRate: 44100, BitDepth: 16, BPM: 127, ChannelCount: 2, SamplingRate: 44100, BitDepth: 16,
DisplayArtist: "artist1 & artist2", DisplayArtist: "artist1 & artist2",
@ -424,6 +426,7 @@ var _ = Describe("Responses", func() {
ItemGenre{Name: "Genre 2"}, ItemGenre{Name: "Genre 2"},
}, },
Moods: []string{"mood1", "mood2"}, Moods: []string{"mood1", "mood2"},
Groupings: []string{"Soundtrack"},
DisplayArtist: "Display artist", DisplayArtist: "Display artist",
Artists: Array[ArtistID3Ref]{ Artists: Array[ArtistID3Ref]{
ArtistID3Ref{Id: "artist-1", Name: "Artist 1"}, ArtistID3Ref{Id: "artist-1", Name: "Artist 1"},