diff --git a/CHANGELOG.md b/CHANGELOG.md index 54eaf48b..1b3e1066 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,7 @@ https://github.com/librespot-org/librespot Connect should use the 'filter-explicit-content' user attribute in the session. - [playback] Add metadata support via a `TrackChanged` event - [connect] Add `activate` and `load` functions to `Spirc`, allowing control over local connect sessions +- [metadata] Add `Lyrics` ### Fixed diff --git a/Cargo.lock b/Cargo.lock index fe16c165..295681e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1500,6 +1500,8 @@ dependencies = [ "librespot-protocol", "log", "protobuf", + "serde", + "serde_json", "thiserror", "uuid", ] diff --git a/metadata/Cargo.toml b/metadata/Cargo.toml index 490b5227..4d977bea 100644 --- a/metadata/Cargo.toml +++ b/metadata/Cargo.toml @@ -16,6 +16,8 @@ log = "0.4" protobuf = "2" thiserror = "1" uuid = { version = "1", default-features = false } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" [dependencies.librespot-core] path = "../core" diff --git a/metadata/src/lib.rs b/metadata/src/lib.rs index ef8443db..e7263cae 100644 --- a/metadata/src/lib.rs +++ b/metadata/src/lib.rs @@ -18,6 +18,7 @@ pub mod episode; pub mod error; pub mod external_id; pub mod image; +pub mod lyrics; pub mod playlist; mod request; pub mod restriction; @@ -33,6 +34,7 @@ use request::RequestResult; pub use album::Album; pub use artist::Artist; pub use episode::Episode; +pub use lyrics::Lyrics; pub use playlist::Playlist; pub use show::Show; pub use track::Track; diff --git a/metadata/src/lyrics.rs b/metadata/src/lyrics.rs new file mode 100644 index 00000000..35263e2d --- /dev/null +++ b/metadata/src/lyrics.rs @@ -0,0 +1,77 @@ +use bytes::Bytes; + +use librespot_core::{Error, FileId, Session, SpotifyId}; + +impl Lyrics { + pub async fn get(session: &Session, id: &SpotifyId) -> Result { + let spclient = session.spclient(); + let lyrics = spclient.get_lyrics(id).await?; + Self::try_from(&lyrics) + } + + pub async fn get_for_image( + session: &Session, + id: &SpotifyId, + image_id: &FileId, + ) -> Result { + let spclient = session.spclient(); + let lyrics = spclient.get_lyrics_for_image(id, image_id).await?; + Self::try_from(&lyrics) + } +} + +impl TryFrom<&Bytes> for Lyrics { + type Error = Error; + + fn try_from(lyrics: &Bytes) -> Result { + serde_json::from_slice(lyrics).map_err(|err| err.into()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Lyrics { + pub colors: Colors, + pub has_vocal_removal: bool, + pub lyrics: LyricsInner, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Colors { + pub background: i32, + pub highlight_text: i32, + pub text: i32, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LyricsInner { + // TODO: 'alternatives' field as an array but I don't know what it's meant for + pub fullscreen_action: String, + pub is_dense_typeface: bool, + pub is_rtl_language: bool, + pub language: String, + pub lines: Vec, + pub provider: String, + pub provider_display_name: String, + pub provider_lyrics_id: String, + pub sync_lyrics_uri: String, + pub sync_type: SyncType, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum SyncType { + Unsynced, + LineSynced, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Line { + pub start_time_ms: String, + pub end_time_ms: String, + pub words: String, + // TODO: 'syllables' array +}