From a4d0a195cde35b93a8d9292498e71c3ab6a74325 Mon Sep 17 00:00:00 2001 From: XZH Date: Sat, 5 Sep 2026 00:02:42 -0700 Subject: [PATCH 1/4] fix(captions): match preferred language to auto-generated tracks Preferred caption slots use names such as English, but many videos only expose English (auto-generated) with language code en. Exact name matching left those tracks unordered, so the player could auto-select another auto-generated language. Match preference names to regional and auto-generated variants, keep human tracks ahead of auto-generated ones, and share the matcher on watch and embed. Fixes #6007 --- .../videos/captions_matching_spec.cr | 36 ++++++++++++++++ src/invidious/routes/embed.cr | 9 +--- src/invidious/routes/watch.cr | 9 +--- src/invidious/videos/caption.cr | 43 +++++++++++++++++++ 4 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 spec/invidious/videos/captions_matching_spec.cr diff --git a/spec/invidious/videos/captions_matching_spec.cr b/spec/invidious/videos/captions_matching_spec.cr new file mode 100644 index 000000000..d2cbb0730 --- /dev/null +++ b/spec/invidious/videos/captions_matching_spec.cr @@ -0,0 +1,36 @@ +require "../../spec_helper" + +Spectator.describe Invidious::Videos::Captions do + describe ".matching" do + it "matches English to English auto-generated ahead of other auto tracks" do + arabic = Invidious::Videos::Captions::Metadata.new("Arabic (auto-generated)", "ar", "http://x", true) + english_auto = Invidious::Videos::Captions::Metadata.new("English (auto-generated)", "en", "http://x", true) + + matched = Invidious::Videos::Captions.matching([arabic, english_auto], ["English"]) + expect(matched.map(&.name)).to eq(["English (auto-generated)"]) + end + + it "prefers a human English track over auto-generated English" do + english_auto = Invidious::Videos::Captions::Metadata.new("English (auto-generated)", "en", "http://x", true) + english = Invidious::Videos::Captions::Metadata.new("English", "en", "http://x", false) + + matched = Invidious::Videos::Captions.matching([english_auto, english], ["English"]) + expect(matched.map(&.name)).to eq(["English", "English (auto-generated)"]) + end + + it "matches regional English names and language codes" do + us = Invidious::Videos::Captions::Metadata.new("English (United States)", "en-US", "http://x", false) + uk = Invidious::Videos::Captions::Metadata.new("English (United Kingdom)", "en-GB", "http://x", false) + german = Invidious::Videos::Captions::Metadata.new("German (Germany)", "de-DE", "http://x", false) + + matched = Invidious::Videos::Captions.matching([german, us, uk], ["English", "en"]) + expect(matched.map(&.name)).to eq(["English (United States)", "English (United Kingdom)"]) + end + + it "ignores blank preference slots" do + arabic = Invidious::Videos::Captions::Metadata.new("Arabic (auto-generated)", "ar", "http://x", true) + matched = Invidious::Videos::Captions.matching([arabic], ["", "", ""]) + expect(matched).to be_empty + end + end +end diff --git a/src/invidious/routes/embed.cr b/src/invidious/routes/embed.cr index 41dc517c2..bde4f2271 100644 --- a/src/invidious/routes/embed.cr +++ b/src/invidious/routes/embed.cr @@ -182,14 +182,7 @@ module Invidious::Routes::Embed captions = video.captions - preferred_captions = captions.select { |caption| - params.preferred_captions.includes?(caption.name) || - params.preferred_captions.includes?(caption.language_code.split("-")[0]) - } - preferred_captions.sort_by! { |caption| - (params.preferred_captions.index(caption.name) || - params.preferred_captions.index(caption.language_code.split("-")[0])).not_nil! - } + preferred_captions = Invidious::Videos::Captions.matching(captions, params.preferred_captions) captions = captions - preferred_captions aspect_ratio = nil diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index 3c66f1bc0..f26bc6119 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -149,14 +149,7 @@ module Invidious::Routes::Watch captions = video.captions - preferred_captions = captions.select { |caption| - params.preferred_captions.includes?(caption.name) || - params.preferred_captions.includes?(caption.language_code.split("-")[0]) - } - preferred_captions.sort_by! { |caption| - (params.preferred_captions.index(caption.name) || - params.preferred_captions.index(caption.language_code.split("-")[0])).not_nil! - } + preferred_captions = Invidious::Videos::Captions.matching(captions, params.preferred_captions) captions = captions - preferred_captions aspect_ratio = "16:9" diff --git a/src/invidious/videos/caption.cr b/src/invidious/videos/caption.cr index c811cfe1b..c6c7113d2 100644 --- a/src/invidious/videos/caption.cr +++ b/src/invidious/videos/caption.cr @@ -86,6 +86,49 @@ module Invidious::Videos end end + # Tracks whose name or language matches the user's caption preferences, + # ranked by preference slot then human tracks before auto-generated. + def self.matching(captions : Array(Metadata), names : Array(String)) : Array(Metadata) + wanted = names.map(&.strip).reject(&.empty?) + return [] of Metadata if wanted.empty? + + selected = captions.select { |caption| matches?(caption, wanted) } + selected.sort_by! { |caption| rank(caption, wanted) } + selected + end + + def self.matches?(caption : Metadata, names : Array(String)) : Bool + names.any? { |name| name_matches?(caption, name) } + end + + private def self.name_matches?(caption : Metadata, name : String) : Bool + needle = name.strip.downcase + return false if needle.empty? + + caption_name = caption.name.downcase + lang = caption.language_code.downcase + base_lang = lang.split("-")[0] + + caption_name == needle || + caption_name.starts_with?(needle + " (") || + caption_name.starts_with?(needle + " - ") || + lang == needle || + base_lang == needle + end + + private def self.rank(caption : Metadata, names : Array(String)) : Tuple(Int32, Int32) + pref_rank = names.size + names.each_with_index do |name, index| + if name_matches?(caption, name) + pref_rank = index + break + end + end + + auto_rank = caption.auto_generated ? 1 : 0 + {pref_rank, auto_rank} + end + # List of all caption languages available on Youtube. LANGUAGES = { "", From 3df2266294be28712912d1877bbc09c0482e794a Mon Sep 17 00:00:00 2001 From: XZH Date: Sat, 5 Sep 2026 00:23:58 -0700 Subject: [PATCH 2/4] 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. --- .../videos/captions_matching_spec.cr | 17 ++ src/invidious/videos/caption.cr | 151 +++++++++++++++++- 2 files changed, 163 insertions(+), 5 deletions(-) diff --git a/spec/invidious/videos/captions_matching_spec.cr b/spec/invidious/videos/captions_matching_spec.cr index d2cbb0730..b8f61a12c 100644 --- a/spec/invidious/videos/captions_matching_spec.cr +++ b/spec/invidious/videos/captions_matching_spec.cr @@ -27,6 +27,23 @@ Spectator.describe Invidious::Videos::Captions do expect(matched.map(&.name)).to eq(["English (United States)", "English (United Kingdom)"]) 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 arabic = Invidious::Videos::Captions::Metadata.new("Arabic (auto-generated)", "ar", "http://x", true) matched = Invidious::Videos::Captions.matching([arabic], ["", "", ""]) diff --git a/src/invidious/videos/caption.cr b/src/invidious/videos/caption.cr index c6c7113d2..ba67e2de6 100644 --- a/src/invidious/videos/caption.cr +++ b/src/invidious/videos/caption.cr @@ -101,6 +101,117 @@ module Invidious::Videos names.any? { |name| name_matches?(caption, name) } 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 needle = name.strip.downcase return false if needle.empty? @@ -109,11 +220,41 @@ module Invidious::Videos lang = caption.language_code.downcase base_lang = lang.split("-")[0] - caption_name == needle || - caption_name.starts_with?(needle + " (") || - caption_name.starts_with?(needle + " - ") || - lang == needle || - base_lang == needle + return true if caption_name == needle || + caption_name.starts_with?(needle + " (") || + caption_name.starts_with?(needle + " - ") || + 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 private def self.rank(caption : Metadata, names : Array(String)) : Tuple(Int32, Int32) From c4e2bc34c50cc855bea4407fdfab28365c075b00 Mon Sep 17 00:00:00 2001 From: XZH Date: Sat, 5 Sep 2026 00:32:59 -0700 Subject: [PATCH 3/4] fix(captions): prefer regional language codes over base fallback Keep region-specific LANGUAGES names such as English (United States) mapped to en-us, then fall back to en. Rank the regional code ahead of en-GB and other base-language matches. --- .../videos/captions_matching_spec.cr | 9 +- src/invidious/videos/caption.cr | 233 +++++++----------- 2 files changed, 95 insertions(+), 147 deletions(-) diff --git a/spec/invidious/videos/captions_matching_spec.cr b/spec/invidious/videos/captions_matching_spec.cr index b8f61a12c..ec36a9754 100644 --- a/spec/invidious/videos/captions_matching_spec.cr +++ b/spec/invidious/videos/captions_matching_spec.cr @@ -27,7 +27,6 @@ Spectator.describe Invidious::Videos::Captions do expect(matched.map(&.name)).to eq(["English (United States)", "English (United Kingdom)"]) 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) @@ -44,6 +43,14 @@ Spectator.describe Invidious::Videos::Captions do expect(matched.map(&.name)).to eq(["en-US"]) end + it "prefers en-US over en-GB for English (United States)" do + us = Invidious::Videos::Captions::Metadata.new("en-US", "en-US", "http://x", false) + uk = Invidious::Videos::Captions::Metadata.new("en-GB", "en-GB", "http://x", false) + + matched = Invidious::Videos::Captions.matching([uk, us], ["English (United States)"]) + expect(matched.map(&.name)).to eq(["en-US", "en-GB"]) + end + it "ignores blank preference slots" do arabic = Invidious::Videos::Captions::Metadata.new("Arabic (auto-generated)", "ar", "http://x", true) matched = Invidious::Videos::Captions.matching([arabic], ["", "", ""]) diff --git a/src/invidious/videos/caption.cr b/src/invidious/videos/caption.cr index ba67e2de6..7340ffcb7 100644 --- a/src/invidious/videos/caption.cr +++ b/src/invidious/videos/caption.cr @@ -101,173 +101,114 @@ module Invidious::Videos names.any? { |name| name_matches?(caption, name) } end - # Map display-language preferences (e.g. "English") onto ISO-ish codes so - # they can match caption names/codes like "en" or "en-US". + # Common LANGUAGES names mapped to ISO 639-1 / BCP-47 codes. + # Auto-generated suffixes are stripped before lookup; regional + # qualifiers keep their more specific code and fall back to the base. 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", + "arabic" => "ar", + "chinese" => "zh", + "chinese (china)" => "zh-cn", + "chinese (hong kong)" => "zh-hk", + "chinese (simplified)" => "zh-hans", + "chinese (taiwan)" => "zh-tw", + "chinese (traditional)" => "zh-hant", + "dutch" => "nl", + "english" => "en", + "english (united kingdom)" => "en-gb", + "english (united states)" => "en-us", + "filipino" => "fil", + "french" => "fr", + "german" => "de", + "indonesian" => "id", + "italian" => "it", + "japanese" => "ja", + "korean" => "ko", + "portuguese" => "pt", + "portuguese (brazil)" => "pt-br", + "russian" => "ru", + "spanish" => "es", + "spanish (latin america)" => "es-419", + "spanish (mexico)" => "es-mx", + "spanish (spain)" => "es-es", + "turkish" => "tr", + "vietnamese" => "vi", } private def self.name_matches?(caption : Metadata, name : String) : Bool - needle = name.strip.downcase - return false if needle.empty? - - caption_name = caption.name.downcase - lang = caption.language_code.downcase - base_lang = lang.split("-")[0] - - return true if caption_name == needle || - caption_name.starts_with?(needle + " (") || - caption_name.starts_with?(needle + " - ") || - 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) } + !match_index(caption, name).nil? end - private def self.canonical_codes(value : String) : Array(String) - raw = value.strip.downcase - return [] of String if raw.empty? + # Tokens from most specific to least: "English (United States)" -> + # "english (united states)", "en-us", "english", "en". + private def self.match_tokens(name : String) : Array(String) + token = name.strip.downcase + return [] of String if token.empty? - codes = [] of String - base_name = raw.split(" - ")[0].split(" (")[0].strip - - if mapped = NAME_TO_CODE[base_name]? - codes << mapped + lookup = token + if lookup.ends_with?(" (auto-generated)") + lookup = lookup.rchop(" (auto-generated)") end - if raw.includes?("-") - codes << raw - codes << raw.split("-")[0] - elsif raw.size.in?(2..3) && raw.chars.all?(&.ascii_letter?) - codes << raw + tokens = [lookup] + if mapped = NAME_TO_CODE[lookup]? + tokens << mapped end - codes.uniq! - codes + base_name = lookup.split(" - ")[0].split(" (")[0].strip + if !base_name.empty? && base_name != lookup + tokens << base_name + if mapped = NAME_TO_CODE[base_name]? + tokens << mapped + end + end + + if lookup.includes?("-") + tokens << lookup.split("-", 2)[0] + elsif lookup.size.in?(2..3) && lookup.chars.all?(&.ascii_letter?) + tokens << lookup + end + + tokens.uniq end - private def self.rank(caption : Metadata, names : Array(String)) : Tuple(Int32, Int32) + private def self.match_index(caption : Metadata, name : String) : Int32? + needles = match_tokens(name) + return nil if needles.empty? + + caption_name = caption.name.strip.downcase + lang = caption.language_code.strip.downcase + base_lang = lang.split("-", 2)[0] + caption_tokens = match_tokens(caption.name) + caption_tokens << lang + caption_tokens << base_lang + + needles.each_with_index do |needle, index| + if caption_name == needle || + caption_name.starts_with?(needle + " (") || + caption_name.starts_with?(needle + " - ") || + lang == needle || + (base_lang == needle && !needle.includes?("-")) || + caption_tokens.includes?(needle) + return index + end + end + + nil + end + + private def self.rank(caption : Metadata, names : Array(String)) : Tuple(Int32, Int32, Int32) pref_rank = names.size + specificity = Int32::MAX names.each_with_index do |name, index| - if name_matches?(caption, name) + if found = match_index(caption, name) pref_rank = index + specificity = found break end end auto_rank = caption.auto_generated ? 1 : 0 - {pref_rank, auto_rank} + {pref_rank, specificity, auto_rank} end # List of all caption languages available on Youtube. From 7f990b061a1b0ade750219310b1b47b1d9c623a7 Mon Sep 17 00:00:00 2001 From: XZH Date: Sat, 5 Sep 2026 00:41:45 -0700 Subject: [PATCH 4/4] fix(captions): rank human tracks before regional specificity Keep preference slot first, then human before auto-generated, and only then use language specificity so an exact auto regional match cannot beat a human base-language track. --- spec/invidious/videos/captions_matching_spec.cr | 8 ++++++++ src/invidious/videos/caption.cr | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/spec/invidious/videos/captions_matching_spec.cr b/spec/invidious/videos/captions_matching_spec.cr index ec36a9754..7ad0abde2 100644 --- a/spec/invidious/videos/captions_matching_spec.cr +++ b/spec/invidious/videos/captions_matching_spec.cr @@ -51,6 +51,14 @@ Spectator.describe Invidious::Videos::Captions do expect(matched.map(&.name)).to eq(["en-US", "en-GB"]) end + it "prefers human base-language English over exact regional auto-generated" do + human = Invidious::Videos::Captions::Metadata.new("English", "en", "http://x", false) + auto_us = Invidious::Videos::Captions::Metadata.new("en-US", "en-US", "http://x", true) + + matched = Invidious::Videos::Captions.matching([auto_us, human], ["English (United States)"]) + expect(matched.map(&.name)).to eq(["English", "en-US"]) + end + it "ignores blank preference slots" do arabic = Invidious::Videos::Captions::Metadata.new("Arabic (auto-generated)", "ar", "http://x", true) matched = Invidious::Videos::Captions.matching([arabic], ["", "", ""]) diff --git a/src/invidious/videos/caption.cr b/src/invidious/videos/caption.cr index 7340ffcb7..bdc498c96 100644 --- a/src/invidious/videos/caption.cr +++ b/src/invidious/videos/caption.cr @@ -87,7 +87,8 @@ module Invidious::Videos end # Tracks whose name or language matches the user's caption preferences, - # ranked by preference slot then human tracks before auto-generated. + # ranked by preference slot, then human tracks before auto-generated, + # then more-specific language matches ahead of base-language fallbacks. def self.matching(captions : Array(Metadata), names : Array(String)) : Array(Metadata) wanted = names.map(&.strip).reject(&.empty?) return [] of Metadata if wanted.empty? @@ -208,7 +209,7 @@ module Invidious::Videos end auto_rank = caption.auto_generated ? 1 : 0 - {pref_rank, specificity, auto_rank} + {pref_rank, auto_rank, specificity} end # List of all caption languages available on Youtube.