From 5f61b1a3cfc430aca271e20fa21f8e8ee3fb0cdb Mon Sep 17 00:00:00 2001 From: Joren Date: Sun, 12 Jul 2026 21:44:01 +0200 Subject: [PATCH] fix: use exact qobuz download quality --- internal/provider/qobuz/client.go | 52 ++++++++++++++++++++++---- internal/provider/qobuz/client_test.go | 18 +++++++++ 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/internal/provider/qobuz/client.go b/internal/provider/qobuz/client.go index 0b78bd4..71fd070 100644 --- a/internal/provider/qobuz/client.go +++ b/internal/provider/qobuz/client.go @@ -364,40 +364,60 @@ func qobuzDownloadExtension(resp map[string]any, quality int, streamURL string) } func qobuzAudioProfile(resp map[string]any, requestedQuality int, ext string) provider.AudioProfile { + exactBitDepth, _ := intValue(firstNonNil(resp["bit_depth"], resp["bits_depth"], resp["maximum_bit_depth"])) + exactSampling, _ := floatValue(firstNonNil(resp["sampling_rate"], resp["sample_rate"], resp["maximum_sampling_rate"])) if formatID, ok := intValue(resp["format_id"]); ok { switch formatID { case 5: + if exactBitDepth == 0 { + exactBitDepth = 16 + } + if exactSampling == 0 { + exactSampling = 44.1 + } return provider.AudioProfile{ Container: "MP3", Codec: "MP3", Quality: "HIGH", - BitDepth: 16, - SamplingRate: "44.1", + BitDepth: exactBitDepth, + SamplingRate: formatSamplingRate(exactSampling), BitrateKbps: 320, } case 6: + if exactBitDepth == 0 { + exactBitDepth = 16 + } + if exactSampling == 0 { + exactSampling = 44.1 + } return provider.AudioProfile{ Container: "FLAC", Codec: "FLAC", Quality: "LOSSLESS", - BitDepth: 16, - SamplingRate: "44.1", + BitDepth: exactBitDepth, + SamplingRate: formatSamplingRate(exactSampling), } case 7: + if exactBitDepth == 0 { + exactBitDepth = 24 + } return provider.AudioProfile{ Container: "FLAC", Codec: "FLAC", Quality: "HI_RES", - BitDepth: 24, - SamplingRate: "96", + BitDepth: exactBitDepth, + SamplingRate: formatSamplingRate(exactSampling), } case 27: + if exactBitDepth == 0 { + exactBitDepth = 24 + } return provider.AudioProfile{ Container: "FLAC", Codec: "FLAC", Quality: "HI_RES", - BitDepth: 24, - SamplingRate: "192", + BitDepth: exactBitDepth, + SamplingRate: formatSamplingRate(exactSampling), } } } @@ -438,6 +458,22 @@ func qobuzAudioProfile(resp map[string]any, requestedQuality int, ext string) pr } } +func firstNonNil(vals ...any) any { + for _, v := range vals { + if v != nil { + return v + } + } + return nil +} + +func formatSamplingRate(v float64) string { + if v <= 0 { + return "" + } + return strconv.FormatFloat(v, 'f', -1, 64) +} + func (c *Client) Close() error { return nil } diff --git a/internal/provider/qobuz/client_test.go b/internal/provider/qobuz/client_test.go index 64e12a2..38eb598 100644 --- a/internal/provider/qobuz/client_test.go +++ b/internal/provider/qobuz/client_test.go @@ -371,6 +371,24 @@ func TestGetDownloadableUsesReturnedURLExtension(t *testing.T) { } } +func TestQobuzAudioProfileUsesExactReturnedQuality(t *testing.T) { + profile := qobuzAudioProfile(map[string]any{ + "format_id": float64(7), + "bit_depth": float64(24), + "sampling_rate": float64(44.1), + }, 4, "flac") + if profile.BitDepth != 24 || profile.SamplingRate != "44.1" || profile.Quality != "HI_RES" { + t.Fatalf("unexpected profile: %+v", profile) + } +} + +func TestQobuzAudioProfileAvoidsGuessingHiResSampleRate(t *testing.T) { + profile := qobuzAudioProfile(map[string]any{"format_id": float64(7)}, 4, "flac") + if profile.BitDepth != 24 || profile.SamplingRate != "" || profile.Quality != "HI_RES" { + t.Fatalf("unexpected profile: %+v", profile) + } +} + func qobuzSecretSig(requestTS, secret string) string { raw := "trackgetFileUrlformat_id27intentstreamtrack_id19512574" + requestTS + secret hash := md5.Sum([]byte(raw))