Skip to content

Commit

Permalink
feat(subsonic): expose replaygain tags
Browse files Browse the repository at this point in the history
  • Loading branch information
sentriz committed May 30, 2024
1 parent 259be0e commit 0e45f5e
Show file tree
Hide file tree
Showing 18 changed files with 281 additions and 152 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 : <code>@eaDir\|[aA]rtwork\|[cC]overs\|[sS]cans\|[sS]pectrals</code> |
| `GONIC_EXCLUDE_PATTERN` | `-exclude-pattern` | **optional** files matching this regex pattern will not be imported. eg <code>@eaDir\|[aA]rtwork\|[cC]overs\|[sS]cans\|[sS]pectrals</code> |
| `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)) |
Expand Down Expand Up @@ -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
Expand Down
12 changes: 9 additions & 3 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
5 changes: 5 additions & 0 deletions db/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
10 changes: 8 additions & 2 deletions mockfs/mockfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
10 changes: 8 additions & 2 deletions scanner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions server/ctrlsubsonic/spec/construct_by_folder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
8 changes: 8 additions & 0 deletions server/ctrlsubsonic/spec/construct_by_tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
9 changes: 9 additions & 0 deletions server/ctrlsubsonic/spec/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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
}

Expand Down
80 changes: 40 additions & 40 deletions server/ctrlsubsonic/testdata/test_get_album_list_random
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -40,65 +40,81 @@
"playCount": 0
},
{
"id": "al-4",
"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-3",
"created": "2019-11-30T00:00:00Z",
"artist": "artist-0",
"artists": null,
"displayArtist": "",
"title": "album-1",
"album": "album-1",
"title": "album-0",
"album": "album-0",
"parent": "al-2",
"isDir": true,
"coverArt": "al-4",
"name": "album-1",
"coverArt": "al-3",
"name": "album-0",
"songCount": 3,
"duration": 300,
"playCount": 0
},
{
"id": "al-12",
"id": "al-4",
"created": "2019-11-30T00:00:00Z",
"artist": "artist-2",
"artist": "artist-0",
"artists": null,
"displayArtist": "",
"title": "album-1",
"album": "album-1",
"parent": "al-10",
"parent": "al-2",
"isDir": true,
"coverArt": "al-12",
"coverArt": "al-4",
"name": "album-1",
"songCount": 3,
"duration": 300,
"playCount": 0
},
{
"id": "al-5",
"id": "al-9",
"created": "2019-11-30T00:00:00Z",
"artist": "artist-0",
"artist": "artist-1",
"artists": null,
"displayArtist": "",
"title": "album-2",
"album": "album-2",
"parent": "al-2",
"parent": "al-6",
"isDir": true,
"coverArt": "al-5",
"coverArt": "al-9",
"name": "album-2",
"songCount": 3,
"duration": 300,
"playCount": 0
},
{
"id": "al-3",
"id": "al-12",
"created": "2019-11-30T00:00:00Z",
"artist": "artist-0",
"artist": "artist-2",
"artists": null,
"displayArtist": "",
"title": "album-0",
"album": "album-0",
"parent": "al-2",
"title": "album-1",
"album": "album-1",
"parent": "al-10",
"isDir": true,
"coverArt": "al-3",
"name": "album-0",
"coverArt": "al-12",
"name": "album-1",
"songCount": 3,
"duration": 300,
"playCount": 0
Expand All @@ -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",
Expand Down
Loading

0 comments on commit 0e45f5e

Please sign in to comment.