fix tidal lossless quality negotiation and atmos format fallback

This commit is contained in:
2026-04-23 23:53:41 +02:00
parent c1e89f5876
commit d5b336ca4e
2 changed files with 108 additions and 12 deletions

View File

@@ -37,12 +37,12 @@ var qualityMap = map[int]string{
4: "HI_RES_LOSSLESS",
}
var qualityToFormat = map[int]string{
0: "HEAACV1",
1: "AACLC",
2: "FLAC",
3: "FLAC_HIRES",
4: "FLAC_HIRES",
var qualityToFormats = map[int][]string{
0: {"HEAACV1"},
1: {"HEAACV1", "AACLC"},
2: {"HEAACV1", "AACLC", "FLAC"},
3: {"HEAACV1", "AACLC", "FLAC", "FLAC_HIRES"},
4: {"HEAACV1", "AACLC", "FLAC", "FLAC_HIRES"},
}
var atmosAudioQualities = []string{"HI_RES_LOSSLESS", "HI_RES", "LOSSLESS", "HIGH"}
@@ -54,6 +54,7 @@ type Client struct {
http *http.Client
limiter *ratelimit.Limiter
baseURL string
openAPI string
loggedIn bool
}
@@ -63,6 +64,7 @@ func New(cfg *config.Config) *Client {
http: netutil.NewHTTPClient(30*time.Second, cfg.Session.Downloads.VerifySSL),
limiter: ratelimit.New(cfg.Session.Downloads.RequestsPerMinute),
baseURL: baseURL,
openAPI: openAPIV2,
}
}
@@ -263,6 +265,11 @@ func (c *Client) GetDownloadable(ctx context.Context, trackID string, quality in
}
if status == http.StatusOK {
if d := downloadableFromPlaybackManifest(resp); d != nil {
if quality >= 2 && d.Extension == "m4a" {
if strict, strictErr := c.getDownloadableFromTrackManifest(ctx, trackID, quality); strictErr == nil && strict != nil {
return strict, nil
}
}
return d, nil
}
}
@@ -475,19 +482,23 @@ func (c *Client) fetchArtistAlbums(ctx context.Context, artistID string) ([]map[
}
func (c *Client) getDownloadableFromTrackManifest(ctx context.Context, trackID string, quality int) (*provider.Downloadable, error) {
format := qualityToFormat[quality]
return c.getDownloadableFromTrackManifestForFormat(ctx, trackID, format)
formats := formatsForQuality(quality, c.cfg.Session.Tidal.PreferAtmos)
return c.getDownloadableFromTrackManifestForFormats(ctx, trackID, formats)
}
func (c *Client) getDownloadableFromTrackManifestForFormat(ctx context.Context, trackID, format string) (*provider.Downloadable, error) {
return c.getDownloadableFromTrackManifestForFormats(ctx, trackID, []string{format})
}
func (c *Client) getDownloadableFromTrackManifestForFormats(ctx context.Context, trackID string, formats []string) (*provider.Downloadable, error) {
params := url.Values{}
params.Set("manifestType", "MPEG_DASH")
params.Set("formats", format)
params.Set("formats", strings.Join(formats, ","))
params.Set("uriScheme", "HTTPS")
params.Set("usage", "PLAYBACK")
params.Set("adaptive", "false")
resp, status, err := c.apiRequest(ctx, "trackManifests/"+trackID, params, openAPIV2)
resp, status, err := c.apiRequest(ctx, "trackManifests/"+trackID, params, c.openAPI)
if err != nil {
return nil, err
}
@@ -507,9 +518,9 @@ func (c *Client) getDownloadableFromTrackManifestForFormat(ctx context.Context,
if uri == "" {
return nil, errors.New("tidal trackManifests missing uri")
}
formats, _ := attrs["formats"].([]any)
attrFormats, _ := attrs["formats"].([]any)
ext := "m4a"
for _, f := range formats {
for _, f := range attrFormats {
fv := strings.ToUpper(stringify(f))
if strings.Contains(fv, "FLAC") {
ext = "flac"
@@ -523,6 +534,18 @@ func (c *Client) getDownloadableFromTrackManifestForFormat(ctx context.Context,
return &provider.Downloadable{URL: uri, Extension: ext, Source: "tidal"}, nil
}
func formatsForQuality(quality int, preferAtmos bool) []string {
base, ok := qualityToFormats[quality]
if !ok {
base = qualityToFormats[0]
}
out := append([]string(nil), base...)
if preferAtmos {
out = append(out, "EAC3_JOC")
}
return out
}
func (c *Client) GetVideoDownloadable(ctx context.Context, videoID string) (*provider.Downloadable, error) {
if !c.loggedIn {
return nil, errors.New("tidal client not logged in")