From 9618108f2a936918bfcd6c30b17e01180e31fbf5 Mon Sep 17 00:00:00 2001 From: lb-a Date: Thu, 30 Apr 2026 12:00:58 +0200 Subject: [PATCH] performance: exclude lyrics if they are not enabled --- internal/app/app.go | 4 ++++ internal/download/downloader.go | 3 +++ internal/provider/tidal/client.go | 39 +++++++++++++++++++++++-------- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index a347344..888f6a4 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -121,6 +121,10 @@ func New(cfg *config.Config) (*Main, error) { }, nil } +// downloaderMaxConnsPerHost picks the per-host idle connection cap for the +// shared download client. We floor at 16 so artwork/manifest fetches and +// concurrent track downloads to the same CDN host can reuse keep-alive +// sockets even when the user configured a tiny max_connections. func downloaderMaxConnsPerHost(maxConnections int) int { if maxConnections > 16 { return maxConnections diff --git a/internal/download/downloader.go b/internal/download/downloader.go index e9a3b44..4daa53d 100644 --- a/internal/download/downloader.go +++ b/internal/download/downloader.go @@ -31,6 +31,9 @@ type Downloader struct { barStarted atomic.Int32 } +// downloadBufferSize sizes HTTP-to-disk copies. 1 MiB cuts read/write syscalls +// ~32x vs Go's default 32 KiB io.Copy buffer, which matters for multi-MB FLAC +// streams off CDNs that can sustain high per-connection throughput. const downloadBufferSize = 1 << 20 func New() *Downloader { diff --git a/internal/provider/tidal/client.go b/internal/provider/tidal/client.go index d411991..a49c180 100644 --- a/internal/provider/tidal/client.go +++ b/internal/provider/tidal/client.go @@ -209,12 +209,17 @@ func (c *Client) GetMetadata(ctx context.Context, item, mediaType string) (map[s if album, ok := resp["album"].(map[string]any); ok { enrichTidalImage(album) } - if lyrics, lrc := c.fetchTrackLyrics(ctx, item); lyrics != "" || lrc != "" { - if lyrics != "" { - resp["lyrics"] = lyrics - } - if lrc != "" { - resp["lyrics_synced"] = lrc + // Lyrics live on a separate endpoint, so fetching them costs an extra + // rate-limited roundtrip per track. Users who don't embed lyrics can + // opt out via metadata.exclude = ["lyrics"]. + if !c.lyricsExcluded() { + if lyrics, lrc := c.fetchTrackLyrics(ctx, item); lyrics != "" || lrc != "" { + if lyrics != "" { + resp["lyrics"] = lyrics + } + if lrc != "" { + resp["lyrics_synced"] = lrc + } } } } @@ -222,6 +227,15 @@ func (c *Client) GetMetadata(ctx context.Context, item, mediaType string) (map[s return resp, nil } +func (c *Client) lyricsExcluded() bool { + for _, k := range c.cfg.Session.Metadata.Exclude { + if strings.EqualFold(strings.TrimSpace(k), "lyrics") { + return true + } + } + return false +} + func (c *Client) fetchTrackLyrics(ctx context.Context, trackID string) (string, string) { params := url.Values{} params.Set("deviceType", "PHONE") @@ -277,10 +291,11 @@ func (c *Client) GetDownloadable(ctx context.Context, trackID string, quality in } if c.cfg.Session.Tidal.PreferAtmos { - if c.trackSupportsAtmos(ctx, trackID) { - if d, _ := c.getAtmosDownloadable(ctx, trackID); d != nil { - return d, nil - } + // No tracks/{id} pre-check: getAtmosDownloadable already validates + // each candidate response via playbackLooksAtmos and falls back + // through the format-specific trackManifests paths. + if d, _ := c.getAtmosDownloadable(ctx, trackID); d != nil { + return d, nil } } @@ -295,6 +310,10 @@ func (c *Client) GetDownloadable(ctx context.Context, trackID string, quality in } if status == http.StatusOK { if d := downloadableFromPlaybackManifest(resp); d != nil { + // Tidal's playbackinfo sometimes returns an m4a (HIGH/AAC) + // stream even when LOSSLESS+ was requested. There is no + // lossless m4a tier, so retry via the openAPI v2 manifest + // before settling for the downgrade. if quality >= 2 && d.Extension == "m4a" { if strict, strictErr := c.getDownloadableFromTrackManifest(ctx, trackID, quality); strictErr == nil && strict != nil { return strict, nil