fix: Allow nullable ReplayGain and support 0.0 (#4239)

* fix(ui,scanner,subsonic): Allow nullable replaygain and support 0.0

Resolves #4236.

Makes the replaygain columns (track/album gain/peak) nullable.
Converts the type to a pointer, allowing for 0.0 (a valid value) to be returned from Subsonic.
Updates tests for this behavior.

* small refactor

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Kendall Garner
2025-06-17 16:02:25 +00:00
committed by GitHub
parent 4359adc042
commit 7640c474cf
17 changed files with 279 additions and 96 deletions
+24
View File
@@ -8,6 +8,7 @@ import (
"github.com/djherbis/times" "github.com/djherbis/times"
"github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/metadata" "github.com/navidrome/navidrome/model/metadata"
"github.com/navidrome/navidrome/utils/gg"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
@@ -82,6 +83,29 @@ var _ = Describe("Extractor", func() {
e = &extractor{} e = &extractor{}
}) })
Describe("ReplayGain", func() {
DescribeTable("test replaygain end-to-end", func(file string, trackGain, trackPeak, albumGain, albumPeak *float64) {
path := "tests/fixtures/" + file
mds, err := e.Parse(path)
Expect(err).ToNot(HaveOccurred())
info := mds[path]
fileInfo, _ := os.Stat(path)
info.FileInfo = testFileInfo{FileInfo: fileInfo}
metadata := metadata.New(path, info)
mf := metadata.ToMediaFile(1, "folderID")
Expect(mf.RGTrackGain).To(Equal(trackGain))
Expect(mf.RGTrackPeak).To(Equal(trackPeak))
Expect(mf.RGAlbumGain).To(Equal(albumGain))
Expect(mf.RGAlbumPeak).To(Equal(albumPeak))
},
Entry("mp3 with no replaygain", "no_replaygain.mp3", nil, nil, nil, nil),
Entry("mp3 with no zero replaygain", "zero_replaygain.mp3", gg.P(0.0), gg.P(1.0), gg.P(0.0), gg.P(1.0)),
)
})
Describe("Participants", func() { Describe("Participants", func() {
DescribeTable("test tags consistent across formats", func(format string) { DescribeTable("test tags consistent across formats", func(format string) {
path := "tests/fixtures/test." + format path := "tests/fixtures/test." + format
@@ -0,0 +1,49 @@
package migrations
import (
"context"
"database/sql"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigrationContext(upMakeReplaygainFieldsNullable, downMakeReplaygainFieldsNullable)
}
func upMakeReplaygainFieldsNullable(ctx context.Context, tx *sql.Tx) error {
_, err := tx.ExecContext(ctx, `
ALTER TABLE media_file ADD COLUMN rg_album_gain_new real;
ALTER TABLE media_file ADD COLUMN rg_album_peak_new real;
ALTER TABLE media_file ADD COLUMN rg_track_gain_new real;
ALTER TABLE media_file ADD COLUMN rg_track_peak_new real;
UPDATE media_file SET
rg_album_gain_new = rg_album_gain,
rg_album_peak_new = rg_album_peak,
rg_track_gain_new = rg_track_gain,
rg_track_peak_new = rg_track_peak;
ALTER TABLE media_file DROP COLUMN rg_album_gain;
ALTER TABLE media_file DROP COLUMN rg_album_peak;
ALTER TABLE media_file DROP COLUMN rg_track_gain;
ALTER TABLE media_file DROP COLUMN rg_track_peak;
ALTER TABLE media_file RENAME COLUMN rg_album_gain_new TO rg_album_gain;
ALTER TABLE media_file RENAME COLUMN rg_album_peak_new TO rg_album_peak;
ALTER TABLE media_file RENAME COLUMN rg_track_gain_new TO rg_track_gain;
ALTER TABLE media_file RENAME COLUMN rg_track_peak_new TO rg_track_peak;
`)
if err != nil {
return err
}
notice(tx, "Fetching replaygain fields properly will require a full scan")
return nil
}
func downMakeReplaygainFieldsNullable(ctx context.Context, tx *sql.Tx) error {
// This code is executed when the migration is rolled back.
return nil
}
+4 -4
View File
@@ -79,10 +79,10 @@ type MediaFile struct {
MbzAlbumArtistID string `structs:"mbz_album_artist_id" json:"mbzAlbumArtistId,omitempty"` // Deprecated: Use Participants instead MbzAlbumArtistID string `structs:"mbz_album_artist_id" json:"mbzAlbumArtistId,omitempty"` // Deprecated: Use Participants instead
MbzAlbumType string `structs:"mbz_album_type" json:"mbzAlbumType,omitempty"` MbzAlbumType string `structs:"mbz_album_type" json:"mbzAlbumType,omitempty"`
MbzAlbumComment string `structs:"mbz_album_comment" json:"mbzAlbumComment,omitempty"` MbzAlbumComment string `structs:"mbz_album_comment" json:"mbzAlbumComment,omitempty"`
RGAlbumGain float64 `structs:"rg_album_gain" json:"rgAlbumGain"` RGAlbumGain *float64 `structs:"rg_album_gain" json:"rgAlbumGain"`
RGAlbumPeak float64 `structs:"rg_album_peak" json:"rgAlbumPeak"` RGAlbumPeak *float64 `structs:"rg_album_peak" json:"rgAlbumPeak"`
RGTrackGain float64 `structs:"rg_track_gain" json:"rgTrackGain"` RGTrackGain *float64 `structs:"rg_track_gain" json:"rgTrackGain"`
RGTrackPeak float64 `structs:"rg_track_peak" json:"rgTrackPeak"` RGTrackPeak *float64 `structs:"rg_track_peak" json:"rgTrackPeak"`
Tags Tags `structs:"tags" json:"tags,omitempty" hash:"ignore"` // All imported tags from the original file Tags Tags `structs:"tags" json:"tags,omitempty" hash:"ignore"` // All imported tags from the original file
Participants Participants `structs:"participants" json:"participants" hash:"ignore"` // All artists that participated in this track Participants Participants `structs:"participants" json:"participants" hash:"ignore"` // All artists that participated in this track
+9 -8
View File
@@ -53,9 +53,9 @@ func (md Metadata) ToMediaFile(libID int, folderID string) model.MediaFile {
mf.MbzAlbumType = md.String(model.TagReleaseType) mf.MbzAlbumType = md.String(model.TagReleaseType)
// ReplayGain // ReplayGain
mf.RGAlbumPeak = md.Float(model.TagReplayGainAlbumPeak, 1) mf.RGAlbumPeak = md.NullableFloat(model.TagReplayGainAlbumPeak)
mf.RGAlbumGain = md.mapGain(model.TagReplayGainAlbumGain, model.TagR128AlbumGain) mf.RGAlbumGain = md.mapGain(model.TagReplayGainAlbumGain, model.TagR128AlbumGain)
mf.RGTrackPeak = md.Float(model.TagReplayGainTrackPeak, 1) mf.RGTrackPeak = md.NullableFloat(model.TagReplayGainTrackPeak)
mf.RGTrackGain = md.mapGain(model.TagReplayGainTrackGain, model.TagR128TrackGain) mf.RGTrackGain = md.mapGain(model.TagReplayGainTrackGain, model.TagR128TrackGain)
// General properties // General properties
@@ -108,23 +108,24 @@ func (md Metadata) AlbumID(mf model.MediaFile, pidConf string) string {
return getPID(mf, md, pidConf) return getPID(mf, md, pidConf)
} }
func (md Metadata) mapGain(rg, r128 model.TagName) float64 { func (md Metadata) mapGain(rg, r128 model.TagName) *float64 {
v := md.Gain(rg) v := md.Gain(rg)
if v != 0 { if v != nil {
return v return v
} }
r128value := md.String(r128) r128value := md.String(r128)
if r128value != "" { if r128value != "" {
var v, err = strconv.Atoi(r128value) var v, err = strconv.Atoi(r128value)
if err != nil { if err != nil {
return 0 return nil
} }
// Convert Q7.8 to float // Convert Q7.8 to float
var value = float64(v) / 256.0 value := float64(v) / 256.0
// Adding 5 dB to normalize with ReplayGain level // Adding 5 dB to normalize with ReplayGain level
return value + 5 value += 5
return &value
} }
return 0 return nil
} }
func (md Metadata) mapLyrics() string { func (md Metadata) mapLyrics() string {
+15 -5
View File
@@ -103,9 +103,11 @@ func (md Metadata) NumAndTotal(key model.TagName) (int, int) { return md.tuple(k
func (md Metadata) Float(key model.TagName, def ...float64) float64 { func (md Metadata) Float(key model.TagName, def ...float64) float64 {
return float(md.first(key), def...) return float(md.first(key), def...)
} }
func (md Metadata) Gain(key model.TagName) float64 { func (md Metadata) NullableFloat(key model.TagName) *float64 { return nullableFloat(md.first(key)) }
func (md Metadata) Gain(key model.TagName) *float64 {
v := strings.TrimSpace(strings.Replace(md.first(key), "dB", "", 1)) v := strings.TrimSpace(strings.Replace(md.first(key), "dB", "", 1))
return float(v) return nullableFloat(v)
} }
func (md Metadata) Pairs(key model.TagName) []Pair { func (md Metadata) Pairs(key model.TagName) []Pair {
values := md.tags[key] values := md.tags[key]
@@ -119,14 +121,22 @@ func (md Metadata) first(key model.TagName) string {
} }
func float(value string, def ...float64) float64 { func float(value string, def ...float64) float64 {
v, err := strconv.ParseFloat(value, 64) v := nullableFloat(value)
if err != nil || v == math.Inf(-1) || math.IsInf(v, 1) || math.IsNaN(v) { if v != nil {
return *v
}
if len(def) > 0 { if len(def) > 0 {
return def[0] return def[0]
} }
return 0 return 0
}
func nullableFloat(value string) *float64 {
v, err := strconv.ParseFloat(value, 64)
if err != nil || v == math.Inf(-1) || math.IsInf(v, 1) || math.IsNaN(v) {
return nil
} }
return v return &v
} }
// Used for tracks and discs // Used for tracks and discs
+20 -18
View File
@@ -8,6 +8,7 @@ import (
"github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/metadata" "github.com/navidrome/navidrome/model/metadata"
"github.com/navidrome/navidrome/utils" "github.com/navidrome/navidrome/utils"
"github.com/navidrome/navidrome/utils/gg"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
@@ -257,38 +258,39 @@ var _ = Describe("Metadata", func() {
} }
DescribeTable("Gain", DescribeTable("Gain",
func(tagValue string, expected float64) { func(tagValue string, expected *float64) {
mf := createMF("replaygain_track_gain", tagValue) mf := createMF("replaygain_track_gain", tagValue)
Expect(mf.RGTrackGain).To(Equal(expected)) Expect(mf.RGTrackGain).To(Equal(expected))
}, },
Entry("0", "0", 0.0), Entry("0", "0", gg.P(0.0)),
Entry("1.2dB", "1.2dB", 1.2), Entry("1.2dB", "1.2dB", gg.P(1.2)),
Entry("Infinity", "Infinity", 0.0), Entry("Infinity", "Infinity", nil),
Entry("Invalid value", "INVALID VALUE", 0.0), Entry("Invalid value", "INVALID VALUE", nil),
Entry("NaN", "NaN", 0.0), Entry("NaN", "NaN", nil),
) )
DescribeTable("Peak", DescribeTable("Peak",
func(tagValue string, expected float64) { func(tagValue string, expected *float64) {
mf := createMF("replaygain_track_peak", tagValue) mf := createMF("replaygain_track_peak", tagValue)
Expect(mf.RGTrackPeak).To(Equal(expected)) Expect(mf.RGTrackPeak).To(Equal(expected))
}, },
Entry("0", "0", 0.0), Entry("0", "0", gg.P(0.0)),
Entry("0.5", "0.5", 0.5), Entry("1.0", "1.0", gg.P(1.0)),
Entry("Invalid dB suffix", "0.7dB", 1.0), Entry("0.5", "0.5", gg.P(0.5)),
Entry("Infinity", "Infinity", 1.0), Entry("Invalid dB suffix", "0.7dB", nil),
Entry("Invalid value", "INVALID VALUE", 1.0), Entry("Infinity", "Infinity", nil),
Entry("NaN", "NaN", 1.0), Entry("Invalid value", "INVALID VALUE", nil),
Entry("NaN", "NaN", nil),
) )
DescribeTable("getR128GainValue", DescribeTable("getR128GainValue",
func(tagValue string, expected float64) { func(tagValue string, expected *float64) {
mf := createMF("r128_track_gain", tagValue) mf := createMF("r128_track_gain", tagValue)
Expect(mf.RGTrackGain).To(Equal(expected)) Expect(mf.RGTrackGain).To(Equal(expected))
}, },
Entry("0", "0", 5.0), Entry("0", "0", gg.P(5.0)),
Entry("-3776", "-3776", -9.75), Entry("-3776", "-3776", gg.P(-9.75)),
Entry("Infinity", "Infinity", 0.0), Entry("Infinity", "Infinity", nil),
Entry("Invalid value", "INVALID VALUE", 0.0), Entry("Invalid value", "INVALID VALUE", nil),
) )
}) })
+4 -4
View File
@@ -25,10 +25,10 @@ type dbMediaFile struct {
Tags string `structs:"-" json:"-"` Tags string `structs:"-" json:"-"`
// These are necessary to map the correct names (rg_*) to the correct fields (RG*) // These are necessary to map the correct names (rg_*) to the correct fields (RG*)
// without using `db` struct tags in the model.MediaFile struct // without using `db` struct tags in the model.MediaFile struct
RgAlbumGain float64 `structs:"-" json:"-"` RgAlbumGain *float64 `structs:"-" json:"-"`
RgAlbumPeak float64 `structs:"-" json:"-"` RgAlbumPeak *float64 `structs:"-" json:"-"`
RgTrackGain float64 `structs:"-" json:"-"` RgTrackGain *float64 `structs:"-" json:"-"`
RgTrackPeak float64 `structs:"-" json:"-"` RgTrackPeak *float64 `structs:"-" json:"-"`
} }
func (m *dbMediaFile) PostScan() error { func (m *dbMediaFile) PostScan() error {
+2 -1
View File
@@ -12,6 +12,7 @@ import (
"github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request" "github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/tests" "github.com/navidrome/navidrome/tests"
"github.com/navidrome/navidrome/utils/gg"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/pocketbase/dbx" "github.com/pocketbase/dbx"
@@ -79,7 +80,7 @@ var (
songAntenna = mf(model.MediaFile{ID: "1004", Title: "Antenna", ArtistID: "2", Artist: "Kraftwerk", songAntenna = mf(model.MediaFile{ID: "1004", Title: "Antenna", ArtistID: "2", Artist: "Kraftwerk",
AlbumID: "103", AlbumID: "103",
Path: p("/kraft/radio/antenna.mp3"), Path: p("/kraft/radio/antenna.mp3"),
RGAlbumGain: 1.0, RGAlbumPeak: 2.0, RGTrackGain: 3.0, RGTrackPeak: 4.0, RGAlbumGain: gg.P(1.0), RGAlbumPeak: gg.P(2.0), RGTrackGain: gg.P(3.0), RGTrackPeak: gg.P(4.0),
}) })
songAntennaWithLyrics = mf(model.MediaFile{ songAntennaWithLyrics = mf(model.MediaFile{
ID: "1005", ID: "1005",
+2 -1
View File
@@ -9,6 +9,7 @@ import (
"strings" "strings"
"github.com/navidrome/navidrome/server/subsonic/responses" "github.com/navidrome/navidrome/server/subsonic/responses"
"github.com/navidrome/navidrome/utils/gg"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
@@ -91,7 +92,7 @@ var _ = Describe("sendResponse", func() {
It("should return a fail response", func() { It("should return a fail response", func() {
payload.Song = &responses.Child{OpenSubsonicChild: &responses.OpenSubsonicChild{}} payload.Song = &responses.Child{OpenSubsonicChild: &responses.OpenSubsonicChild{}}
// An +Inf value will cause an error when marshalling to JSON // An +Inf value will cause an error when marshalling to JSON
payload.Song.ReplayGain = responses.ReplayGain{TrackGain: math.Inf(1)} payload.Song.ReplayGain = responses.ReplayGain{TrackGain: gg.P(math.Inf(1))}
q := r.URL.Query() q := r.URL.Query()
q.Add("f", "json") q.Add("f", "json")
r.URL.RawQuery = q.Encode() r.URL.RawQuery = q.Encode()
@@ -166,6 +166,52 @@
], ],
"displayComposer": "composer 1 \u0026 composer 2", "displayComposer": "composer 1 \u0026 composer 2",
"explicitStatus": "clean" "explicitStatus": "clean"
},
{
"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,
"isVideo": false,
"bpm": 0,
"comment": "",
"sortName": "",
"mediaType": "",
"musicBrainzId": "",
"isrc": [],
"genres": [],
"replayGain": {
"trackGain": 0,
"albumGain": 0,
"trackPeak": 0,
"albumPeak": 0,
"baseGain": 0,
"fallbackGain": 0
},
"channelCount": 0,
"samplingRate": 0,
"bitDepth": 0,
"moods": [],
"artists": [],
"displayArtist": "",
"albumArtists": [],
"displayAlbumArtist": "",
"contributors": [],
"displayComposer": "",
"explicitStatus": ""
} }
] ]
} }
@@ -33,5 +33,8 @@
<artist id="2" name="artist2"></artist> <artist id="2" name="artist2"></artist>
</contributors> </contributors>
</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" isVideo="false">
<replayGain trackGain="0" albumGain="0" trackPeak="0" albumPeak="0" baseGain="0" fallbackGain="0"></replayGain>
</song>
</album> </album>
</subsonic-response> </subsonic-response>
@@ -112,6 +112,37 @@
], ],
"displayComposer": "composer 1 \u0026 composer 2", "displayComposer": "composer 1 \u0026 composer 2",
"explicitStatus": "clean" "explicitStatus": "clean"
},
{
"id": "",
"isDir": false,
"isVideo": false,
"bpm": 0,
"comment": "",
"sortName": "",
"mediaType": "",
"musicBrainzId": "",
"isrc": [],
"genres": [],
"replayGain": {
"trackGain": 0,
"albumGain": 0,
"trackPeak": 0,
"albumPeak": 0,
"baseGain": 0,
"fallbackGain": 0
},
"channelCount": 0,
"samplingRate": 0,
"bitDepth": 0,
"moods": [],
"artists": [],
"displayArtist": "",
"albumArtists": [],
"displayAlbumArtist": "",
"contributors": [],
"displayComposer": "",
"explicitStatus": ""
} }
], ],
"id": "1", "id": "1",
@@ -25,5 +25,8 @@
<artist id="4" name="composer2"></artist> <artist id="4" name="composer2"></artist>
</contributors> </contributors>
</child> </child>
<child id="" isDir="false" isVideo="false">
<replayGain trackGain="0" albumGain="0" trackPeak="0" albumPeak="0" baseGain="0" fallbackGain="0"></replayGain>
</child>
</directory> </directory>
</subsonic-response> </subsonic-response>
+7 -7
View File
@@ -546,16 +546,16 @@ type ItemGenre struct {
} }
type ReplayGain struct { type ReplayGain struct {
TrackGain float64 `xml:"trackGain,omitempty,attr" json:"trackGain,omitempty"` TrackGain *float64 `xml:"trackGain,omitempty,attr" json:"trackGain,omitempty"`
AlbumGain float64 `xml:"albumGain,omitempty,attr" json:"albumGain,omitempty"` AlbumGain *float64 `xml:"albumGain,omitempty,attr" json:"albumGain,omitempty"`
TrackPeak float64 `xml:"trackPeak,omitempty,attr" json:"trackPeak,omitempty"` TrackPeak *float64 `xml:"trackPeak,omitempty,attr" json:"trackPeak,omitempty"`
AlbumPeak float64 `xml:"albumPeak,omitempty,attr" json:"albumPeak,omitempty"` AlbumPeak *float64 `xml:"albumPeak,omitempty,attr" json:"albumPeak,omitempty"`
BaseGain float64 `xml:"baseGain,omitempty,attr" json:"baseGain,omitempty"` BaseGain *float64 `xml:"baseGain,omitempty,attr" json:"baseGain,omitempty"`
FallbackGain float64 `xml:"fallbackGain,omitempty,attr" json:"fallbackGain,omitempty"` FallbackGain *float64 `xml:"fallbackGain,omitempty,attr" json:"fallbackGain,omitempty"`
} }
func (r ReplayGain) MarshalXML(e *xml.Encoder, start xml.StartElement) error { func (r ReplayGain) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if r.TrackGain == 0 && r.AlbumGain == 0 && r.TrackPeak == 0 && r.AlbumPeak == 0 && r.BaseGain == 0 && r.FallbackGain == 0 { if r.TrackGain == nil && r.AlbumGain == nil && r.TrackPeak == nil && r.AlbumPeak == nil && r.BaseGain == nil && r.FallbackGain == nil {
return nil return nil
} }
type replayGain ReplayGain type replayGain ReplayGain
+15 -3
View File
@@ -12,6 +12,7 @@ import (
"github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/consts"
. "github.com/navidrome/navidrome/server/subsonic/responses" . "github.com/navidrome/navidrome/server/subsonic/responses"
"github.com/navidrome/navidrome/utils/gg"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
@@ -213,7 +214,7 @@ var _ = Describe("Responses", func() {
Context("with data", func() { Context("with data", func() {
BeforeEach(func() { BeforeEach(func() {
response.Directory = &Directory{Id: "1", Name: "N"} response.Directory = &Directory{Id: "1", Name: "N"}
child := make([]Child, 1) child := make([]Child, 2)
t := time.Date(2016, 03, 2, 20, 30, 0, 0, time.UTC) t := time.Date(2016, 03, 2, 20, 30, 0, 0, time.UTC)
child[0] = Child{ child[0] = Child{
Id: "1", IsDir: true, Title: "title", Album: "album", Artist: "artist", Track: 1, Id: "1", IsDir: true, Title: "title", Album: "album", Artist: "artist", Track: 1,
@@ -227,7 +228,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"},
ReplayGain: ReplayGain{TrackGain: 1, AlbumGain: 2, TrackPeak: 3, AlbumPeak: 4, BaseGain: 5, FallbackGain: 6}, 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{
{Id: "1", Name: "artist1"}, {Id: "1", Name: "artist1"},
@@ -247,6 +248,9 @@ var _ = Describe("Responses", func() {
}, },
ExplicitStatus: "clean", ExplicitStatus: "clean",
} }
child[1].OpenSubsonicChild = &OpenSubsonicChild{
ReplayGain: ReplayGain{TrackGain: gg.P(0.0), AlbumGain: gg.P(0.0), TrackPeak: gg.P(0.0), AlbumPeak: gg.P(0.0), BaseGain: gg.P(0.0), FallbackGain: gg.P(0.0)},
}
response.Directory.Child = child response.Directory.Child = child
}) })
@@ -309,13 +313,18 @@ var _ = Describe("Responses", func() {
Year: 1985, Genre: "Rock", CoverArt: "1", Size: 8421341, ContentType: "audio/flac", Year: 1985, Genre: "Rock", CoverArt: "1", Size: 8421341, ContentType: "audio/flac",
Suffix: "flac", TranscodedContentType: "audio/mpeg", TranscodedSuffix: "mp3", Suffix: "flac", TranscodedContentType: "audio/mpeg", TranscodedSuffix: "mp3",
Duration: 146, BitRate: 320, Starred: &t, Duration: 146, BitRate: 320, Starred: &t,
}, {
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", TranscodedContentType: "audio/mpeg", TranscodedSuffix: "mp3",
Duration: 146, BitRate: 320, Starred: &t,
}} }}
songs[0].OpenSubsonicChild = &OpenSubsonicChild{ songs[0].OpenSubsonicChild = &OpenSubsonicChild{
Genres: []ItemGenre{{Name: "rock"}, {Name: "progressive"}}, Genres: []ItemGenre{{Name: "rock"}, {Name: "progressive"}},
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"},
ReplayGain: ReplayGain{TrackGain: 1, AlbumGain: 2, TrackPeak: 3, AlbumPeak: 4, BaseGain: 5, FallbackGain: 6}, 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",
Artists: []ArtistID3Ref{ Artists: []ArtistID3Ref{
@@ -334,6 +343,9 @@ var _ = Describe("Responses", func() {
DisplayComposer: "composer 1 & composer 2", DisplayComposer: "composer 1 & composer 2",
ExplicitStatus: "clean", ExplicitStatus: "clean",
} }
songs[1].OpenSubsonicChild = &OpenSubsonicChild{
ReplayGain: ReplayGain{TrackGain: gg.P(0.0), AlbumGain: gg.P(0.0), TrackPeak: gg.P(0.0), AlbumPeak: gg.P(0.0), BaseGain: gg.P(0.0), FallbackGain: gg.P(0.0)},
}
response.AlbumWithSongsID3.AlbumID3 = album response.AlbumWithSongsID3.AlbumID3 = album
response.AlbumWithSongsID3.Song = songs response.AlbumWithSongsID3.Song = songs
}) })
Binary file not shown.
Binary file not shown.