Session de debug radio paradise
This commit is contained in:
BIN
pmoparadise/.pmomusic_audio/cache.db
Normal file
BIN
pmoparadise/.pmomusic_audio/cache.db
Normal file
Binary file not shown.
@@ -39,7 +39,9 @@ async fn main() -> Result<()> {
|
||||
println!("Now Playing:");
|
||||
println!(" Title: {}", song.title);
|
||||
println!(" Artist: {}", song.artist);
|
||||
println!(" Album: {}", song.album);
|
||||
if let Some(ref album) = song.album {
|
||||
println!(" Album: {}", album);
|
||||
}
|
||||
if let Some(year) = song.year {
|
||||
println!(" Year: {}", year);
|
||||
}
|
||||
@@ -77,7 +79,9 @@ async fn main() -> Result<()> {
|
||||
duration_sec / 60,
|
||||
duration_sec % 60
|
||||
);
|
||||
println!(" Album: {}", song.album);
|
||||
if let Some(ref album) = song.album {
|
||||
println!(" Album: {}", album);
|
||||
}
|
||||
|
||||
if let Some(year) = song.year {
|
||||
print!(" Year: {}", year);
|
||||
|
||||
@@ -261,7 +261,9 @@ fn create_song_item(
|
||||
|
||||
// Add metadata
|
||||
item.add_artist(song.artist.clone());
|
||||
item.add_album(song.album.clone());
|
||||
if let Some(ref album) = song.album {
|
||||
item.add_album(album.clone());
|
||||
}
|
||||
|
||||
if let Some(year) = song.year {
|
||||
item.set_date(format!("{}-01-01", year));
|
||||
|
||||
@@ -1,8 +1,111 @@
|
||||
//! Data models for Radio Paradise API responses
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Deserialize a string or number into a u64
|
||||
fn deserialize_string_or_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
use serde::de::Error;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum StringOrU64 {
|
||||
String(String),
|
||||
Number(u64),
|
||||
}
|
||||
|
||||
match StringOrU64::deserialize(deserializer)? {
|
||||
StringOrU64::String(s) => s.parse::<u64>().map_err(D::Error::custom),
|
||||
StringOrU64::Number(n) => Ok(n),
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialize a string or number into a f64, then convert to u64 milliseconds
|
||||
fn deserialize_length<'de, D>(deserializer: D) -> Result<u64, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
use serde::de::Error;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum StringOrNumber {
|
||||
String(String),
|
||||
Float(f64),
|
||||
Int(u64),
|
||||
}
|
||||
|
||||
match StringOrNumber::deserialize(deserializer)? {
|
||||
StringOrNumber::String(s) => {
|
||||
let seconds = s.parse::<f64>().map_err(D::Error::custom)?;
|
||||
Ok((seconds * 1000.0) as u64)
|
||||
}
|
||||
StringOrNumber::Float(f) => Ok((f * 1000.0) as u64),
|
||||
StringOrNumber::Int(i) => Ok(i),
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialize an optional string or number into Option<u32>
|
||||
fn deserialize_optional_string_or_u32<'de, D>(deserializer: D) -> Result<Option<u32>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
use serde::de::Error;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum StringOrU32 {
|
||||
String(String),
|
||||
Number(u32),
|
||||
}
|
||||
|
||||
let opt = Option::<StringOrU32>::deserialize(deserializer)?;
|
||||
match opt {
|
||||
None => Ok(None),
|
||||
Some(StringOrU32::String(s)) => {
|
||||
if s.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
s.parse::<u32>().map(Some).map_err(D::Error::custom)
|
||||
}
|
||||
}
|
||||
Some(StringOrU32::Number(n)) => Ok(Some(n)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialize an optional string or number into Option<f32>
|
||||
fn deserialize_optional_string_or_f32<'de, D>(deserializer: D) -> Result<Option<f32>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
use serde::de::Error;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum StringOrF32 {
|
||||
String(String),
|
||||
Float(f32),
|
||||
Int(i32),
|
||||
}
|
||||
|
||||
let opt = Option::<StringOrF32>::deserialize(deserializer)?;
|
||||
match opt {
|
||||
None => Ok(None),
|
||||
Some(StringOrF32::String(s)) => {
|
||||
if s.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
s.parse::<f32>().map(Some).map_err(D::Error::custom)
|
||||
}
|
||||
}
|
||||
Some(StringOrF32::Float(f)) => Ok(Some(f)),
|
||||
Some(StringOrF32::Int(i)) => Ok(Some(i as f32)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Bitrate quality levels for Radio Paradise streams
|
||||
///
|
||||
/// Radio Paradise offers 5 quality levels:
|
||||
@@ -77,11 +180,13 @@ pub struct Song {
|
||||
/// Song title
|
||||
pub title: String,
|
||||
|
||||
/// Album name
|
||||
pub album: String,
|
||||
/// Album name (may be missing for promos/announcements)
|
||||
#[serde(default)]
|
||||
pub album: Option<String>,
|
||||
|
||||
/// Year of release
|
||||
#[serde(default)]
|
||||
/// Note: API returns this as a string, we deserialize to u32
|
||||
#[serde(default, deserialize_with = "deserialize_optional_string_or_u32")]
|
||||
pub year: Option<u32>,
|
||||
|
||||
/// Elapsed time from start of block in milliseconds
|
||||
@@ -95,7 +200,8 @@ pub struct Song {
|
||||
pub cover: Option<String>,
|
||||
|
||||
/// Rating (0-10)
|
||||
#[serde(default)]
|
||||
/// Note: API returns this as a string, we deserialize to f32
|
||||
#[serde(default, deserialize_with = "deserialize_optional_string_or_f32")]
|
||||
pub rating: Option<f32>,
|
||||
|
||||
/// Additional metadata
|
||||
@@ -130,12 +236,18 @@ pub struct ImageInfo {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Block {
|
||||
/// Event ID for this block (start event)
|
||||
/// Note: API returns this as a string, we deserialize to u64
|
||||
#[serde(deserialize_with = "deserialize_string_or_u64")]
|
||||
pub event: EventId,
|
||||
|
||||
/// Event ID for the next block (end event)
|
||||
/// Note: API returns this as a string, we deserialize to u64
|
||||
#[serde(deserialize_with = "deserialize_string_or_u64")]
|
||||
pub end_event: EventId,
|
||||
|
||||
/// Total length of the block in milliseconds
|
||||
/// Note: API returns this as a string in seconds (e.g., "1715.54"), we convert to ms
|
||||
#[serde(deserialize_with = "deserialize_length")]
|
||||
pub length: DurationMs,
|
||||
|
||||
/// URL to stream this block
|
||||
@@ -256,7 +368,7 @@ mod tests {
|
||||
let song = Song {
|
||||
artist: "Test Artist".to_string(),
|
||||
title: "Test Song".to_string(),
|
||||
album: "Test Album".to_string(),
|
||||
album: Some("Test Album".to_string()),
|
||||
year: Some(2024),
|
||||
elapsed: 1000,
|
||||
duration: 5000,
|
||||
|
||||
@@ -109,7 +109,7 @@ impl From<Block> for BlockResponse {
|
||||
index,
|
||||
artist: song.artist.clone(),
|
||||
title: song.title.clone(),
|
||||
album: song.album.clone(),
|
||||
album: song.album.clone().unwrap_or_default(),
|
||||
year: song.year,
|
||||
elapsed_ms: song.elapsed,
|
||||
duration_ms: song.duration,
|
||||
@@ -138,7 +138,7 @@ impl From<NowPlaying> for NowPlayingResponse {
|
||||
index,
|
||||
artist: song.artist.clone(),
|
||||
title: song.title.clone(),
|
||||
album: song.album.clone(),
|
||||
album: song.album.clone().unwrap_or_default(),
|
||||
year: song.year,
|
||||
elapsed_ms: song.elapsed,
|
||||
duration_ms: song.duration,
|
||||
@@ -153,7 +153,7 @@ impl From<NowPlaying> for NowPlayingResponse {
|
||||
index,
|
||||
artist: song.artist.clone(),
|
||||
title: song.title.clone(),
|
||||
album: song.album.clone(),
|
||||
album: song.album.clone().unwrap_or_default(),
|
||||
year: song.year,
|
||||
elapsed_ms: song.elapsed,
|
||||
duration_ms: song.duration,
|
||||
|
||||
@@ -179,8 +179,10 @@ impl RadioParadiseSource {
|
||||
track = track.with_artist(song.artist.clone());
|
||||
}
|
||||
|
||||
if !song.album.is_empty() {
|
||||
track = track.with_album(song.album.clone());
|
||||
if let Some(ref album) = song.album {
|
||||
if !album.is_empty() {
|
||||
track = track.with_album(album.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if song.duration > 0 {
|
||||
@@ -229,11 +231,7 @@ impl RadioParadiseSource {
|
||||
} else {
|
||||
None
|
||||
},
|
||||
album: if !song.album.is_empty() {
|
||||
Some(song.album.clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
album: song.album.clone().filter(|a| !a.is_empty()),
|
||||
duration_secs: if song.duration > 0 {
|
||||
Some((song.duration / 1000) as u64)
|
||||
} else {
|
||||
@@ -559,7 +557,7 @@ impl MusicSource for RadioParadiseSource {
|
||||
let audio_metadata = AudioMetadata {
|
||||
title: Some(song.title.clone()),
|
||||
artist: if !song.artist.is_empty() { Some(song.artist.clone()) } else { None },
|
||||
album: if !song.album.is_empty() { Some(song.album.clone()) } else { None },
|
||||
album: song.album.clone().filter(|a| !a.is_empty()),
|
||||
duration_secs: if song.duration > 0 { Some((song.duration / 1000) as u64) } else { None },
|
||||
year: None,
|
||||
track_number: None,
|
||||
|
||||
Reference in New Issue
Block a user