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:
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
## Unreleased
|
## 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
|
- **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
|
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)
|
own size, so library posters (Radarr's copy is only 333×500, not 500×750 as assumed in 0.4.0)
|
||||||
|
|||||||
+22
-2
@@ -35,7 +35,7 @@ func (c *Client) Poster(images []Image, inLibrary bool) (key string, fetch func(
|
|||||||
if err != nil && remote != "" {
|
if err != nil && remote != "" {
|
||||||
// The instance has no usable copy (not cached, or behind a login):
|
// The instance has no usable copy (not cached, or behind a login):
|
||||||
// use the metadata provider's, as for titles not in the library.
|
// 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
|
return remoteData, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,9 @@ func (c *Client) Poster(images []Image, inLibrary bool) (key string, fetch func(
|
|||||||
}
|
}
|
||||||
if img.RemoteURL != "" {
|
if img.RemoteURL != "" {
|
||||||
remote := 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
|
return "", nil, false
|
||||||
@@ -84,6 +86,24 @@ func (c *Client) localPoster(ctx context.Context, local string, large bool) ([]b
|
|||||||
return try(local)
|
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
|
// scaledVariant turns /MediaCover/1/poster.jpg?x into
|
||||||
// /MediaCover/1/poster-250.jpg?x (for width 250; Radarr and Sonarr keep 250
|
// /MediaCover/1/poster-250.jpg?x (for width 250; Radarr and Sonarr keep 250
|
||||||
// and 500).
|
// and 500).
|
||||||
|
|||||||
@@ -8,6 +8,23 @@ import (
|
|||||||
"testing"
|
"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) {
|
func TestScaledVariant(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
in string
|
in string
|
||||||
|
|||||||
Reference in New Issue
Block a user