pub fn parse_lrc(lrc: &str) -> Vec<(u32, String)> {
let mut out: Vec<(u32, String)> = Vec::new();
for line in lrc.lines() {
let mut rest = line;
let mut stamps: Vec<u32> = Vec::new();
while rest.starts_with('[') {
let Some(end) = rest.find(']') else { break };
let tag = &rest[1..end];
if let Some(ms) = parse_lrc_stamp(tag) {
stamps.push(ms);
}
rest = rest[end + 1..].trim_start();
}
let text = rest.trim().to_string();
for ms in stamps {
out.push((ms, text.clone()));
}
}
out.sort_by_key(|(t, _)| *t);
out
}
pub fn parse_lrc_stamp(tag: &str) -> Option<u32> {
let (mm, rest) = tag.split_once(':')?;
let mm: u32 = mm.parse().ok()?;
let (ss, cs) = match rest.split_once('.') {
Some((s, c)) => (s.parse::<u32>().ok()?, c),
None => (rest.parse::<u32>().ok()?, "0"),
};
if !cs.is_empty() && !cs.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
let cs: u32 = if cs.is_empty() {
0
} else {
format!("{:0<3}", &cs[..cs.len().min(3)]).parse().ok()?
};
mm.checked_mul(60)?
.checked_add(ss)?
.checked_mul(1000)?
.checked_add(cs)
}