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/connect/src/spirc.rs b/connect/src/spirc.rs index 84c5e3e6..5705c8c2 100644 --- a/connect/src/spirc.rs +++ b/connect/src/spirc.rs @@ -1138,7 +1138,7 @@ impl SpircTask { self.state.set_status(PlayStatus::kPlayStatusPlay); self.update_state_position(position_ms); self.play_status = SpircPlayStatus::Playing { - nominal_start_time: self.now_ms() as i64 - position_ms as i64, + nominal_start_time: self.now_ms() - position_ms as i64, preloading_of_next_track_triggered, }; } diff --git a/core/src/authentication.rs b/core/src/authentication.rs index b2bcad94..89117860 100644 --- a/core/src/authentication.rs +++ b/core/src/authentication.rs @@ -173,5 +173,5 @@ where D: serde::Deserializer<'de>, { let v: String = serde::Deserialize::deserialize(de)?; - base64::decode(&v).map_err(|e| serde::de::Error::custom(e.to_string())) + base64::decode(v).map_err(|e| serde::de::Error::custom(e.to_string())) } diff --git a/discovery/src/server.rs b/discovery/src/server.rs index faea33a1..379988aa 100644 --- a/discovery/src/server.rs +++ b/discovery/src/server.rs @@ -61,7 +61,7 @@ impl RequestHandler { } fn handle_get_info(&self) -> Response { - let public_key = base64::encode(&self.keys.public_key()); + let public_key = base64::encode(self.keys.public_key()); let device_type: &str = self.config.device_type.into(); let mut active_user = String::new(); if let Some(username) = &self.username { @@ -139,7 +139,7 @@ impl RequestHandler { let encrypted = &encrypted_blob[16..encrypted_blob_len - 20]; let cksum = &encrypted_blob[encrypted_blob_len - 20..encrypted_blob_len]; - let base_key = Sha1::digest(&shared_key); + let base_key = Sha1::digest(shared_key); let base_key = &base_key[..16]; let checksum_key = { @@ -179,7 +179,7 @@ impl RequestHandler { data }; - let credentials = Credentials::with_blob(username, &decrypted, &self.config.device_id)?; + let credentials = Credentials::with_blob(username, decrypted, &self.config.device_id)?; self.tx.send(credentials)?; 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/audio/item.rs b/metadata/src/audio/item.rs index 2a672075..b2789008 100644 --- a/metadata/src/audio/item.rs +++ b/metadata/src/audio/item.rs @@ -109,7 +109,7 @@ impl AudioItem { ) }; - let popularity = track.popularity.max(0).min(100) as u8; + let popularity = track.popularity.clamp(0, 100) as u8; let number = track.number.max(0) as u32; let disc_number = track.disc_number.max(0) as u32; 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 +} diff --git a/playback/src/convert.rs b/playback/src/convert.rs index 1bc8a88e..a7efe452 100644 --- a/playback/src/convert.rs +++ b/playback/src/convert.rs @@ -76,13 +76,7 @@ impl Converter { let min = -factor; let max = factor - 1.0; - if int_value < min { - min - } else if int_value > max { - max - } else { - int_value - } + int_value.clamp(min, max) } pub fn f64_to_f32(&mut self, samples: &[f64]) -> Vec { diff --git a/playback/src/lib.rs b/playback/src/lib.rs index a52ca2fa..43a5b4f0 100644 --- a/playback/src/lib.rs +++ b/playback/src/lib.rs @@ -15,6 +15,6 @@ pub mod player; pub const SAMPLE_RATE: u32 = 44100; pub const NUM_CHANNELS: u8 = 2; -pub const SAMPLES_PER_SECOND: u32 = SAMPLE_RATE as u32 * NUM_CHANNELS as u32; +pub const SAMPLES_PER_SECOND: u32 = SAMPLE_RATE * NUM_CHANNELS as u32; pub const PAGES_PER_MS: f64 = SAMPLE_RATE as f64 / 1000.0; pub const MS_PER_PAGE: f64 = 1000.0 / SAMPLE_RATE as f64; diff --git a/playback/src/player.rs b/playback/src/player.rs index f0f0d492..3fdf41b7 100644 --- a/playback/src/player.rs +++ b/playback/src/player.rs @@ -1311,7 +1311,7 @@ impl Future for PlayerInternal { self.send_event(PlayerEvent::PositionCorrection { play_request_id, track_id, - position_ms: new_stream_position_ms as u32, + position_ms: new_stream_position_ms, }); } } diff --git a/protocol/build.rs b/protocol/build.rs index b7c3f44d..b63fa1aa 100644 --- a/protocol/build.rs +++ b/protocol/build.rs @@ -9,7 +9,7 @@ fn out_dir() -> PathBuf { } fn cleanup() { - let _ = fs::remove_dir_all(&out_dir()); + let _ = fs::remove_dir_all(out_dir()); } fn compile() {