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:
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||||
syncpkg "git.fabledsword.com/bvandeusen/minstrel/internal/sync"
|
||||
)
|
||||
|
||||
// Loudness analysis (M464 #4995).
|
||||
@@ -351,10 +352,15 @@ const (
|
||||
// An inconclusive attempt leaves any existing row alone. The scan deletes a
|
||||
// row when its file changes, so a row still here describes these bytes, and a
|
||||
// value from an older method is a better gain than none until it is redone.
|
||||
//
|
||||
// A stored measurement is logged to the sync feed as a track upsert, so
|
||||
// clients that cache the library (Android) pick up the new gain (#4997). If
|
||||
// the log fails nothing is stored, and the backfill tries the track again.
|
||||
func storeLoudness(
|
||||
ctx context.Context, q *dbq.Queries, logger *slog.Logger,
|
||||
ctx context.Context, db dbq.DBTX, logger *slog.Logger,
|
||||
trackID pgtype.UUID, path string, r loudnessResult,
|
||||
) loudnessOutcome {
|
||||
q := dbq.New(db)
|
||||
if r.err != nil {
|
||||
logger.Warn("loudness: analysis failed", "path", path, "err", r.err)
|
||||
if r.inconclusive() {
|
||||
@@ -388,6 +394,14 @@ func storeLoudness(
|
||||
}
|
||||
}
|
||||
}
|
||||
// Logged before the write, as reconcile does (#2704): a log that then
|
||||
// finds the write failed costs clients one wasted re-read, but a write
|
||||
// that lands with no log is never retried, since the track is settled, and
|
||||
// clients would never learn its gain.
|
||||
if err := syncpkg.LogChange(ctx, db, syncpkg.EntityTrack, syncpkg.FormatUUID(trackID), syncpkg.OpUpsert); err != nil {
|
||||
logger.Warn("loudness: LogChange track upsert failed; not storing", "path", path, "err", err)
|
||||
return loudnessStoreFailed
|
||||
}
|
||||
if err := q.UpsertTrackLoudness(ctx, params); err != nil {
|
||||
logger.Warn("loudness: storing measurement failed", "path", path, "err", err)
|
||||
return loudnessStoreFailed
|
||||
|
||||
Reference in New Issue
Block a user