# 03 — Subtitles on Jellyfin (arrflix.s8n.ru)

Last updated: 2026-05-08
Server: Jellyfin 10.10.3 (X64) on nullstone, container `jellyfin`
URL: <https://arrflix.s8n.ru>
Use-case: Futurama (44 episodes), Polish audio, no embedded subs, automatic English subtitles required.

---

## 1. How Jellyfin resolves subtitles (priority order)

When a client requests playback, Jellyfin presents subtitle streams from three sources, in this order:

1. **Embedded** — subtitle tracks muxed inside the container (`mkv`, `mp4`, etc.). Codecs: `srt`, `ass`, `ssa`, `pgs`, `vobsub`, `dvd_subtitle`, `mov_text`. Always available.
2. **Sidecar** (external) — `.srt`, `.vtt`, `.ass`, `.ssa`, `.sub` files placed next to the video on disk. Discovered automatically on library scan.
3. **Downloaded** — fetched at scan time (or via "Find subtitles" UI) by a subtitle provider plugin (OpenSubtitles, etc.). Saved as a sidecar when `SaveSubtitlesWithMedia=true`.

A user's `SubtitleMode` setting (per-user) decides what is auto-selected at playback:

- `None` — never load subtitles by default.
- `Default` — load only if marked `IsDefault` in the file.
- `Always` — always load if any track in `SubtitleLanguagePreference` exists.
- `Smart` — load only when audio language differs from the user's audio preference.
- `OnlyForced` — load only forced tracks.

The s8n user is set to **`Always` + preference `eng`** (see § 6).

---

## 2. Current Futurama state (verified 2026-05-08)

```
Library:   TV Shows (Id 767bffe4f11c93ef34b805451a696a4e, path /media/tv)
Episodes:  44 (Futurama.s01e01.pl.mkv … s01e44? — actually s01–s07 PL dub split)
Container: mkv
Audio:     ac3 stereo 192k, language=pol
Video:     h264 1080p (the eng tag on the video stream is a mux artefact, not English audio)
Subs:      0 embedded, 0 sidecar — verified on episodes 1–5; assume same for all 44
```

Result: every episode needs subs from OpenSubtitles **or** sidecars dropped on disk. There is nothing to extract — `mkvextract`/`ffmpeg` would yield nothing.

---

## 3. OpenSubtitles plugin — what is installed, what is pending

### 3.1 Installed (done by automation 2026-05-08)

| Item | Value |
|---|---|
| Plugin | **Open Subtitles** v20.0.0.0 |
| GUID | `4b9ed42f-5185-48b5-9803-6ff2989014c4` |
| Status | `Active` (after `docker restart jellyfin`, ~5 s downtime) |
| Repo | Official `https://repo.jellyfin.org/files/plugin/manifest.json` (already configured) |
| API endpoint | `https://api.opensubtitles.com/api/v1` (REST, NOT the legacy XML-RPC at .org) |
| API key | **Embedded in the plugin binary** — user does NOT supply one |

### 3.2 Why v20 and not v24

| Plugin version | targetAbi | Compatible with 10.10.3? |
|---|---|---|
| v24, v23, v22, v21 | 10.11.x | **NO** — ABI too new, will not load |
| **v20** | **10.9.0.0** | **YES** — installed |
| v19 and older | 10.8.x | yes but lacks recent fixes |

When the server is upgraded to 10.11.x, switch to v24 via:

```bash
curl -s -X POST -H "X-Emby-Token: $TOKEN" \
  "https://arrflix.s8n.ru/Packages/Installed/Open%20Subtitles?AssemblyGuid=4b9ed42f-5185-48b5-9803-6ff2989014c4&Version=24.0.0.0&RepositoryUrl=https%3A%2F%2Frepo.jellyfin.org%2Ffiles%2Fplugin%2Fmanifest.json"
docker restart jellyfin
```

### 3.3 The `.com` vs `.org` distinction (READ THIS)

OpenSubtitles split into two services in 2021:

- **opensubtitles.org** — legacy site, legacy XML-RPC API. Old Jellyfin plugin (v16 and below) used this. **DEAD** for new code.
- **opensubtitles.com** — new site, new REST API. Jellyfin plugin v17+ (including the v20 we have) uses this exclusively.

User accounts: an opensubtitles.org account does NOT automatically work on .com. Existing .org users must:
1. Visit <https://www.opensubtitles.com/en/users/sign_up>.
2. Sign up with the same email — the system will offer to import the .org account.
3. Reset password (mandatory; the .org password hash is incompatible).

User does NOT need to obtain an API key. The plugin embeds its own key (verified by reading `OpenSubtitlesPlugin.ApiKey` in `RequestHandler.cs` of v20). Free accounts get **20 downloads/day**; VIP accounts get more.

### 3.4 Pending — user supplies credentials

After signup at opensubtitles.com, save creds via API:

```bash
TOKEN=<JELLYFIN_API_TOKEN>
USER='your-opensubtitles-com-username'
PASS='your-opensubtitles-com-password'

# 1. Validate (returns 200 on success, 401 on bad creds)
curl -s -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \
  -d "{\"Username\":\"$USER\",\"Password\":\"$PASS\"}" \
  "https://arrflix.s8n.ru/Jellyfin.Plugin.OpenSubtitles/ValidateLoginInfo" -w "\nHTTP %{http_code}\n"

# 2. Persist into plugin config
curl -s -X POST -H "X-Emby-Token: $TOKEN" -H "Content-Type: application/json" \
  -d "{\"Username\":\"$USER\",\"Password\":\"$PASS\",\"CredentialsInvalid\":false}" \
  "https://arrflix.s8n.ru/Plugins/4b9ed42f-5185-48b5-9803-6ff2989014c4/Configuration" -w "\nHTTP %{http_code}\n"
```

Or via UI: `Dashboard → Plugins → Open Subtitles → Settings`.

A failed validate currently returns `{"Message":"Error, invalid username/password failed:5 remaining:5"}` HTTP 401 — the embedded API key is fine, only the user creds are missing.

---

## 4. Library + user configuration (already applied)

### 4.1 Library options — `TV Shows` (and `Movies`)

`POST /Library/VirtualFolders/LibraryOptions` was issued with:

```json
{
  "Id": "767bffe4f11c93ef34b805451a696a4e",
  "LibraryOptions": {
    "SubtitleDownloadLanguages": ["eng"],
    "RequirePerfectSubtitleMatch": false,
    "SkipSubtitlesIfAudioTrackMatches": true,
    "SkipSubtitlesIfEmbeddedSubtitlesPresent": false,
    "SaveSubtitlesWithMedia": true,
    "AllowEmbeddedSubtitles": "AllowAll"
  }
}
```

Effect:

- **`SubtitleDownloadLanguages: ["eng"]`** — primary trigger. Any new scan or "Refresh metadata" will fetch English subs for items lacking them.
- **`RequirePerfectSubtitleMatch: false`** — accept good-match subs even when filename hashes don't line up (Polish-dub `Futurama.s01e01.pl.mkv` will not byte-match an English .srt anywhere on the planet).
- **`SkipSubtitlesIfAudioTrackMatches: true`** — never fetch a language already present as an audio track (no English audio here, so no effect; safe to leave on).
- **`SaveSubtitlesWithMedia: true`** — write `.eng.srt` next to the `.mkv` instead of caching in the metadata folder. Survives library wipes and is portable.

### 4.2 Per-user playback prefs — user `s8n`

`POST /Users/2be0f0d3fe3a45dc9298138a15a01925/Configuration`:

```json
{
  "AudioLanguagePreference": "pol",
  "SubtitleLanguagePreference": "eng",
  "SubtitleMode": "Always"
}
```

Result: every Futurama episode auto-plays Polish audio + auto-loads English subs.

Other users (if any) need the same change individually — there is no global "default new user" subtitle prefs in 10.10.x; set on each user.

---

## 5. Triggering download for the existing 44 episodes

Adding `SubtitleDownloadLanguages` does NOT retroactively fetch subs for items already scanned. After credentials are saved, force a search:

### 5.1 Single episode (test path — episode `Futurama.s01e01.pl`)

```bash
EP=2b73bc176fbf8a02bb9bea9015ec13c6

# Query providers
curl -s -H "X-Emby-Token: $TOKEN" \
  "https://arrflix.s8n.ru/Items/$EP/RemoteSearch/Subtitles/eng" | jq .
# Returns array of SubtitleInfo objects: Id, ProviderName, Format, Comment, IsHashMatch, ...

# Pick one, e.g. SUBID = first result's Id
SUBID="opensubtitles_..."

# Download + save next to media
curl -s -X POST -H "X-Emby-Token: $TOKEN" \
  "https://arrflix.s8n.ru/Items/$EP/RemoteSearch/Subtitles/$SUBID" -w "HTTP %{http_code}\n"
# 204 = saved. File appears as Futurama.s01e01.pl.eng.srt next to the mkv.
```

While unauthenticated, `RemoteSearch` returned `[]` — confirms credentials are the only blocker.

### 5.2 All 44 — refresh metadata for the whole series

Easiest is a series-level refresh once creds are entered:

```bash
SERIES=156e57437f795e5c8cd80fc98bafaee0  # Futurama
curl -s -X POST -H "X-Emby-Token: $TOKEN" \
  "https://arrflix.s8n.ru/Items/$SERIES/Refresh?MetadataRefreshMode=FullRefresh&ImageRefreshMode=Default&ReplaceAllMetadata=false&ReplaceAllImages=false&Recursive=true" \
  -w "HTTP %{http_code}\n"
```

`MetadataRefreshMode=FullRefresh` triggers subtitle download for items missing matching language. This will take a few minutes and is rate-limited by the OpenSubtitles 20-downloads/day cap on free accounts. **44 episodes exceeds that** — plan for 3 days, or upgrade to VIP, or use sidecars (§ 7).

---

## 6. Per-user vs library default — who wins

| Setting | Lives in | Effect |
|---|---|---|
| `SubtitleDownloadLanguages` (library) | Library options | What languages are *fetched* and *saved to disk* |
| `SubtitleLanguagePreference` (user) | User config | Which existing track is *auto-selected at playback* |
| `SubtitleMode` (user) | User config | When to auto-display (`Always` / `Default` / `OnlyForced` / `Smart` / `None`) |
| `AudioLanguagePreference` (user) | User config | Which audio track is auto-selected |

A user's preferences are *display-time* only — they cannot trigger a download. A library's `SubtitleDownloadLanguages` is *scan-time* only — it does not affect what plays. You need both, and they should agree.

---

## 7. Sidecar subtitles — naming convention

If/when subs come from OpenSubtitles, Jellyfin saves them as sidecars (because `SaveSubtitlesWithMedia=true`). You can also drop your own. Filename pattern:

```
<videobasename>.<lang>[.flag].<ext>
```

Examples for `Futurama.s01e01.pl.mkv`:

| Filename | Meaning |
|---|---|
| `Futurama.s01e01.pl.en.srt` | English, regular |
| `Futurama.s01e01.pl.eng.srt` | English (ISO-639-2 code, also accepted) |
| `Futurama.s01e01.pl.en.forced.srt` | English forced (only foreign-language scenes) |
| `Futurama.s01e01.pl.en.sdh.srt` | English SDH (deaf/hard-of-hearing — includes [music], [door slams]) |
| `Futurama.s01e01.pl.en.cc.srt` | English closed captions (alias of SDH in Jellyfin) |
| `Futurama.s01e01.pl.en.default.srt` | Marked default — auto-selects regardless of `SubtitleMode` |
| `Futurama.s01e01.pl.en.forced.default.srt` | Forced AND default (flags compose, any order) |
| `Futurama.s01e01.pl.en.ass` | Advanced SubStation Alpha — supports styling/positions |
| `Futurama.s01e01.pl.en.ssa` | SubStation Alpha — older, also supported |
| `Futurama.s01e01.pl.en.vtt` | WebVTT — supported |
| `Futurama.s01e01.pl.en.sub` + `.idx` | VobSub (DVD bitmap subs) |

**Language codes:** Jellyfin accepts both ISO-639-1 (`en`, `pl`, `de`) and ISO-639-2/T (`eng`, `pol`, `deu`). Either works for filename matching — the parser canonicalises to 639-2 internally.

**Region tags** like `en-US`, `pt-BR` are accepted and shown to the user but treated as the bare language for matching.

After dropping sidecars, trigger a library scan:

```bash
curl -s -X POST -H "X-Emby-Token: $TOKEN" "https://arrflix.s8n.ru/Library/Refresh"
```

Or per-item: `POST /Items/{id}/Refresh`.

---

## 8. Extracting embedded subs to sidecars (irrelevant for Futurama, but documented)

If a future series has embedded but slow-loading PGS/SSA subs, extract once with `ffmpeg`:

```bash
# List streams
ffmpeg -i input.mkv 2>&1 | grep Subtitle

# Extract stream index 2 (first sub) to .srt — works for srt/ass/mov_text
ffmpeg -i input.mkv -map 0:s:0 -c:s copy input.en.srt

# For PGS or VobSub bitmap subs, copy keeps them as-is (rename .sup):
ffmpeg -i input.mkv -map 0:s:0 -c:s copy input.en.sup
# Then OCR with subtitleedit-cli or pgs2srt to convert to text srt.
```

Or with `mkvextract` (mkvtoolnix-cli):

```bash
mkvmerge -i input.mkv      # lists tracks with IDs
mkvextract tracks input.mkv 2:input.en.srt
```

Jellyfin also has a built-in plugin **Subtitle Extract** (manifest GUID `cd893c24-b59e-4060-87b2-184070e1bf68`, latest v7.0.0.0 needs ABI 10.11.2.0 — wait until 10.11 upgrade). It extracts on the fly and caches in `metadata/Subtitles/`.

---

## 9. Auto-download settings cheatsheet

| Field | Where set | Recommended value | Why |
|---|---|---|---|
| `SubtitleDownloadLanguages` | Library options | `["eng"]` | Trigger downloads in English |
| `RequirePerfectSubtitleMatch` | Library options | `false` | Don't insist on hash-match for foreign-dub releases |
| `SkipSubtitlesIfEmbeddedSubtitlesPresent` | Library options | `false` | Allow override fetch even if embedded subs exist (e.g. if embedded are forced-only) |
| `SkipSubtitlesIfAudioTrackMatches` | Library options | `true` | Don't fetch English subs for English audio (no waste) |
| `SaveSubtitlesWithMedia` | Library options | `true` | Subs saved as sidecars on disk; portable; survives metadata wipe |
| `AllowEmbeddedSubtitles` | Library options | `AllowAll` | Default; `AllowText` excludes PGS bitmap |
| `MaxResults` | (n/a in v20 plugin config) | — | The plugin returns the provider's full list; client picks |
| `IsAutomated` | (n/a, removed) | — | Older plugins had a flag; v20 always auto-fetches when a library scan finds an item missing the configured language |

---

## 10. Troubleshooting

| Symptom | Cause / fix |
|---|---|
| `RemoteSearch` returns `[]` for every episode | Creds missing / wrong (HTTP 401 in plugin logs). Re-validate via `ValidateLoginInfo`. |
| `429 Too Many Requests` in logs | Hit the 20/day quota on free account. Wait 24 h, upgrade to VIP, or fall back to sidecars. |
| Subs found but in wrong language | OpenSubtitles can mislabel — set `RequirePerfectSubtitleMatch: true` to filter, or pick manually via UI. |
| `.srt` on disk but Jellyfin doesn't show it | Filename language token doesn't match. Use `.en.srt` not `.english.srt`. Trigger library scan. |
| Subs show but client doesn't auto-display | User-side. Set `SubtitleMode: Always` and `SubtitleLanguagePreference: eng`. |
| Embedded subs preferred over downloaded | Expected — embedded come first in the priority order. Use the player's track switcher, or remux without subs. |
| `CredentialsInvalid: true` keeps reappearing | Plugin auto-flips this on a 401. Re-enter creds (likely changed on opensubtitles.com) and reset to `false`. |
| Plugin v24 install but stuck on `Restart` forever after upgrade | Server still on 10.10.x — v24 needs ABI 10.11. Reinstall v20. |

Plugin logs: `docker logs jellyfin 2>&1 | grep -i opensubtitles`.

---

## 11. Summary — Futurama after this work

| Step | State |
|---|---|
| Plugin **Open Subtitles v20.0.0.0** | Installed + Active |
| TV library `SubtitleDownloadLanguages` | `["eng"]` |
| TV library `RequirePerfectSubtitleMatch` | `false` |
| TV library `SaveSubtitlesWithMedia` | `true` |
| User `s8n` `SubtitleMode` | `Always` |
| User `s8n` `SubtitleLanguagePreference` | `eng` |
| User `s8n` `AudioLanguagePreference` | `pol` |
| OpenSubtitles **credentials** | **PENDING — user signs up at <https://www.opensubtitles.com>** |
| Series refresh to fetch all 44 | **PENDING — after creds entered** |

When the user enters creds and runs the series refresh in § 5.2, expect ~20 episodes downloaded the first day (free quota), the rest over the next two days unless upgraded. Sidecar filenames will be `Futurama.s01eXX.pl.eng.srt` next to each `.mkv`.
