use anyhow::Result;
use serde::Deserialize;
pub const MUSIC_INDICATOR: &str = "♪";
#[derive(Debug, Deserialize)]
pub struct Track {
pub id: u32,
#[serde(rename = "trackName")]
pub track_name: String,
#[serde(rename = "artistName")]
pub artist_name: String,
#[serde(rename = "albumName")]
pub album_name: String,
pub duration: f32,
pub instrumental: bool,
#[serde(rename = "plainLyrics")]
pub plain_lyrics: Option<String>,
#[serde(rename = "syncedLyrics")]
pub synced_lyrics: Option<SyncedLyrics>,
}
#[derive(Debug, Clone)]
pub struct Timestamp {
pub m: u32,
pub s: u8,
pub ms: u32,
}
impl Timestamp {
pub fn new(m: u32, s: u8, ms: u32) -> Self {
Self { m, s, ms }
}
pub fn seconds(&self) -> f32 {
(self.m * 60 + self.s as u32 + self.ms / 1000) as f32
}
}
#[derive(Debug)]
pub struct SyncedLyrics {
pub raw: String,
}
impl SyncedLyrics {
pub fn new(raw: String) -> Self {
Self { raw }
}
pub fn pieces(&self) -> Vec<(Timestamp, String)> {
let re = regex::Regex::new(r"\[(\d+):(\d+).(\d+)\][ ]*(.*)").unwrap();
re.captures_iter(&self.raw)
.map(|cap| {
(
Timestamp::new(
cap.get(1).unwrap().as_str().parse::<u32>().unwrap(),
cap.get(2).unwrap().as_str().parse::<u8>().unwrap(),
cap.get(3).unwrap().as_str().parse::<u32>().unwrap(),
),
{
let item = cap.get(4).unwrap().as_str().to_string();
if item.trim().is_empty() {
MUSIC_INDICATOR.to_string()
} else {
item
}
},
)
})
.collect::<Vec<_>>()
}
pub fn at(&self, timestamp: Timestamp) -> (Timestamp, String) {
let pieces = self.pieces();
let mut elapsed = 0_f32;
let mut i = 0_usize;
while elapsed < timestamp.seconds() {
elapsed += pieces[i].0.seconds();
i += 1;
}
let (timestamp, lyrics) = &pieces[i];
(timestamp.clone(), lyrics.clone())
}
pub fn deltas(&self, include_initial: bool) -> Vec<f32> {
let pieces = self.pieces();
let mut deltas = vec![];
if include_initial {
deltas.push(pieces[0].0.seconds());
}
let mut i = 0_usize;
while i < pieces.len() - 1 {
deltas.push(pieces[i + 1].0.seconds() - pieces[i].0.seconds());
i += 1;
}
deltas
}
}
impl<'de> Deserialize<'de> for SyncedLyrics {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
Ok(SyncedLyrics::new(raw))
}
}
pub async fn search<K: ToString>(q: K) -> Result<Vec<Track>> {
let client = reqwest::Client::new();
let resp = client
.get("https://lrclib.net/api/search")
.query(&[("q", q.to_string())])
.send()
.await?;
Ok(resp.json::<Vec<Track>>().await?)
}
pub async fn get_by_id(id: u32) -> Result<Track> {
let client = reqwest::Client::new();
let resp = client
.get(format!("https://lrclib.net/api/get/{}", id))
.send()
.await?;
Ok(resp.json::<Track>().await?)
}