mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-04 00:47:00 +01:00
refactor(core,player): move the language table into pstr-core
The crawl needs it to read a subtitle file's language from its name (en, eng, English), and pstr-player re-exports it unchanged.
This commit is contained in:
1 parent
c44b04cf4a
commit
51454b5a6e
3 files changed
+128
-82
No files matched your search
@@ -0,0 +1,126 @@
|
||||
//! Language tags: what a container or a file name calls a language, and what a
|
||||
//! viewer calls it.
|
||||
|
||||
/// The languages offered by name where a preference is chosen ahead of any
|
||||
/// file, as the three-letter tag a Matroska file carries. The same list
|
||||
/// [`language_name`] knows, and the phones' `LANGUAGES`; mpv treats the two-
|
||||
/// and three-letter forms as one.
|
||||
pub const LANGUAGES: [&str; 29] = [
|
||||
"jpn", "eng", "ger", "fre", "spa", "ita", "por", "dut", "rus", "pol", "swe", "dan", "nor",
|
||||
"fin", "cze", "hun", "tur", "ara", "heb", "hin", "kor", "chi", "tha", "vie", "ukr", "rum",
|
||||
"gre", "ind", "may",
|
||||
];
|
||||
|
||||
/// Each language's name and the tags it goes by, the [`LANGUAGES`] form first.
|
||||
///
|
||||
/// Deliberately a short list rather than a full ISO 639 table: it covers what
|
||||
/// the shares this app is pointed at actually contain, and an unrecognised tag
|
||||
/// still shows as something a viewer can pick between — `"por-BR"` reads fine
|
||||
/// on its own.
|
||||
const TABLE: [(&str, &[&str]); 29] = [
|
||||
("English", &["eng", "en"]),
|
||||
("Japanese", &["jpn", "ja"]),
|
||||
("German", &["ger", "de", "deu"]),
|
||||
("French", &["fre", "fr", "fra"]),
|
||||
("Spanish", &["spa", "es"]),
|
||||
("Italian", &["ita", "it"]),
|
||||
("Portuguese", &["por", "pt"]),
|
||||
("Dutch", &["dut", "nl", "nld"]),
|
||||
("Russian", &["rus", "ru"]),
|
||||
("Polish", &["pol", "pl"]),
|
||||
("Swedish", &["swe", "sv"]),
|
||||
("Danish", &["dan", "da"]),
|
||||
("Norwegian", &["nor", "no"]),
|
||||
("Finnish", &["fin", "fi"]),
|
||||
("Czech", &["cze", "cs", "ces"]),
|
||||
("Hungarian", &["hun", "hu"]),
|
||||
("Turkish", &["tur", "tr"]),
|
||||
("Arabic", &["ara", "ar"]),
|
||||
("Hebrew", &["heb", "he"]),
|
||||
("Hindi", &["hin", "hi"]),
|
||||
("Korean", &["kor", "ko"]),
|
||||
("Chinese", &["chi", "zh", "zho"]),
|
||||
("Thai", &["tha", "th"]),
|
||||
("Vietnamese", &["vie", "vi"]),
|
||||
("Ukrainian", &["ukr", "uk"]),
|
||||
("Romanian", &["rum", "ro", "ron"]),
|
||||
("Greek", &["gre", "el", "ell"]),
|
||||
("Indonesian", &["ind", "id"]),
|
||||
("Malay", &["may", "ms", "msa"]),
|
||||
];
|
||||
|
||||
/// A readable name for a language tag, falling back to the tag itself.
|
||||
pub fn language_name(tag: &str) -> String {
|
||||
// Container tags come in both the two- and three-letter forms, and often
|
||||
// with a region suffix: "en", "eng", "en-US", "pt-BR".
|
||||
let base = tag.split(['-', '_']).next().unwrap_or(tag);
|
||||
let Some((name, _)) = TABLE
|
||||
.iter()
|
||||
.find(|(_, tags)| tags.iter().any(|known| known.eq_ignore_ascii_case(base)))
|
||||
else {
|
||||
return tag.to_string();
|
||||
};
|
||||
|
||||
// The region matters when there is one — Brazilian Portuguese is not
|
||||
// Portuguese to anyone choosing between the two.
|
||||
match tag.split_once(['-', '_']) {
|
||||
Some((_, region)) if !region.is_empty() => {
|
||||
format!("{name} ({})", region.to_ascii_uppercase())
|
||||
}
|
||||
_ => (*name).to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether two container tags name the same language: "en" and "eng" do.
|
||||
pub fn same_language(a: &str, b: &str) -> bool {
|
||||
a.eq_ignore_ascii_case(b) || language_name(a) == language_name(b)
|
||||
}
|
||||
|
||||
/// The [`LANGUAGES`] tag for one word of a file name, if it names a language:
|
||||
/// "en", "eng" and "English" are all `"eng"`.
|
||||
///
|
||||
/// For the words around a subtitle file's name — `Episode 01.en.srt`,
|
||||
/// `2_English.srt` — where a two-letter word is far more often a language than
|
||||
/// anything else.
|
||||
pub fn language_tag(word: &str) -> Option<&'static str> {
|
||||
TABLE
|
||||
.iter()
|
||||
.find(|(name, tags)| {
|
||||
name.eq_ignore_ascii_case(word) || tags.iter().any(|tag| tag.eq_ignore_ascii_case(word))
|
||||
})
|
||||
.map(|(_, tags)| tags[0])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn language_tags_resolve_in_both_iso_forms_and_keep_their_region() {
|
||||
assert_eq!(language_name("jpn"), "Japanese");
|
||||
assert_eq!(language_name("ja"), "Japanese");
|
||||
assert_eq!(language_name("pt-BR"), "Portuguese (BR)");
|
||||
// An unknown tag is shown as it stands rather than as "Unknown", which
|
||||
// would make two of them indistinguishable.
|
||||
assert_eq!(language_name("mis"), "mis");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_language_named_by_either_tag_or_by_its_name_has_the_tag_a_preference_uses() {
|
||||
assert_eq!(language_tag("en"), Some("eng"));
|
||||
assert_eq!(language_tag("ENG"), Some("eng"));
|
||||
assert_eq!(language_tag("German"), Some("ger"));
|
||||
assert_eq!(language_tag("deu"), Some("ger"));
|
||||
assert_eq!(language_tag("forced"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_language_offered_by_name_is_one_the_table_knows() {
|
||||
let firsts: Vec<&str> = TABLE.iter().map(|(_, tags)| tags[0]).collect();
|
||||
let mut offered = LANGUAGES.to_vec();
|
||||
let mut known = firsts.clone();
|
||||
offered.sort_unstable();
|
||||
known.sort_unstable();
|
||||
assert_eq!(offered, known);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ pub mod chapters;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod franchise;
|
||||
pub mod language;
|
||||
pub mod library;
|
||||
pub mod metadata;
|
||||
pub mod naming;
|
||||
|
||||
Reference in new issue
Block a user