Posters: fetch TheMovieDB images at the shown size, not the original

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 16:00:34 +02:00
co-authored by Claude Opus 5.5
parent ca8c71fa52
commit a70daaa0a3
3 changed files with 42 additions and 2 deletions
+3
View File
@@ -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)
+22 -2
View File
@@ -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).
+17
View File
@@ -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