A drop-in TMDB image provider for Jellyfin that gets two long-standing image issues out of the way:
- Posters in your library's language with a clean fallback to English, no more textless no-language posters as the built-in provider's first fallback.
- Episode images that match your library's order for shows like Bluey, Star Trek or Doctor Who Classic where TVDB and TMDB disagree on which episode lives at which (Season, Episode) position. Matched by title, not by index, so the image is bound to the episode itself rather than its slot.
Also from the same dev: Sodalite, a native Apple TV client for Jellyfin with first-class Jellyseerr integration (TestFlight beta). Built on AetherEngine, a reusable video player engine for iOS, tvOS and macOS.
In Jellyfin: Admin → Plugins → Repositories → + and add
https://raw.githubusercontent.com/superuser404notfound/jellyfin-plugin-language-aware-images/main/manifest.json
Then Catalog → Metadata → Language-Aware Images → Install. Restart the server.
After install, go to Admin → Library → (your library) → Image Fetchers and drag Language-Aware TMDB Images to the top, otherwise the built-in provider still wins.
Admin → Plugins → Language-Aware Images:
| Field | Default | Notes |
|---|---|---|
PreferredLanguageOverride |
empty | Empty = each library's language (a regional value like pt-BR auto-expands to pt-br,pt). Or an ordered list e.g. pt-br,pt,pt-pt. |
FallbackLanguage |
en |
Comma-separated fallback list, e.g. en or en,fr. Used when no preferred-language image exists. |
IncludeOriginalLanguage |
false |
Add the title's original language as an extra bucket (e.g. Japanese for anime). |
OriginalLanguageLast |
true |
Original-language bucket goes after the fallback (dead-last) so international names win; off = between preferred and fallback. Only applies when IncludeOriginalLanguage is on. |
OnlyOriginalLanguageForPosters |
false |
Strict mode: posters only in original language, drops all other buckets. Backdrops/logos unaffected. |
IncludeNoLanguageForPosters |
false |
Allow textless posters as last resort. |
IncludeNoLanguageForBackdrops |
true |
Backdrops are usually language-agnostic anyway. |
IncludeNoLanguageForLogos |
true |
Most studio logos are designed without text. |
PreferNoLanguageForPosters |
false |
Rank textless posters above titled art. Needs IncludeNoLanguageForPosters. |
PreferNoLanguageForBackdrops |
false |
Rank textless backdrops first (clean, no burned-in title). Needs IncludeNoLanguageForBackdrops. |
PreferNoLanguageForLogos |
false |
Rank textless logos first. Needs IncludeNoLanguageForLogos. |
SortByVotes |
true |
Sort by vote_count (TMDB UI behavior). Off = sort by vote_average. |
MinimumVoteCount |
0 |
Drops images with fewer votes. 0 = keep everything (recommended). |
MatchEpisodeImagesByTitle |
true |
Episode images by title-lookup, not by (S,E) position. Fixes alternative-order shows. |
PosterImageSize |
original |
TMDB poster size: w92, w154, w185, w342, w500, w780, original. |
BackdropImageSize |
original |
TMDB backdrop size: w300, w780, w1280, original. |
LogoImageSize |
original |
TMDB logo size: w45, w92, w154, w185, w300, w500, original. |
StillImageSize |
original |
TMDB episode still size: w92, w185, w300, original. |
TmdbApiKey |
empty | Bring your own TMDB key. Empty = uses Jellyfin's bundled key. |
The bucket order is preferred, fallback, then original (opt-in, dead-last by
default, see OriginalLanguageLast), then textless (opt-in per type, or moved
to the very top instead when PreferNoLanguageFor* is on for that type), with a
vote_count DESC tiebreak within each bucket. The exact order is enforced
through Jellyfin's own downstream image sort, so the picker shows synthetic
ratings rather than TMDB's and labels every image with the library language
even when it is really a regional variant or the original language (see "Note on
the image picker" below).
To make its ordering survive Jellyfin's own downstream image sort and filter, this plugin tags every image it returns with the library's metadata language and encodes the bucket rank as a synthetic community rating. Two visible consequences, both by design and not bugs:
- In the Edit Images dialog, images are labelled with the library language (and show synthetic ratings) even when the artwork is really English, textless, or another language. Changing the library's preferred download language changes those labels accordingly.
- The dialog's "All languages" toggle has no visible effect, because every image already carries the library-language tag its filter keys on.
This is the deliberate trade-off that guarantees the exact bucket order you configured. If you would rather see real per-image language tags and give up the ordering guarantee, use Jellyfin's built-in TMDB image provider instead.
Jellyfin's built-in TMDB provider respects the library language for language-matched images, but when no match exists it prefers textless (no-language-tag) ones over the English fallback, jellyfin/jellyfin#9878. Textless posters on TMDB are often awkwardly chosen: cropped stills, alternate art, foreign-market exports without text. The result is a library that looks visually inconsistent.
This plugin enforces a clean cascade:
- Images in the library's language
- English fallback (configurable)
- Textless, only if you opt in per image type (useful for logos, off by default for posters)
Within each bucket, images are sorted by vote_count DESC, vote_average DESC,
the same order TMDB's /images UI uses, so you get the most popular image
in the matching language rather than a random one.
Regional variants. TMDB tags some posters by region (pt-BR vs pt-PT)
but only exposes the region through its image-language filter, not the
response. The plugin therefore issues one TMDB call per regional code in your
cascade and merges by image, so a Brazilian library gets the pt-br poster
rather than the pt (Portugal) one. Plain 2-letter cascades keep the original
single-call behaviour.
If you watch Bluey in Disney+ order, Star Trek in chronological order, or
Doctor Who Classic in DVD order, your library's (Season, Episode) numbering
won't line up with TMDB's. The built-in TMDB image provider asks for "S2E5"
literally and pulls the still that TMDB has at that position, which is the
wrong episode entirely. TMDB's own episode_groups API exists in theory but
is community-edited and frequently inaccurate.
This plugin instead looks up the still by episode title: it fetches all TMDB episodes of the show once (cached per (show, language)) and matches your local episode title against TMDB's. Image is bound to title, not to position, so it works regardless of which order your library is in.
The match is title-first, position-never: even for in-sync libraries where the title also happens to live at the local (S, E) position, the plugin returns the title-matched still tagged with your preferred language so it wins Jellyfin's downstream sort over the built-in provider's positional still. This way you always get the language-aware entry at the top of the image picker.
Title normalisation handles the most common variations (case, leading
articles in German and English, punctuation, whitespace). Combined episode
titles ("A / B", common in altdvd-style libraries where one entry covers
two TMDB single-sketch episodes) are split on / and each half is tried;
the first half's still is returned for the combined entry. If no candidate
matches at all the plugin returns nothing and the built-in provider takes
over, no regression.
If this plugin saves you time and you'd like to say thanks, there's a Ko-fi.
GPL-3.0 (the plugin links against Jellyfin's GPL assemblies).