Added ReplayGain 2.0 track/album calculator
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
6729f7ef38
commit
4f8f274a09
|
@ -13,7 +13,7 @@ steps:
|
|||
image: golang:1.18rc1-bullseye
|
||||
commands:
|
||||
- DEBIAN_FRONTEND=noninteractive apt update
|
||||
- DEBIAN_FRONTEND=noninteractive apt install -y git build-essential autoconf automake libtool libflac-dev libopus-dev libopusfile-dev libsamplerate0-dev libmp3lame-dev
|
||||
- DEBIAN_FRONTEND=noninteractive apt install -y git build-essential autoconf automake libtool libflac-dev libopus-dev libopusfile-dev libsamplerate0-dev libmp3lame-dev libebur128-dev
|
||||
- git clone --depth 1 https://github.com/xiph/libopusenc.git && cd libopusenc && ./autogen.sh && ./configure --prefix /usr && make && make install && cd ..
|
||||
- git clone --depth 1 https://github.com/mstorsjo/fdk-aac.git && cd fdk-aac && ./autogen.sh && ./configure --prefix /usr && make -j$(nproc) && make install && cd ..
|
||||
- go test -cover -coverpkg=git.gammaspectra.live/S.O.N.G/Kirika/hasher,git.gammaspectra.live/S.O.N.G/Kirika/audio,git.gammaspectra.live/S.O.N.G/Kirika/audio/format,git.gammaspectra.live/S.O.N.G/Kirika/audio/format/flac,git.gammaspectra.live/S.O.N.G/Kirika/audio/format/mp3,git.gammaspectra.live/S.O.N.G/Kirika/audio/format/opus,git.gammaspectra.live/S.O.N.G/Kirika/audio/format/tta -v
|
||||
|
|
107
Kirika_test.go
107
Kirika_test.go
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/aac"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/flac"
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/opus"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/tta"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/packetizer"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/replaygain"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/hasher"
|
||||
"io"
|
||||
"os"
|
||||
|
@ -84,6 +86,111 @@ func TestVorbisDecode(t *testing.T) {
|
|||
doTest(".vorbis", t)
|
||||
}
|
||||
|
||||
func TestReplayGain24(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fp, err := os.Open(TestSingleSample24)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer fp.Close()
|
||||
source, err := flac.NewFormat().Open(fp)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
gain, peak, err := replaygain.GetTrackReplayGain(source)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
expect := "-11.590850 dB, 1.000000"
|
||||
result := fmt.Sprintf("%f dB, %f", gain, peak)
|
||||
if result != expect {
|
||||
t.Errorf("Wrong ReplayGain %s != %s", result, expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplayGain16(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fp, err := os.Open(TestSingleSample16)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer fp.Close()
|
||||
source, err := flac.NewFormat().Open(fp)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
gain, peak, err := replaygain.GetTrackReplayGain(source)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
expect := "-7.748202 dB, 0.942137"
|
||||
result := fmt.Sprintf("%f dB, %f", gain, peak)
|
||||
if result != expect {
|
||||
t.Errorf("Wrong ReplayGain %s != %s", result, expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlbumReplayGain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
location := TestSampleLocations[1]
|
||||
entries, err := os.ReadDir(location)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
var sources []audio.Source
|
||||
for _, f := range entries {
|
||||
if path.Ext(f.Name()) == ".flac" {
|
||||
fullPath := path.Join(location, f.Name())
|
||||
|
||||
fp, err := os.Open(fullPath)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer fp.Close()
|
||||
decoders, err := guess.GetDecoders(fp, fullPath)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
source, err := guess.Open(fp, decoders)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
sources = append(sources, source)
|
||||
}
|
||||
}
|
||||
|
||||
albumGain, albumPeak, trackGains, trackPeaks, err := replaygain.GetAlbumReplayGain(sources)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
expect := "-8.539780 dB, 0.970916, [-7.748202 -9.020742 -9.192557 -8.015611 -8.303189 -9.071835 -9.356090 -8.114885 -7.760444 -7.317174 -8.339798 -8.906274 -8.001073], [0.942137 0.970916 0.970916 0.970916 0.942106 0.970916 0.970916 0.965606 0.970916 0.970916 0.942137 0.970916 0.942137]"
|
||||
result := fmt.Sprintf("%f dB, %f, %f, %f", albumGain, albumPeak, trackGains, trackPeaks)
|
||||
if result != expect {
|
||||
t.Errorf("Wrong ReplayGain %s != %s", result, expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasher24(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ Collection of audio utilities for decoding/encoding files and streams.
|
|||
* Audio downmixing to stereo/mono
|
||||
* Multi-codec decoder and encoder
|
||||
* Multi-format packetizers
|
||||
* ReplayGain 2.0 track/album calculator
|
||||
|
||||
## Codecs supported
|
||||
|
||||
|
@ -73,4 +74,9 @@ sudo apt install libmp3lame-dev
|
|||
### [libsamplerate](https://github.com/libsndfile/libsamplerate) (required by [gosamplerate](https://github.com/dh1tw/gosamplerate))
|
||||
```shell
|
||||
sudo apt install libsamplerate0-dev
|
||||
```
|
||||
|
||||
### [libebur128](https://github.com/jiixyj/libebur128) (required by [go-ebur128](https://git.gammaspectra.live/S.O.N.G/go-ebur128))
|
||||
```shell
|
||||
sudo apt install libebur128-dev
|
||||
```
|
113
audio/replaygain/normalization.go
Normal file
113
audio/replaygain/normalization.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
package replaygain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
libebur128 "git.gammaspectra.live/S.O.N.G/go-ebur128"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const referenceLevel = -18.0
|
||||
|
||||
//GetTrackReplayGain calculates track ReplayGain 2.0
|
||||
func GetTrackReplayGain(source audio.Source) (gain, peak float64, err error) {
|
||||
state := libebur128.NewState(source.Channels, source.SampleRate, libebur128.LoudnessGlobalMomentary|libebur128.SamplePeak)
|
||||
if state == nil {
|
||||
err = errors.New("could not initialize state")
|
||||
return
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
for block := range source.Blocks {
|
||||
if err = state.AddFloat(block); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var loudness float64
|
||||
var peakSlice []float64
|
||||
|
||||
if loudness, err = state.GetLoudnessGlobal(); err != nil {
|
||||
return
|
||||
}
|
||||
if peakSlice, err = state.GetSamplePeak(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range peakSlice {
|
||||
if p > peak {
|
||||
peak = p
|
||||
}
|
||||
}
|
||||
|
||||
gain = referenceLevel - loudness
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//GetAlbumReplayGain calculates album and tracks ReplayGain 2.0
|
||||
func GetAlbumReplayGain(sources []audio.Source) (albumGain, albumPeak float64, trackGains []float64, trackPeaks []float64, err error) {
|
||||
var states []*libebur128.State
|
||||
var wg sync.WaitGroup
|
||||
defer func() {
|
||||
wg.Wait()
|
||||
for _, state := range states {
|
||||
state.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
for _, source := range sources {
|
||||
state := libebur128.NewState(source.Channels, source.SampleRate, libebur128.LoudnessGlobalMomentary|libebur128.SamplePeak)
|
||||
if state == nil {
|
||||
err = errors.New("could not initialize state")
|
||||
return
|
||||
}
|
||||
states = append(states, state)
|
||||
|
||||
wg.Add(1)
|
||||
go func(source audio.Source) {
|
||||
defer wg.Done()
|
||||
for block := range source.Blocks {
|
||||
if err = state.AddFloat(block); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}(source)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
var albumLoudness float64
|
||||
var peakSlice []float64
|
||||
var trackLoudness float64
|
||||
|
||||
if albumLoudness, err = libebur128.GetLoudnessGlobalMultiple(states); err != nil {
|
||||
return
|
||||
}
|
||||
for _, state := range states {
|
||||
if trackLoudness, err = state.GetLoudnessGlobal(); err != nil {
|
||||
return
|
||||
}
|
||||
if peakSlice, err = state.GetSamplePeak(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var tPeak float64
|
||||
|
||||
for _, p := range peakSlice {
|
||||
if p > albumPeak {
|
||||
albumPeak = p
|
||||
}
|
||||
if p > tPeak {
|
||||
tPeak = p
|
||||
}
|
||||
}
|
||||
|
||||
trackGains = append(trackGains, referenceLevel-trackLoudness)
|
||||
trackPeaks = append(trackPeaks, tPeak)
|
||||
}
|
||||
|
||||
albumGain = referenceLevel - albumLoudness
|
||||
|
||||
return
|
||||
}
|
1
go.mod
1
go.mod
|
@ -3,6 +3,7 @@ module git.gammaspectra.live/S.O.N.G/Kirika
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
git.gammaspectra.live/S.O.N.G/go-ebur128 v0.0.0-20220308105126-3299585e6a9a
|
||||
git.gammaspectra.live/S.O.N.G/go-fdkaac v0.0.0-20220228131722-e9cb84c52f48
|
||||
git.gammaspectra.live/S.O.N.G/go-pus v0.0.0-20220227175608-6cc027f24dba
|
||||
git.gammaspectra.live/S.O.N.G/go-tta v0.2.1-0.20220226150007-096de1072bd6
|
||||
|
|
2
go.sum
2
go.sum
|
@ -1,3 +1,5 @@
|
|||
git.gammaspectra.live/S.O.N.G/go-ebur128 v0.0.0-20220308105126-3299585e6a9a h1:cEkgIB5RSBFcyeB/VhpHiuYof2V/nMU5js46UroYs4c=
|
||||
git.gammaspectra.live/S.O.N.G/go-ebur128 v0.0.0-20220308105126-3299585e6a9a/go.mod h1:5H4eVW9uknpn8REFr+C3ejhvXdncgm/pbGqKGC43gFY=
|
||||
git.gammaspectra.live/S.O.N.G/go-fdkaac v0.0.0-20220228131722-e9cb84c52f48 h1:MaKiBfXQl0keyfdCi1PxGOKRTiWhIs8PqCal5GhKDi0=
|
||||
git.gammaspectra.live/S.O.N.G/go-fdkaac v0.0.0-20220228131722-e9cb84c52f48/go.mod h1:pkWt//S9hLVEQaJDPu/cHHPk8vPpo/0+zHy0me4LIP4=
|
||||
git.gammaspectra.live/S.O.N.G/go-pus v0.0.0-20220227175608-6cc027f24dba h1:JEaxCVgdr3XXAuDCPAx7ttLFZaaHzTEzG+oRnVUtUKU=
|
||||
|
|
Loading…
Reference in a new issue