mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Merge pull request #1084 from gdesmott/lyrics
[dev] core: spclient: parse lyrics
This commit is contained in:
commit
c14c22a5ef
13 changed files with 94 additions and 16 deletions
|
@ -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
|
||||
|
||||
|
|
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1500,6 +1500,8 @@ dependencies = [
|
|||
"librespot-protocol",
|
||||
"log",
|
||||
"protobuf",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
]
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ impl RequestHandler {
|
|||
}
|
||||
|
||||
fn handle_get_info(&self) -> Response<hyper::Body> {
|
||||
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)?;
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
77
metadata/src/lyrics.rs
Normal file
77
metadata/src/lyrics.rs
Normal file
|
@ -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<Self, Error> {
|
||||
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<Self, Error> {
|
||||
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<Self, Self::Error> {
|
||||
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<Line>,
|
||||
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
|
||||
}
|
|
@ -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<f32> {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in a new issue