Preserve external subtitle language names
Dreamio now keeps known OpenSubtitles language metadata attached to generic VLC subtitle tracks, so the native captions menu can show useful names instead of a stack of indistinguishable Track N entries.
Summary
Fixed the subtitle menu naming path for externally attached VLC subtitle tracks. Generic VLC names like Track 2 now receive Dreamio's preserved OpenSubtitles display name when available.
Changes Made
- Changed VLC subtitle display-name reconciliation so saved external subtitle names are only consumed by generic VLC track labels.
- Expanded subtitle language-code aliases for the ISO-639-2 codes observed in Stremio/OpenSubtitles payloads, including Dutch, Danish, Romanian, Serbian, Persian, Portuguese, Finnish, Hebrew, Hungarian, Turkish, Czech, Arabic, Greek, French, German, Spanish, and English.
- Added regression coverage for Dutch and Danish three-letter subtitle codes.
Context
The debug log showed OpenSubtitles candidates arriving with usable language metadata, then VLC eventually surfacing external subtitle tracks as Track 2, Track 3, and similar generic names. Dreamio already had a preservation queue, but it could spend a queued display name on a VLC track that already had a meaningful title, which left later generic tracks unnamed.
Important Implementation Details
The key behavioral change is deliberately narrow: reconciliation now skips non-generic VLC subtitle names. If VLC gives a real name such as English (SDH) - [English], Dreamio keeps it. If VLC gives Track 4, Dreamio can replace it with the next preserved external subtitle display name.
The alias table turns common three-letter subtitle codes into two-letter language codes before asking Locale for localized names. This makes labels such as nld and dan become Dutch and Danish instead of raw codes.
Relevant Diff Snippets
Dreamio/VLCNativePlaybackBackend.swift
328 unmodified lines329330331332333334328 unmodified lines.filter { $0.id >= 0 }.filter { !externalSubtitleBaselineTrackIDs.contains($0.id) }.filter { externalSubtitleDisplayNamesByTrackID[$0.id] == nil }.sorted { $0.id < $1.id }.forEach { track inguard !pendingExternalSubtitleDisplayNames.isEmpty else {328 unmodified lines329330331332333334335328 unmodified lines.filter { $0.id >= 0 }.filter { !externalSubtitleBaselineTrackIDs.contains($0.id) }.filter { externalSubtitleDisplayNamesByTrackID[$0.id] == nil }.filter { SubtitleDisplayName.isGenericLabel($0.name) }.sorted { $0.id < $1.id }.forEach { track inguard !pendingExternalSubtitleDisplayNames.isEmpty else {
Dreamio/StreamCandidate.swift
47 unmodified lines4849505152535455565758596047 unmodified lines]private static let languageCodeAliases = ["eng": "en","en": "en","spa": "es","es": "es","fre": "fr","fra": "fr","fr": "fr"]static func displayName(for candidate: SubtitleCandidate) -> String {47 unmodified lines48495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949547 unmodified lines]private static let languageCodeAliases = ["ara": "ar","ar": "ar","cze": "cs","ces": "cs","cs": "cs","dan": "da","da": "da","de": "de","deu": "de","ell": "el","el": "el","eng": "en","en": "en","fin": "fi","fi": "fi","spa": "es","es": "es","ger": "de","gre": "el","heb": "he","he": "he","hun": "hu","hu": "hu","fre": "fr","fra": "fr","fr": "fr","dut": "nl","nld": "nl","nl": "nl","per": "fa","fas": "fa","fa": "fa","pob": "pt","por": "pt","pt": "pt","ron": "ro","rum": "ro","ro": "ro","srp": "sr","sr": "sr","tur": "tr","tr": "tr"]static func displayName(for candidate: SubtitleCandidate) -> String {
Tests/StreamResolverTests.swift
471 unmodified lines472473474475476477471 unmodified lines)),"movie.es")}private static func testSubtitleDisplayNameUsesPreservedNamesForGenericVLCTracks() {471 unmodified lines472473474475476477478479480481482483484485486487488489490491492493471 unmodified lines)),"movie.es")assertEqual(SubtitleDisplayName.displayName(for: SubtitleCandidate(url: URL(string: "https://opensubtitles.example.test/download/subtitle.srt")!,label: "Track 3",language: "nld")),"Dutch")assertEqual(SubtitleDisplayName.displayName(for: SubtitleCandidate(url: URL(string: "https://opensubtitles.example.test/download/subtitle.srt")!,label: "Track 4",language: "dan")),"Danish")}private static func testSubtitleDisplayNameUsesPreservedNamesForGenericVLCTracks() {
Expected Impact for End-Users
When subtitles load through the native VLC player, the captions menu should be easier to scan. Users should see language names for generic external subtitle tracks instead of guessing which Track N corresponds to the language they want.
Validation
- Ran
swiftc -parse-as-library Dreamio/StreamCandidate.swift Dreamio/StreamResolver.swift Tests/StreamResolverTests.swift -o /tmp/StreamResolverTests && /tmp/StreamResolverTests: passed. - Ran
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild -workspace Dreamio.xcworkspace -scheme Dreamio -configuration Debug -sdk iphonesimulator build: passed.
Issues, Limitations, and Mitigations
This still depends on VLC eventually surfacing external subtitle tracks after addPlaybackSlave. The change improves the names once tracks appear; it does not change VLC's asynchronous attachment behavior or guarantee that every remote subtitle file becomes visible instantly.
Follow-up Work
No required follow-up remains for this fix. A useful later improvement would be deeper URL-to-track correlation if MobileVLCKit exposes enough metadata to map each external subtitle slave to an exact surfaced track.