diff --git a/README.md b/README.md
index feeb98b..af2a117 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,7 @@ password can then be changed from the web interface
| `GONIC_JUKEBOX_ENABLED` | `-jukebox-enabled` | **optional** whether the subsonic [jukebox api](https://airsonic.github.io/docs/jukebox/) should be enabled |
| `GONIC_JUKEBOX_MPV_EXTRA_ARGS` | `-jukebox-mpv-extra-args` | **optional** extra command line arguments to pass to the jukebox mpv daemon |
| `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. Example : @eaDir\|[aA]rtwork\|[cC]overs\|[sS]cans\|[sS]pectrals |
+| `GONIC_EXCLUDE_PATTERN` | `-exclude-pattern` | **optional** files matching this regex pattern will not be imported. eg @eaDir\|[aA]rtwork\|[cC]overs\|[sS]cans\|[sS]pectrals |
| `GONIC_MULTI_VALUE_GENRE` | `-multi-value-genre` | **optional** setting for multi-valued genre tags when scanning ([see more](#multi-valued-tags-v016)) |
| `GONIC_MULTI_VALUE_ARTIST` | `-multi-value-artist` | **optional** setting for multi-valued artist tags when scanning ([see more](#multi-valued-tags-v016)) |
| `GONIC_MULTI_VALUE_ALBUM_ARTIST` | `-multi-value-album-artist` | **optional** setting for multi-valued album artist tags when scanning ([see more](#multi-valued-tags-v016)) |
@@ -97,7 +97,7 @@ the available modes are:
gonic supports multiple music folders. this can be handy if you have your music separated by albums, compilations, singles. or maybe 70s, 80s, 90s. whatever.
-on top of that - if you don't decide your folder names, or simply do not want the same name in your subsonic client,
+on top of that - if you don't decide your folder names, or simply do not want the same name in your subsonic client,
gonic can parse aliases for the folder names with the optional `ALIAS->PATH` syntax
if you're running gonic with the command line, stack the `-music-path` arg
diff --git a/db/db.go b/db/db.go
index 9ea81a9..ab5a948 100644
--- a/db/db.go
+++ b/db/db.go
@@ -235,9 +235,15 @@ type Track struct {
TagTrackNumber int `sql:"default: null"`
TagDiscNumber int `sql:"default: null"`
TagBrainzID string `sql:"default: null"`
- TrackStar *TrackStar
- TrackRating *TrackRating
- AverageRating float64 `sql:"default: null"`
+
+ ReplayGainTrackGain float32
+ ReplayGainTrackPeak float32
+ ReplayGainAlbumGain float32
+ ReplayGainAlbumPeak float32
+
+ TrackStar *TrackStar
+ TrackRating *TrackRating
+ AverageRating float64 `sql:"default: null"`
}
func (t *Track) AudioLength() int { return t.Length }
diff --git a/db/migrations.go b/db/migrations.go
index 80304cf..447bfdf 100644
--- a/db/migrations.go
+++ b/db/migrations.go
@@ -72,6 +72,7 @@ func (db *DB) Migrate(ctx MigrationContext) error {
construct(ctx, "202311072309", migrateAlbumInfo),
construct(ctx, "202311082304", migrateTemporaryDisplayAlbumArtist),
construct(ctx, "202312110003", migrateAddExtraIndexes),
+ construct(ctx, "202405301140", migrateAddReplayGainFields),
}
return gormigrate.
@@ -813,3 +814,7 @@ func migrateAddExtraIndexes(tx *gorm.DB, _ MigrationContext) error {
CREATE INDEX idx_artist_appearances_album_id ON "artist_appearances" (album_id);
`).Error
}
+
+func migrateAddReplayGainFields(tx *gorm.DB, _ MigrationContext) error {
+ return tx.AutoMigrate(Track{}).Error
+}
diff --git a/mockfs/mockfs.go b/mockfs/mockfs.go
index 450c136..a109f80 100644
--- a/mockfs/mockfs.go
+++ b/mockfs/mockfs.go
@@ -362,8 +362,14 @@ func (i *TagInfo) Genres() []string { return []string{i.RawGenre} }
func (i *TagInfo) TrackNumber() int { return 1 }
func (i *TagInfo) DiscNumber() int { return 1 }
func (i *TagInfo) Year() int { return 2021 }
-func (i *TagInfo) Length() int { return firstInt(100, i.RawLength) }
-func (i *TagInfo) Bitrate() int { return firstInt(100, i.RawBitrate) }
+
+func (i *TagInfo) ReplayGainTrackGain() float32 { return 0 }
+func (i *TagInfo) ReplayGainTrackPeak() float32 { return 0 }
+func (i *TagInfo) ReplayGainAlbumGain() float32 { return 0 }
+func (i *TagInfo) ReplayGainAlbumPeak() float32 { return 0 }
+
+func (i *TagInfo) Length() int { return firstInt(100, i.RawLength) }
+func (i *TagInfo) Bitrate() int { return firstInt(100, i.RawBitrate) }
var _ tagcommon.Reader = (*tagReader)(nil)
diff --git a/scanner/scanner.go b/scanner/scanner.go
index 661c2e1..adbd8d4 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -469,8 +469,14 @@ func populateTrack(tx *db.DB, album *db.Album, track *db.Track, trags tagcommon.
track.TagDiscNumber = trags.DiscNumber()
track.TagBrainzID = trags.BrainzID()
- track.Length = trags.Length() // these two should be calculated
- track.Bitrate = trags.Bitrate() // ...from the file instead of tags
+ track.ReplayGainTrackGain = trags.ReplayGainTrackGain()
+ track.ReplayGainTrackPeak = trags.ReplayGainTrackPeak()
+ track.ReplayGainAlbumGain = trags.ReplayGainAlbumGain()
+ track.ReplayGainAlbumPeak = trags.ReplayGainAlbumPeak()
+
+ // these two are calculated from the file instead of tags
+ track.Length = trags.Length()
+ track.Bitrate = trags.Bitrate()
if err := tx.Save(&track).Error; err != nil {
return fmt.Errorf("saving track: %w", err)
diff --git a/server/ctrlsubsonic/spec/construct_by_folder.go b/server/ctrlsubsonic/spec/construct_by_folder.go
index bbd0655..f8f35d3 100644
--- a/server/ctrlsubsonic/spec/construct_by_folder.go
+++ b/server/ctrlsubsonic/spec/construct_by_folder.go
@@ -102,6 +102,14 @@ func NewTCTrackByFolder(t *db.Track, parent *db.Album) *TrackChild {
for _, a := range t.Artists {
trCh.Artists = append(trCh.Artists, &ArtistRef{ID: a.SID(), Name: a.Name})
}
+ if t.ReplayGainTrackGain != 0 || t.ReplayGainAlbumGain != 0 {
+ trCh.ReplayGain = &ReplayGain{
+ TrackGain: t.ReplayGainTrackGain,
+ TrackPeak: t.ReplayGainTrackPeak,
+ AlbumGain: t.ReplayGainAlbumGain,
+ AlbumPeak: t.ReplayGainAlbumPeak,
+ }
+ }
return trCh
}
diff --git a/server/ctrlsubsonic/spec/construct_by_tags.go b/server/ctrlsubsonic/spec/construct_by_tags.go
index a3d8457..97b8332 100644
--- a/server/ctrlsubsonic/spec/construct_by_tags.go
+++ b/server/ctrlsubsonic/spec/construct_by_tags.go
@@ -112,6 +112,14 @@ func NewTrackByTags(t *db.Track, album *db.Album) *TrackChild {
for _, a := range album.Artists {
ret.AlbumArtists = append(ret.AlbumArtists, &ArtistRef{ID: a.SID(), Name: a.Name})
}
+ if t.ReplayGainTrackGain != 0 || t.ReplayGainAlbumGain != 0 {
+ ret.ReplayGain = &ReplayGain{
+ TrackGain: t.ReplayGainTrackGain,
+ TrackPeak: t.ReplayGainTrackPeak,
+ AlbumGain: t.ReplayGainAlbumGain,
+ AlbumPeak: t.ReplayGainAlbumPeak,
+ }
+ }
return ret
}
diff --git a/server/ctrlsubsonic/spec/spec.go b/server/ctrlsubsonic/spec/spec.go
index 757234d..48055b0 100644
--- a/server/ctrlsubsonic/spec/spec.go
+++ b/server/ctrlsubsonic/spec/spec.go
@@ -169,6 +169,13 @@ type TranscodeMeta struct {
TranscodedSuffix string `xml:"transcodedSuffix,attr,omitempty" json:"transcodedSuffix,omitempty"`
}
+type ReplayGain struct {
+ TrackGain float32 `xml:"trackGain,attr" json:"trackGain"`
+ TrackPeak float32 `xml:"trackPeak,attr" json:"trackPeak"`
+ AlbumGain float32 `xml:"albumGain,attr" json:"albumGain"`
+ AlbumPeak float32 `xml:"albumPeak,attr" json:"albumPeak"`
+}
+
// https://opensubsonic.netlify.app/docs/responses/child/
type TrackChild struct {
ID *specid.ID `xml:"id,attr,omitempty" json:"id,omitempty"`
@@ -211,6 +218,8 @@ type TrackChild struct {
UserRating int `xml:"userRating,attr,omitempty" json:"userRating,omitempty"`
AverageRating string `xml:"averageRating,attr,omitempty" json:"averageRating,omitempty"`
+ ReplayGain *ReplayGain `xml:"replayGain" json:"replayGain"`
+
TranscodeMeta
}
diff --git a/server/ctrlsubsonic/testdata/test_get_album_list_random b/server/ctrlsubsonic/testdata/test_get_album_list_random
index 604aaa1..e88d80f 100644
--- a/server/ctrlsubsonic/testdata/test_get_album_list_random
+++ b/server/ctrlsubsonic/testdata/test_get_album_list_random
@@ -8,16 +8,16 @@
"albumList": {
"album": [
{
- "id": "al-9",
+ "id": "al-5",
"created": "2019-11-30T00:00:00Z",
- "artist": "artist-1",
+ "artist": "artist-0",
"artists": null,
"displayArtist": "",
"title": "album-2",
"album": "album-2",
- "parent": "al-6",
+ "parent": "al-2",
"isDir": true,
- "coverArt": "al-9",
+ "coverArt": "al-5",
"name": "album-2",
"songCount": 3,
"duration": 300,
@@ -40,48 +40,16 @@
"playCount": 0
},
{
- "id": "al-4",
- "created": "2019-11-30T00:00:00Z",
- "artist": "artist-0",
- "artists": null,
- "displayArtist": "",
- "title": "album-1",
- "album": "album-1",
- "parent": "al-2",
- "isDir": true,
- "coverArt": "al-4",
- "name": "album-1",
- "songCount": 3,
- "duration": 300,
- "playCount": 0
- },
- {
- "id": "al-12",
+ "id": "al-13",
"created": "2019-11-30T00:00:00Z",
"artist": "artist-2",
"artists": null,
"displayArtist": "",
- "title": "album-1",
- "album": "album-1",
- "parent": "al-10",
- "isDir": true,
- "coverArt": "al-12",
- "name": "album-1",
- "songCount": 3,
- "duration": 300,
- "playCount": 0
- },
- {
- "id": "al-5",
- "created": "2019-11-30T00:00:00Z",
- "artist": "artist-0",
- "artists": null,
- "displayArtist": "",
"title": "album-2",
"album": "album-2",
- "parent": "al-2",
+ "parent": "al-10",
"isDir": true,
- "coverArt": "al-5",
+ "coverArt": "al-13",
"name": "album-2",
"songCount": 3,
"duration": 300,
@@ -103,6 +71,54 @@
"duration": 300,
"playCount": 0
},
+ {
+ "id": "al-4",
+ "created": "2019-11-30T00:00:00Z",
+ "artist": "artist-0",
+ "artists": null,
+ "displayArtist": "",
+ "title": "album-1",
+ "album": "album-1",
+ "parent": "al-2",
+ "isDir": true,
+ "coverArt": "al-4",
+ "name": "album-1",
+ "songCount": 3,
+ "duration": 300,
+ "playCount": 0
+ },
+ {
+ "id": "al-9",
+ "created": "2019-11-30T00:00:00Z",
+ "artist": "artist-1",
+ "artists": null,
+ "displayArtist": "",
+ "title": "album-2",
+ "album": "album-2",
+ "parent": "al-6",
+ "isDir": true,
+ "coverArt": "al-9",
+ "name": "album-2",
+ "songCount": 3,
+ "duration": 300,
+ "playCount": 0
+ },
+ {
+ "id": "al-12",
+ "created": "2019-11-30T00:00:00Z",
+ "artist": "artist-2",
+ "artists": null,
+ "displayArtist": "",
+ "title": "album-1",
+ "album": "album-1",
+ "parent": "al-10",
+ "isDir": true,
+ "coverArt": "al-12",
+ "name": "album-1",
+ "songCount": 3,
+ "duration": 300,
+ "playCount": 0
+ },
{
"id": "al-8",
"created": "2019-11-30T00:00:00Z",
@@ -119,22 +135,6 @@
"duration": 300,
"playCount": 0
},
- {
- "id": "al-13",
- "created": "2019-11-30T00:00:00Z",
- "artist": "artist-2",
- "artists": null,
- "displayArtist": "",
- "title": "album-2",
- "album": "album-2",
- "parent": "al-10",
- "isDir": true,
- "coverArt": "al-13",
- "name": "album-2",
- "songCount": 3,
- "duration": 300,
- "playCount": 0
- },
{
"id": "al-11",
"created": "2019-11-30T00:00:00Z",
diff --git a/server/ctrlsubsonic/testdata/test_get_album_list_two_random b/server/ctrlsubsonic/testdata/test_get_album_list_two_random
index 99edf56..dbcad0e 100644
--- a/server/ctrlsubsonic/testdata/test_get_album_list_two_random
+++ b/server/ctrlsubsonic/testdata/test_get_album_list_two_random
@@ -24,32 +24,16 @@
"year": 2021
},
{
- "id": "al-5",
- "created": "2019-11-30T00:00:00Z",
- "artistId": "ar-1",
- "artist": "artist-0",
- "artists": [{ "id": "ar-1", "name": "artist-0" }],
- "displayArtist": "artist-0",
- "title": "album-2",
- "album": "album-2",
- "coverArt": "al-5",
- "name": "album-2",
- "songCount": 3,
- "duration": 300,
- "playCount": 0,
- "year": 2021
- },
- {
- "id": "al-8",
+ "id": "al-7",
"created": "2019-11-30T00:00:00Z",
"artistId": "ar-2",
"artist": "artist-1",
"artists": [{ "id": "ar-2", "name": "artist-1" }],
"displayArtist": "artist-1",
- "title": "album-1",
- "album": "album-1",
- "coverArt": "al-8",
- "name": "album-1",
+ "title": "album-0",
+ "album": "album-0",
+ "coverArt": "al-7",
+ "name": "album-0",
"songCount": 3,
"duration": 300,
"playCount": 0,
@@ -71,38 +55,6 @@
"playCount": 0,
"year": 2021
},
- {
- "id": "al-12",
- "created": "2019-11-30T00:00:00Z",
- "artistId": "ar-3",
- "artist": "artist-2",
- "artists": [{ "id": "ar-3", "name": "artist-2" }],
- "displayArtist": "artist-2",
- "title": "album-1",
- "album": "album-1",
- "coverArt": "al-12",
- "name": "album-1",
- "songCount": 3,
- "duration": 300,
- "playCount": 0,
- "year": 2021
- },
- {
- "id": "al-7",
- "created": "2019-11-30T00:00:00Z",
- "artistId": "ar-2",
- "artist": "artist-1",
- "artists": [{ "id": "ar-2", "name": "artist-1" }],
- "displayArtist": "artist-1",
- "title": "album-0",
- "album": "album-0",
- "coverArt": "al-7",
- "name": "album-0",
- "songCount": 3,
- "duration": 300,
- "playCount": 0,
- "year": 2021
- },
{
"id": "al-13",
"created": "2019-11-30T00:00:00Z",
@@ -135,6 +87,54 @@
"playCount": 0,
"year": 2021
},
+ {
+ "id": "al-12",
+ "created": "2019-11-30T00:00:00Z",
+ "artistId": "ar-3",
+ "artist": "artist-2",
+ "artists": [{ "id": "ar-3", "name": "artist-2" }],
+ "displayArtist": "artist-2",
+ "title": "album-1",
+ "album": "album-1",
+ "coverArt": "al-12",
+ "name": "album-1",
+ "songCount": 3,
+ "duration": 300,
+ "playCount": 0,
+ "year": 2021
+ },
+ {
+ "id": "al-5",
+ "created": "2019-11-30T00:00:00Z",
+ "artistId": "ar-1",
+ "artist": "artist-0",
+ "artists": [{ "id": "ar-1", "name": "artist-0" }],
+ "displayArtist": "artist-0",
+ "title": "album-2",
+ "album": "album-2",
+ "coverArt": "al-5",
+ "name": "album-2",
+ "songCount": 3,
+ "duration": 300,
+ "playCount": 0,
+ "year": 2021
+ },
+ {
+ "id": "al-8",
+ "created": "2019-11-30T00:00:00Z",
+ "artistId": "ar-2",
+ "artist": "artist-1",
+ "artists": [{ "id": "ar-2", "name": "artist-1" }],
+ "displayArtist": "artist-1",
+ "title": "album-1",
+ "album": "album-1",
+ "coverArt": "al-8",
+ "name": "album-1",
+ "songCount": 3,
+ "duration": 300,
+ "playCount": 0,
+ "year": 2021
+ },
{
"id": "al-9",
"created": "2019-11-30T00:00:00Z",
diff --git a/server/ctrlsubsonic/testdata/test_get_album_with_cover b/server/ctrlsubsonic/testdata/test_get_album_with_cover
index 40ee4ae..ba80c38 100644
--- a/server/ctrlsubsonic/testdata/test_get_album_with_cover
+++ b/server/ctrlsubsonic/testdata/test_get_album_with_cover
@@ -48,7 +48,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-2",
@@ -75,7 +76,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-3",
@@ -102,7 +104,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
}
]
}
diff --git a/server/ctrlsubsonic/testdata/test_get_music_directory_with_tracks b/server/ctrlsubsonic/testdata/test_get_music_directory_with_tracks
index 44af192..a9468a9 100644
--- a/server/ctrlsubsonic/testdata/test_get_music_directory_with_tracks
+++ b/server/ctrlsubsonic/testdata/test_get_music_directory_with_tracks
@@ -33,7 +33,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-2",
@@ -58,7 +59,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-3",
@@ -83,7 +85,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
}
]
}
diff --git a/server/ctrlsubsonic/testdata/test_get_music_directory_without_tracks b/server/ctrlsubsonic/testdata/test_get_music_directory_without_tracks
index 6872329..b4a9721 100644
--- a/server/ctrlsubsonic/testdata/test_get_music_directory_without_tracks
+++ b/server/ctrlsubsonic/testdata/test_get_music_directory_without_tracks
@@ -23,7 +23,8 @@
"isVideo": false,
"parent": "al-2",
"title": "album-0",
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "al-4",
@@ -38,7 +39,8 @@
"isVideo": false,
"parent": "al-2",
"title": "album-1",
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "al-5",
@@ -53,7 +55,8 @@
"isVideo": false,
"parent": "al-2",
"title": "album-2",
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
}
]
}
diff --git a/server/ctrlsubsonic/testdata/test_search_three_q_tra b/server/ctrlsubsonic/testdata/test_search_three_q_tra
index 4b1758d..4c1e755 100644
--- a/server/ctrlsubsonic/testdata/test_search_three_q_tra
+++ b/server/ctrlsubsonic/testdata/test_search_three_q_tra
@@ -34,7 +34,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-2",
@@ -63,7 +64,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-3",
@@ -92,7 +94,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-4",
@@ -121,7 +124,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-5",
@@ -150,7 +154,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-6",
@@ -179,7 +184,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-7",
@@ -208,7 +214,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-8",
@@ -237,7 +244,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-9",
@@ -266,7 +274,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-10",
@@ -295,7 +304,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-11",
@@ -324,7 +334,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-12",
@@ -353,7 +364,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-13",
@@ -382,7 +394,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-14",
@@ -411,7 +424,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-15",
@@ -440,7 +454,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-16",
@@ -469,7 +484,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-17",
@@ -498,7 +514,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-18",
@@ -527,7 +544,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-19",
@@ -556,7 +574,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-20",
@@ -585,7 +604,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
}
]
}
diff --git a/server/ctrlsubsonic/testdata/test_search_two_q_alb b/server/ctrlsubsonic/testdata/test_search_two_q_alb
index 41c773e..3c449b6 100644
--- a/server/ctrlsubsonic/testdata/test_search_two_q_alb
+++ b/server/ctrlsubsonic/testdata/test_search_two_q_alb
@@ -20,7 +20,8 @@
"isVideo": false,
"parent": "al-2",
"title": "album-0",
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "al-4",
@@ -35,7 +36,8 @@
"isVideo": false,
"parent": "al-2",
"title": "album-1",
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "al-5",
@@ -50,7 +52,8 @@
"isVideo": false,
"parent": "al-2",
"title": "album-2",
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "al-7",
@@ -65,7 +68,8 @@
"isVideo": false,
"parent": "al-6",
"title": "album-0",
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "al-8",
@@ -80,7 +84,8 @@
"isVideo": false,
"parent": "al-6",
"title": "album-1",
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "al-9",
@@ -95,7 +100,8 @@
"isVideo": false,
"parent": "al-6",
"title": "album-2",
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "al-11",
@@ -110,7 +116,8 @@
"isVideo": false,
"parent": "al-10",
"title": "album-0",
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "al-12",
@@ -125,7 +132,8 @@
"isVideo": false,
"parent": "al-10",
"title": "album-1",
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "al-13",
@@ -140,7 +148,8 @@
"isVideo": false,
"parent": "al-10",
"title": "album-2",
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
}
]
}
diff --git a/server/ctrlsubsonic/testdata/test_search_two_q_tra b/server/ctrlsubsonic/testdata/test_search_two_q_tra
index a92993d..b1cfd43 100644
--- a/server/ctrlsubsonic/testdata/test_search_two_q_tra
+++ b/server/ctrlsubsonic/testdata/test_search_two_q_tra
@@ -30,7 +30,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-2",
@@ -55,7 +56,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-3",
@@ -80,7 +82,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-4",
@@ -105,7 +108,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-5",
@@ -130,7 +134,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-6",
@@ -155,7 +160,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-7",
@@ -180,7 +186,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-8",
@@ -205,7 +212,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-9",
@@ -230,7 +238,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-10",
@@ -255,7 +264,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-11",
@@ -280,7 +290,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-12",
@@ -305,7 +316,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-13",
@@ -330,7 +342,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-14",
@@ -355,7 +368,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-15",
@@ -380,7 +394,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-16",
@@ -405,7 +420,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-17",
@@ -430,7 +446,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-18",
@@ -455,7 +472,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-19",
@@ -480,7 +498,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
},
{
"id": "tr-20",
@@ -505,7 +524,8 @@
"discNumber": 1,
"type": "music",
"year": 2021,
- "musicBrainzId": ""
+ "musicBrainzId": "",
+ "replayGain": null
}
]
}
diff --git a/tags/tagcommon/tagcommmon.go b/tags/tagcommon/tagcommmon.go
index 80beb52..10da58d 100644
--- a/tags/tagcommon/tagcommmon.go
+++ b/tags/tagcommon/tagcommmon.go
@@ -24,9 +24,15 @@ type Info interface {
Genres() []string
TrackNumber() int
DiscNumber() int
+ Year() int
+
+ ReplayGainTrackGain() float32
+ ReplayGainTrackPeak() float32
+ ReplayGainAlbumGain() float32
+ ReplayGainAlbumPeak() float32
+
Length() int
Bitrate() int
- Year() int
}
const (
diff --git a/tags/taglib/taglib.go b/tags/taglib/taglib.go
index e8e0cce..ba2ab82 100644
--- a/tags/taglib/taglib.go
+++ b/tags/taglib/taglib.go
@@ -51,8 +51,14 @@ func (i *info) Genres() []string { return find(i.raw, "genres") }
func (i *info) TrackNumber() int { return intSep("/", first(find(i.raw, "tracknumber"))) } // eg. 5/12
func (i *info) DiscNumber() int { return intSep("/", first(find(i.raw, "discnumber"))) } // eg. 1/2
func (i *info) Year() int { return intSep("-", first(find(i.raw, "originaldate", "date", "year"))) } // eg. 2023-12-01
-func (i *info) Length() int { return i.props.Length }
-func (i *info) Bitrate() int { return i.props.Bitrate }
+
+func (i *info) ReplayGainTrackGain() float32 { return dB(first(find(i.raw, "replaygain_track_gain"))) }
+func (i *info) ReplayGainTrackPeak() float32 { return flt(first(find(i.raw, "replaygain_track_peak"))) }
+func (i *info) ReplayGainAlbumGain() float32 { return dB(first(find(i.raw, "replaygain_album_gain"))) }
+func (i *info) ReplayGainAlbumPeak() float32 { return flt(first(find(i.raw, "replaygain_album_peak"))) }
+
+func (i *info) Length() int { return i.props.Length }
+func (i *info) Bitrate() int { return i.props.Bitrate }
func first[T comparable](is []T) T {
var z T
@@ -83,6 +89,17 @@ func filterStr(ss []string) []string {
return r
}
+func flt(in string) float32 {
+ f, _ := strconv.ParseFloat(in, 32)
+ return float32(f)
+}
+func dB(in string) float32 {
+ in = strings.ToLower(in)
+ in = strings.TrimSuffix(in, " db")
+ in = strings.TrimSuffix(in, "db")
+ return flt(in)
+}
+
func intSep(sep, in string) int {
start, _, _ := strings.Cut(in, sep)
out, _ := strconv.Atoi(start)