fix(captions): map display-language preferences to language codes

Canonicalize names like English to en before matching so code-labeled
tracks such as en and en-US honor the preferred caption language.
This commit is contained in:
XZH 2026-09-05 00:23:58 -07:00
parent a4d0a195cd
commit 3df2266294
2 changed files with 163 additions and 5 deletions

View File

@ -27,6 +27,23 @@ Spectator.describe Invidious::Videos::Captions do
expect(matched.map(&.name)).to eq(["English (United States)", "English (United Kingdom)"]) expect(matched.map(&.name)).to eq(["English (United States)", "English (United Kingdom)"])
end end
it "matches English preference to a code-labeled en track" do
code_only = Invidious::Videos::Captions::Metadata.new("en", "en", "http://x", true)
arabic = Invidious::Videos::Captions::Metadata.new("ar", "ar", "http://x", true)
matched = Invidious::Videos::Captions.matching([arabic, code_only], ["English"])
expect(matched.map(&.name)).to eq(["en"])
end
it "matches English preference to a code-labeled en-US track" do
regional = Invidious::Videos::Captions::Metadata.new("en-US", "en-US", "http://x", false)
german = Invidious::Videos::Captions::Metadata.new("de-DE", "de-DE", "http://x", false)
matched = Invidious::Videos::Captions.matching([german, regional], ["English"])
expect(matched.map(&.name)).to eq(["en-US"])
end
it "ignores blank preference slots" do it "ignores blank preference slots" do
arabic = Invidious::Videos::Captions::Metadata.new("Arabic (auto-generated)", "ar", "http://x", true) arabic = Invidious::Videos::Captions::Metadata.new("Arabic (auto-generated)", "ar", "http://x", true)
matched = Invidious::Videos::Captions.matching([arabic], ["", "", ""]) matched = Invidious::Videos::Captions.matching([arabic], ["", "", ""])

View File

@ -101,6 +101,117 @@ module Invidious::Videos
names.any? { |name| name_matches?(caption, name) } names.any? { |name| name_matches?(caption, name) }
end end
# Map display-language preferences (e.g. "English") onto ISO-ish codes so
# they can match caption names/codes like "en" or "en-US".
NAME_TO_CODE = {
"afrikaans" => "af",
"albanian" => "sq",
"amharic" => "am",
"arabic" => "ar",
"armenian" => "hy",
"azerbaijani" => "az",
"bangla" => "bn",
"basque" => "eu",
"belarusian" => "be",
"bosnian" => "bs",
"bulgarian" => "bg",
"burmese" => "my",
"cantonese" => "yue",
"catalan" => "ca",
"cebuano" => "ceb",
"chinese" => "zh",
"corsican" => "co",
"croatian" => "hr",
"czech" => "cs",
"danish" => "da",
"dutch" => "nl",
"english" => "en",
"esperanto" => "eo",
"estonian" => "et",
"filipino" => "fil",
"finnish" => "fi",
"french" => "fr",
"galician" => "gl",
"georgian" => "ka",
"german" => "de",
"greek" => "el",
"gujarati" => "gu",
"haitian creole" => "ht",
"hausa" => "ha",
"hawaiian" => "haw",
"hebrew" => "he",
"hindi" => "hi",
"hmong" => "hmn",
"hungarian" => "hu",
"icelandic" => "is",
"igbo" => "ig",
"indonesian" => "id",
"interlingue" => "ie",
"irish" => "ga",
"italian" => "it",
"japanese" => "ja",
"javanese" => "jv",
"kannada" => "kn",
"kazakh" => "kk",
"khmer" => "km",
"korean" => "ko",
"kurdish" => "ku",
"kyrgyz" => "ky",
"lao" => "lo",
"latin" => "la",
"latvian" => "lv",
"lithuanian" => "lt",
"luxembourgish" => "lb",
"macedonian" => "mk",
"malagasy" => "mg",
"malay" => "ms",
"malayalam" => "ml",
"maltese" => "mt",
"maori" => "mi",
"marathi" => "mr",
"mongolian" => "mn",
"nepali" => "ne",
"norwegian bokmål" => "nb",
"norwegian bokmal" => "nb",
"nyanja" => "ny",
"pashto" => "ps",
"persian" => "fa",
"polish" => "pl",
"portuguese" => "pt",
"punjabi" => "pa",
"romanian" => "ro",
"russian" => "ru",
"samoan" => "sm",
"scottish gaelic" => "gd",
"serbian" => "sr",
"shona" => "sn",
"sindhi" => "sd",
"sinhala" => "si",
"slovak" => "sk",
"slovenian" => "sl",
"somali" => "so",
"southern sotho" => "st",
"spanish" => "es",
"sundanese" => "su",
"swahili" => "sw",
"swedish" => "sv",
"tajik" => "tg",
"tamil" => "ta",
"telugu" => "te",
"thai" => "th",
"turkish" => "tr",
"ukrainian" => "uk",
"urdu" => "ur",
"uzbek" => "uz",
"vietnamese" => "vi",
"welsh" => "cy",
"western frisian" => "fy",
"xhosa" => "xh",
"yiddish" => "yi",
"yoruba" => "yo",
"zulu" => "zu",
}
private def self.name_matches?(caption : Metadata, name : String) : Bool private def self.name_matches?(caption : Metadata, name : String) : Bool
needle = name.strip.downcase needle = name.strip.downcase
return false if needle.empty? return false if needle.empty?
@ -109,11 +220,41 @@ module Invidious::Videos
lang = caption.language_code.downcase lang = caption.language_code.downcase
base_lang = lang.split("-")[0] base_lang = lang.split("-")[0]
caption_name == needle || return true if caption_name == needle ||
caption_name.starts_with?(needle + " (") || caption_name.starts_with?(needle + " (") ||
caption_name.starts_with?(needle + " - ") || caption_name.starts_with?(needle + " - ") ||
lang == needle || lang == needle ||
base_lang == needle base_lang == needle
# Display-language preferences such as "English" must also match
# code-labeled tracks like "en" / "en-US".
needle_codes = canonical_codes(needle)
caption_codes = canonical_codes(caption_name)
caption_codes << lang
caption_codes << base_lang
needle_codes.any? { |code| caption_codes.includes?(code) }
end
private def self.canonical_codes(value : String) : Array(String)
raw = value.strip.downcase
return [] of String if raw.empty?
codes = [] of String
base_name = raw.split(" - ")[0].split(" (")[0].strip
if mapped = NAME_TO_CODE[base_name]?
codes << mapped
end
if raw.includes?("-")
codes << raw
codes << raw.split("-")[0]
elsif raw.size.in?(2..3) && raw.chars.all?(&.ascii_letter?)
codes << raw
end
codes.uniq!
codes
end end
private def self.rank(caption : Metadata, names : Array(String)) : Tuple(Int32, Int32) private def self.rank(caption : Metadata, names : Array(String)) : Tuple(Int32, Int32)