feat(api): deliver loudness gains to every client (M464 #4997)
release / web (push) Successful in 1m44s
release / go (push) Successful in 2m12s
release / govulncheck (push) Successful in 40s
release / android (push) Successful in 5m28s
release / Build signed APK (releases and dev) (push) Successful in 4m42s
release / integration (push) Successful in 15m33s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m21s
release / Verify release artifacts (tag releases only) (push) Skipped
release / web (push) Successful in 1m44s
release / go (push) Successful in 2m12s
release / govulncheck (push) Successful in 40s
release / android (push) Successful in 5m28s
release / Build signed APK (releases and dev) (push) Successful in 4m42s
release / integration (push) Successful in 15m33s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m21s
release / Verify release artifacts (tag releases only) (push) Skipped
ReplayGain 2.0 values (gain to -18 LUFS, linear peak) derived from the stored track and album loudness: - Web: GET /api/tracks/replay-gain?ids=... (up to 200), a lookup the player calls for its queue, rather than a field on every TrackRef surface. - Android: track_gain/track_peak and album_gain/album_peak on the sync views, so cached tracks level offline. Storing a measurement logs a track change, and an album's values moving logs an album change, both before the write (#2704), so caches pick the gains up. - OpenSubsonic: replayGain on every song (album, getSong, search3, starred), as a JSON object and an XML element. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||||
)
|
||||
|
||||
func TestGetReplayGain(t *testing.T) {
|
||||
h, pool := testHandlers(t)
|
||||
user := seedUser(t, pool, "rg1", "pw", false)
|
||||
measured, _ := seedTrackForRemoveTest(t, h, "rg-measured", "", "")
|
||||
unmeasured, _ := seedTrackForRemoveTest(t, h, "rg-unmeasured", "", "")
|
||||
lufs, peak := float32(-12.5), float32(-1)
|
||||
if err := dbq.New(pool).UpsertTrackLoudness(context.Background(), dbq.UpsertTrackLoudnessParams{
|
||||
TrackID: measured.ID, IntegratedLufs: &lufs, TruePeakDbtp: &peak, AnalysisVersion: 1,
|
||||
}); err != nil {
|
||||
t.Fatalf("seed loudness: %v", err)
|
||||
}
|
||||
r := chi.NewRouter()
|
||||
r.Get("/api/tracks/replay-gain", h.handleGetReplayGain)
|
||||
get := func(query string) *httptest.ResponseRecorder {
|
||||
req := withUser(httptest.NewRequest(http.MethodGet, "/api/tracks/replay-gain"+query, nil), user)
|
||||
rec := httptest.NewRecorder()
|
||||
r.ServeHTTP(rec, req)
|
||||
return rec
|
||||
}
|
||||
|
||||
rec := get("?ids=" + uuidToString(measured.ID) + "," + uuidToString(unmeasured.ID) + ",not-a-uuid")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d; body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
var resp struct {
|
||||
Items map[string]struct {
|
||||
TrackGain *float32 `json:"track_gain"`
|
||||
TrackPeak *float32 `json:"track_peak"`
|
||||
AlbumGain *float32 `json:"album_gain"`
|
||||
} `json:"items"`
|
||||
}
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
g, ok := resp.Items[uuidToString(measured.ID)]
|
||||
if !ok || g.TrackGain == nil || *g.TrackGain != -5.5 || g.TrackPeak == nil {
|
||||
t.Errorf("measured track = %+v (present %v), want track_gain -5.5 with a peak", g, ok)
|
||||
}
|
||||
if g.AlbumGain != nil {
|
||||
t.Errorf("album_gain present with no album loudness: %v", *g.AlbumGain)
|
||||
}
|
||||
if _, ok := resp.Items[uuidToString(unmeasured.ID)]; ok {
|
||||
t.Errorf("unmeasured track listed; absent means no adjustment")
|
||||
}
|
||||
if len(resp.Items) != 1 {
|
||||
t.Errorf("items = %v, want only the measured track", resp.Items)
|
||||
}
|
||||
|
||||
if rec := get(""); rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), `"items":{}`) {
|
||||
t.Errorf("no ids: %d %s, want 200 with empty items", rec.Code, rec.Body.String())
|
||||
}
|
||||
if rec := get("?ids=" + strings.Repeat("x,", maxReplayGainIDs)); rec.Code != http.StatusBadRequest {
|
||||
t.Errorf("%d ids: status %d, want 400", maxReplayGainIDs+1, rec.Code)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user