Fixture for this doc: TV Shows library, series **Futurama** (1999), 44 episodes split across S01–S03.
---
## 1. The bug we hit
User report: "the titles suck."
Symptoms (verified via `/Items` REST query):
| Field | Before |
|------------------|----------------------------|
| `Name` | `Futurama.s01e01.pl` (raw filename) |
| `ProviderIds` | `{}` (empty) |
| `Overview` | `""` (empty) |
| `PremiereDate` | 2021-04-12 (file mtime, not airdate) |
| `ProductionYear` | 2021 (file mtime year, not 1999) |
| Series ProviderIds | `{}` — never matched on TMDB |
Root cause: the **Futurama series object had no provider IDs**, so episode-level metadata fetchers had nothing to query against. The library scan had ingested files purely from the path/filename parser; nothing had been pinned to a TMDB / TVDB show.
Why it happened: the library option `EnableInternetProviders` was reported as `false` by the API at the moment Futurama was first scanned, so the auto-identify step on first scan never ran. After enabling it via API + restart, the series still didn't auto-match because by then the per-item record existed without provider hints.
The fix is a two-step: **(a)** lock the series to TMDB id 615 via `RemoteSearch/Apply`, **(b)** trigger a recursive `Refresh` with `ReplaceAllMetadata=true` so all episodes pull from TMDB/TVDB using the locked series id.
---
## 2. How Jellyfin parses TV filenames
Jellyfin's TV file matcher is documented at https://jellyfin.org/docs/general/server/media/shows/ . It uses a regex chain in `Emby.Naming.TV.EpisodeResolver`. The supported episode-number patterns include (case-insensitive, dots and dashes equivalent to spaces):
-`Futurama` (top-level folder) → series name (used for TMDB search if no NFO/IDs)
-`Season 01` → season number (also accepts `Season 1`, `S01`, `Season.01`, …)
-`Futurama.s01e01.pl.mkv` → episode 1 of season 1; the trailing `.pl` is treated as a tag (language hint), not part of the title
Anything before the `s##e##` token is ignored; anything after is treated as an optional episode title segment, but Jellyfin will overwrite it from TMDB once identified.
---
## 3. Official naming conventions Jellyfin recommends
From the Jellyfin docs:
```
TV Shows
├── Show Name (Year) ← year disambiguates remakes (Futurama 1999 vs 2023)
│ ├── Season 00 ← specials
│ │ └── S00E01.mkv
│ ├── Season 01
│ │ ├── Show Name S01E01.mkv
│ │ ├── Show Name S01E02.mkv
│ │ └── Show Name S01E03-E04.mkv ← multi-episode file
│ └── Season 02
│ └── ...
```
Recommended for our case (to avoid future ambiguity with the 2023 reboot):
We did **not** rename on disk — instead we identified the series via API. Renaming would require either touching the source under `/home/admin/Downloads` (forbidden) or the bind-mount on nullstone (allowed but not necessary now that the API match is locked).
---
## 4. The Identify flow (REST API)
Jellyfin's Identify wizard has three calls. Auth header: `X-Emby-Token: <api-key>`.
Returns an array of candidates from each registered provider (TheMovieDb, TheTVDB, OMDb). Inspect `ProviderIds`, `ProductionYear`, `Overview`, `ImageUrl` to pick the right one.
For Episodes use `RemoteSearch/Episode` and pass `SeriesProviderIds` + `IndexNumber` + `ParentIndexNumber`. For Movies, `RemoteSearch/Movie`.
Response: `204 No Content` on success. **Note**: this endpoint runs synchronously for ~30–60 s while Jellyfin pulls images. The Traefik proxy in front of Jellyfin will return `502 Bad Gateway` if you don't bump `--max-time` above the proxy's idle threshold. Use `--max-time 60` from the client; the operation continues server-side regardless of the client timeout.
### 4.3 Trigger a metadata refresh
```bash
curl -s --max-time 60 -X POST -H "X-Emby-Token: $TOKEN" \
-`Recursive=true` — descend into seasons + episodes (otherwise only the series item refreshes)
-`MetadataRefreshMode=FullRefresh` — bypass cache, hit network
-`ReplaceAllMetadata=true` — overwrite local fields including title, overview, premiere date (this is what kills the `Futurama.s01e01.pl` placeholder names)
-`ReplaceAllImages=false` — keep existing images (set `true` if you also want to repull artwork)
The refresh is a fire-and-forget job. Poll for completion by re-querying episodes:
`LockedFields` tells subsequent refreshes to leave those fields alone — important if you want to keep your override across future scans.
### 5.2 Single episode via UI
Three-dot menu on the episode → **Identify** → enter known title or provider id → pick result → **OK**. Equivalent to the API flow above.
### 5.3 Locking entire series to a provider (as we did for Futurama)
Three-dot menu on the series → **Identify** → search by name → pick the right candidate (year matters!) → **OK**. Then three-dot → **Refresh metadata** → "Replace all metadata".
---
## 6. Language preferences and how they cascade
Three layers, most-specific wins:
1.**Server default** — `Dashboard > Display > Metadata language`. Affects libraries that don't set their own.
2.**Library** — `Dashboard > Libraries > <Library> > Manage > Preferred metadata language`. Stored in `/config/root/default/<Library>/options.xml` as `<PreferredMetadataLanguage>` and `<MetadataCountryCode>`.
3.**Item** — items can have `LockedFields` on individual fields; when a field is locked it ignores the library/server defaults for that field.
TMDB falls back to English automatically when a Polish translation doesn't exist, so this is generally safe. To override per-library, edit the library and change the dropdown — or via API:
Returns 204. **Side effect**: changing library options can trigger a container restart cycle on this Jellyfin install — saw it in our logs at the moment of update. Schedule this when no playback is active.
To override per-item to force English titles on a single show despite a Polish library, set `LockedFields` after manually editing the names — refreshes will then respect the override.
---
## 7. Multi-episode files (`S01E01-E02`)
Jellyfin handles a single file containing two consecutive episodes by:
1. Filename matches the multi-episode regex: `S01E01-E02`, `S01E01-E03`, etc.
2. Library DB creates **one** physical media item but exposes it as **N** virtual `Episode` rows (one per episode number in range), each pointing to the same `Path`.
3. Each virtual episode pulls its own metadata (title, overview, airdate) from TMDB.
4. Playback: clicking any of the N virtual episodes plays the same file from the start; users have to scrub manually.
If your file actually contains two episodes back-to-back, this is what you want. If it's a single mistitled file, rename it to a non-multi pattern.
For our Futurama set, all files are single-episode (`s01e01.pl.mkv`), so this didn't apply. But note: the original-broadcast Season 1 of Futurama is 13 episodes; our collection groups it as 9 / 20 / 15 = 44, which is the **production-order** split (Fox broadcast some S1 episodes during the S2 run). The user's filenames already reflect production order, and TMDB id 615 is keyed to production order, so episode names line up.
Overview: "Philip J. Fry przez przypadek hibernuje się i budzi w odległej przyszłości,
gdzie poznaje krewnego, profesora Hubert J. Farnswortha, który postanawia..."
```
Confirmed correct show: 1999 original Futurama (NOT the 2023 Hulu reboot, which is TMDB id 211410).
---
## 10. Operational notes and gotchas
- **Empty `EnableInternetProviders` in `options.xml`**: when the field is absent from XML, Jellyfin defaults to `true`. The `false` we saw in the API response on the initial `GET /Library/VirtualFolders` may have been a stale DTO state from an older config; the XML never had the override. Future-proof by saving options through the UI once.
- **`/Items/RemoteSearch/Apply/{id}` is slow.** It downloads images synchronously. Always use `--max-time 60` (or longer) and accept that a `502` from the proxy doesn't mean it failed — verify with a follow-up GET on the item.
- **No TMDB API key set** in `Jellyfin.Plugin.Tmdb.xml` — that's fine, the plugin ships with a built-in non-anon key. If you ever hit rate limits, set `<TmdbApiKey>` in `/config/plugins/configurations/Jellyfin.Plugin.Tmdb.xml`.
- **Restart sensitivity**: changing library options can trigger ffprobe re-scan of the whole library, observed in our logs. Schedule library option changes during low-use windows.
- **Open Subtitles plugin is throwing errors on every episode refresh** (`MediaBrowser.Providers.Subtitles.SubtitleManager: Error downloading subtitles from Open Subtitles`). Not blocking, but worth fixing in a future doc — likely needs an Open Subtitles account configured under the plugin settings.
- **Polish-as-default for media language is intentional.** TMDB's Polish coverage on classic American shows is good. Override per-library only if you find titles falling back to filenames (which means TMDB had no Polish entry — extremely rare for US prime-time shows).