From e9109a8a0c9d130c15144a373ca758a14549dd1a Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Sun, 19 Oct 2025 01:21:31 +0200 Subject: [PATCH] Session de debug radio paradise --- pmoparadise/.pmomusic_audio/cache.db | Bin 0 -> 20480 bytes pmoparadise/examples/now_playing.rs | 8 +- .../src/mediaserver/content_directory.rs | 4 +- pmoparadise/src/models.rs | 124 +++++++++++++++++- pmoparadise/src/pmoserver_ext.rs | 6 +- pmoparadise/src/source.rs | 14 +- 6 files changed, 136 insertions(+), 20 deletions(-) create mode 100644 pmoparadise/.pmomusic_audio/cache.db diff --git a/pmoparadise/.pmomusic_audio/cache.db b/pmoparadise/.pmomusic_audio/cache.db new file mode 100644 index 0000000000000000000000000000000000000000..6bb75df69dceb4727044f982454cf1a9d6b66f71 GIT binary patch literal 20480 zcmeI#L2J}N6u|LGck4=F+u|kHCuN}vMHCUQrrS}LuG_ke(4IovM2t;0Z8CxNsCe=- z^zO&-Yk4#YEt@u7Kfv-INHRlS@@9TBmv{1E6q!&yC+UT6^_O*9>e;U+-L1c>-TkdLuebheJ}o*l1Q0*~0R#|0009ILKmdVd0`qpq ze$}(S??kg8oJYaj^Yb7|Jd^s<^URCWygptZOtj-_8IBM1sXP=dM`NjfA-l1k886Sm zK%A4oo_vW+R`&YeS{-{(WVRp9JWb*_oSG<^)qmIi{{$8vHRrK-*Z(0_$L{s4pDp8m zio<$>dVlREnd|J2^u1MW$h~%XF0MXx<#;lD?@Z3*P@nBJg0dt}r=gdpaTVMQSWI$B zK*cv4yZW7;$bo+Ad>py*y!oSrdO54+`-{-{fp2{8YnIHaTz4khf4%m|KI~bl+MMhv zE^7JWWSFEH-#sHQ>Skpv|F}GC-SW0w-e?FQfB*srAbqfW2LS;D5I_I{1Q0*~0R#|00D;vPp#ER|J!XXn UAb 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); diff --git a/pmoparadise/src/mediaserver/content_directory.rs b/pmoparadise/src/mediaserver/content_directory.rs index 32208168..be2e0b03 100644 --- a/pmoparadise/src/mediaserver/content_directory.rs +++ b/pmoparadise/src/mediaserver/content_directory.rs @@ -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)); diff --git a/pmoparadise/src/models.rs b/pmoparadise/src/models.rs index dc091ca1..3b422cd5 100644 --- a/pmoparadise/src/models.rs +++ b/pmoparadise/src/models.rs @@ -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 +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::().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 +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::().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 +fn deserialize_optional_string_or_u32<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + use serde::de::Error; + + #[derive(Deserialize)] + #[serde(untagged)] + enum StringOrU32 { + String(String), + Number(u32), + } + + let opt = Option::::deserialize(deserializer)?; + match opt { + None => Ok(None), + Some(StringOrU32::String(s)) => { + if s.is_empty() { + Ok(None) + } else { + s.parse::().map(Some).map_err(D::Error::custom) + } + } + Some(StringOrU32::Number(n)) => Ok(Some(n)), + } +} + +/// Deserialize an optional string or number into Option +fn deserialize_optional_string_or_f32<'de, D>(deserializer: D) -> Result, 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::::deserialize(deserializer)?; + match opt { + None => Ok(None), + Some(StringOrF32::String(s)) => { + if s.is_empty() { + Ok(None) + } else { + s.parse::().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, /// 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, /// Elapsed time from start of block in milliseconds @@ -95,7 +200,8 @@ pub struct Song { pub cover: Option, /// 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, /// 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, diff --git a/pmoparadise/src/pmoserver_ext.rs b/pmoparadise/src/pmoserver_ext.rs index b974b7fc..e4536c34 100644 --- a/pmoparadise/src/pmoserver_ext.rs +++ b/pmoparadise/src/pmoserver_ext.rs @@ -109,7 +109,7 @@ impl From 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 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 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, diff --git a/pmoparadise/src/source.rs b/pmoparadise/src/source.rs index c7f83f3f..7db8a0e1 100644 --- a/pmoparadise/src/source.rs +++ b/pmoparadise/src/source.rs @@ -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,