diff --git a/CHANGELOG.md b/CHANGELOG.md index 289c570..d37a59e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- **Posters of titles not in the library load faster:** they are fetched from TheMovieDB at the + size shown (780 pixels wide for real images) instead of the original Radarr names, which is often + 2000×3000 and over 2 MB: about a tenth of the download and a sixth of the decoding time. - **Real-image posters fill their box in every terminal:** they are scaled to the exact pixel size of their cells before they are sent. Ghostty shows an image that is smaller than its place at its own size, so library posters (Radarr's copy is only 333×500, not 500×750 as assumed in 0.4.0) diff --git a/internal/arr/image.go b/internal/arr/image.go index dd0ad7b..42bc57f 100644 --- a/internal/arr/image.go +++ b/internal/arr/image.go @@ -35,7 +35,7 @@ func (c *Client) Poster(images []Image, inLibrary bool) (key string, fetch func( if err != nil && remote != "" { // The instance has no usable copy (not cached, or behind a login): // use the metadata provider's, as for titles not in the library. - if remoteData, remoteErr := FetchURL(ctx, remote); remoteErr == nil { + if remoteData, remoteErr := FetchURL(ctx, tmdbSized(remote, large)); remoteErr == nil { return remoteData, nil } } @@ -44,7 +44,9 @@ func (c *Client) Poster(images []Image, inLibrary bool) (key string, fetch func( } if img.RemoteURL != "" { remote := img.RemoteURL - return remote, func(ctx context.Context, _ bool) ([]byte, error) { return FetchURL(ctx, remote) }, true + return remote, func(ctx context.Context, large bool) ([]byte, error) { + return FetchURL(ctx, tmdbSized(remote, large)) + }, true } } return "", nil, false @@ -84,6 +86,24 @@ func (c *Client) localPoster(ctx context.Context, local string, large bool) ([]b return try(local) } +// tmdbSized asks TheMovieDB for a poster of the size that is shown instead of +// the original Radarr hands out (often 2000×3000 and over 2 MB, which takes +// longer to download and to decode than everything else together): 780 +// pixels wide for real images, 342 for block characters. Other URLs are +// returned as they are. +func tmdbSized(rawURL string, large bool) string { + u, err := url.Parse(rawURL) + if err != nil || u.Host != "image.tmdb.org" || !strings.HasPrefix(u.Path, "/t/p/original/") { + return rawURL + } + size := "w342" + if large { + size = "w780" + } + u.Path = "/t/p/" + size + strings.TrimPrefix(u.Path, "/t/p/original") + return u.String() +} + // scaledVariant turns /MediaCover/1/poster.jpg?x into // /MediaCover/1/poster-250.jpg?x (for width 250; Radarr and Sonarr keep 250 // and 500). diff --git a/internal/arr/image_test.go b/internal/arr/image_test.go index 84b6a0a..b73bec0 100644 --- a/internal/arr/image_test.go +++ b/internal/arr/image_test.go @@ -8,6 +8,23 @@ import ( "testing" ) +func TestTMDBSized(t *testing.T) { + for _, tc := range []struct { + in string + large bool + want string + }{ + {"https://image.tmdb.org/t/p/original/abc.jpg", true, "https://image.tmdb.org/t/p/w780/abc.jpg"}, + {"https://image.tmdb.org/t/p/original/abc.jpg", false, "https://image.tmdb.org/t/p/w342/abc.jpg"}, + {"https://image.tmdb.org/t/p/w500/abc.jpg", true, "https://image.tmdb.org/t/p/w500/abc.jpg"}, + {"https://artworks.thetvdb.com/banners/posters/1.jpg", true, "https://artworks.thetvdb.com/banners/posters/1.jpg"}, + } { + if got := tmdbSized(tc.in, tc.large); got != tc.want { + t.Errorf("tmdbSized(%q, %v) = %q, want %q", tc.in, tc.large, got, tc.want) + } + } +} + func TestScaledVariant(t *testing.T) { for _, tc := range []struct { in string