Session de debug radio paradise

This commit is contained in:
2025-10-19 01:21:31 +02:00
parent 23c6d8b7a7
commit 9932007bba
6 changed files with 136 additions and 20 deletions

Binary file not shown.

View File

@@ -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);

View File

@@ -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));

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,