From 0e45f5e84cd650211351179edf3eed89a54c6c75 Mon Sep 17 00:00:00 2001 From: sentriz Date: Thu, 30 May 2024 11:43:45 +0100 Subject: [PATCH] feat(subsonic): expose replaygain tags --- README.md | 4 +- db/db.go | 12 +- db/migrations.go | 5 + mockfs/mockfs.go | 10 +- scanner/scanner.go | 10 +- .../ctrlsubsonic/spec/construct_by_folder.go | 8 ++ server/ctrlsubsonic/spec/construct_by_tags.go | 8 ++ server/ctrlsubsonic/spec/spec.go | 9 ++ .../testdata/test_get_album_list_random | 110 +++++++++--------- .../testdata/test_get_album_list_two_random | 106 ++++++++--------- .../testdata/test_get_album_with_cover | 9 +- .../test_get_music_directory_with_tracks | 9 +- .../test_get_music_directory_without_tracks | 9 +- .../testdata/test_search_three_q_tra | 60 ++++++---- .../testdata/test_search_two_q_alb | 27 +++-- .../testdata/test_search_two_q_tra | 60 ++++++---- tags/tagcommon/tagcommmon.go | 8 +- tags/taglib/taglib.go | 21 +++- 18 files changed, 307 insertions(+), 178 deletions(-) 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)