use clap::ValueEnum;
use kakasi::IsJapanese;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Serialize, Deserialize, Clone, Debug, Default, ValueEnum, PartialEq)]
pub enum Language {
#[default]
#[value(hide = true)]
Other,
#[value(name = "zh")]
Chinese,
#[value(name = "ja")]
Japanese,
#[value(name = "ko")]
Korean,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Lyrics {
pub language: Language,
pub duration: f64,
pub lyrics: Vec<(f64, String)>,
}
impl Lyrics {
pub fn from_json(object: &Value) -> Option<Lyrics> {
let synced = object["syncedLyrics"].as_str()?.to_string();
let timestamped: Vec<(f64, String)> = synced
.split('\n')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.map(|s| Some((get_time_from_string(&s[..10])?, s[10..].trim().to_string())))
.collect::<Option<_>>()?;
let duration = object["duration"].as_f64()?;
let language = object["plainLyrics"].as_str().map_or_else(
|| get_language_from_text(×tamped
.iter()
.map(|(_, line)| line)
.fold(String::new(), |a, b| a + b + &"\n")),
get_language_from_text
);
Some(Lyrics {
language,
duration,
lyrics: timestamped,
})
}
pub fn get_line_at_time(&self, time: f64) -> &str {
match self.lyrics.iter().rev().find(|l| time > l.0) {
Some(l) => &l.1,
None => "",
}
}
}
fn get_language_from_text(lyrics: &str) -> Language {
match kakasi::is_japanese(lyrics) {
IsJapanese::Maybe => Language::Chinese,
IsJapanese::True => Language::Japanese,
IsJapanese::False => {
if korean_romanize::has_korean(lyrics) {
Language::Korean
} else {
Language::Other
}
},
}
}
fn get_time_from_string(time: &str) -> Option<f64> {
let minutes: f64 = time[1..3].parse().ok()?;
let seconds: f64 = time[4..6].parse().ok()?;
let milliseconds: f64 = time[7..9].parse().ok()?;
Some(
(minutes * 60.) + (seconds) + (milliseconds / 100.)
)
}