Skip to content

superuser404notfound/jellyfin-plugin-language-aware-images

Repository files navigation

Language-Aware Images

A drop-in TMDB image provider for Jellyfin that gets two long-standing image issues out of the way:

  1. 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.
  2. 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.

Install

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.

Configuration

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).

Note on the image picker

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.

Why

Posters / backdrops / logos

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:

  1. Images in the library's language
  2. English fallback (configurable)
  3. 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.

Episode images for shows with alternative orderings

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.

Support

If this plugin saves you time and you'd like to say thanks, there's a Ko-fi.

License

GPL-3.0 (the plugin links against Jellyfin's GPL assemblies).

About

Drop-in TMDB image provider for Jellyfin: library-language posters with clean English fallback (no more textless), and episode images matched by title for shows with alternative orderings (Bluey, Star Trek, Doctor Who Classic).

Topics

Resources

License

Stars

8 stars

Watchers

1 watching

Forks

Packages

 
 
 

Contributors