Skip to content

Subtitle selection uses different language matching on transcode vs remux paths #2026

Description

@chrisbenincasa

Found during a backend architecture review. Not user-reported. Likely related to #1960.

Subtitle picking is implemented twice, and both are live in the same file on different branches:

  • Transcode pathFfmpegStreamFactory.ts:518StreamSelectorStreamSelectionEvaluator.resolveSubtitleAction (server/src/ffmpeg/StreamSelectionEvaluator.ts:177-277)
  • Passthrough/remux pathFfmpegStreamFactory.ts:634SubtitleStreamPicker.pickSubtitles (server/src/ffmpeg/SubtitleStreamPicker.ts:37-155)

The forced/default filter, the allowExternal and allowImageBased checks, and the embedded-text extraction fallback are near line-for-line identical between them. Both call the same helper getSubtitleDetailsWithExtractedPath (SubtitleStreamPicker.ts:157-196).

The language matching genuinely disagrees.

Evaluator (StreamSelectionEvaluator.ts:227-234) compares raw lowercased strings across three fields, with no normalization:

if (
  stream.languageCodeISO6392?.toLowerCase() !== langLower &&
  stream.languageCodeISO6391?.toLowerCase() !== langLower &&
  stream.language?.toLowerCase() !== langLower
) { continue; }

Picker (SubtitleStreamPicker.ts:91-101) normalizes through LanguageService.getAlpha3TCode and checks only one field:

if (
  stream.languageCodeISO6392 &&
  LanguageService.getAlpha3TCode(stream.languageCodeISO6392) !== pref.languageCode
) { continue; }

Two concrete consequences:

  1. The transcode path has no ISO 639-2/B → /T normalization. A deu/ger style mismatch is handled on the remux path and not on the transcode path. This looks like the same root cause as [Bug]: Reported audio language "deu" is not considered German #1960, on the other branch.
  2. The picker skips the language check entirely when languageCodeISO6392 is absent, so an untagged stream matches any requested language. The evaluator would reject it.

Net effect: the same program can get a different subtitle track depending on whether it is remuxed or transcoded.

Two smaller divergences: the picker has a preferTextBased ordering with an early return (:43-49, :138-140) that the evaluator lacks; and with no preferences the picker falls back to default-or-first (:50-73) while the evaluator's 'default' action returns null when nothing is flagged default (:189-191).

services/StreamSelectionProfileResolver.ts:177-179 already carries a comment acknowledging that it re-implements picker behaviour.

Fixing the language normalization on the transcode path is the immediate bug. Collapsing the two into one selector that both paths call with different policies is the longer-term fix.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendPertains to the Tunarr serverbugSomething isn't workingstreamingRelated to streaming features

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions