Prise en compte des métadonnées dans PMO Contrôle

This commit is contained in:
2025-12-03 18:27:45 +01:00
parent f99ab2a41e
commit 825254a2bb
10 changed files with 165 additions and 2 deletions

View File

@@ -356,6 +356,13 @@ fn playback_item_from_entry(server: &MusicServer, entry: &MediaEntry) -> Option<
item.title = Some(entry.title.clone());
item.server_id = Some(server.id().clone());
item.object_id = Some(entry.id.clone());
item.artist = entry.artist.clone();
item.album = entry.album.clone();
item.genre = entry.genre.clone();
item.album_art_uri = entry.album_art_uri.clone();
item.date = entry.date.clone();
item.track_number = entry.track_number.clone();
item.creator = entry.creator.clone();
Some(item)
}
@@ -431,6 +438,34 @@ fn spawn_renderer_event_thread(
io::stdout().flush().ok();
}
}
RendererEvent::MetadataChanged { id, metadata } => {
if id == renderer_id {
println!("\n[EVENT] Metadata changed:");
if let Some(title) = &metadata.title {
println!(" Title: {}", title);
}
if let Some(artist) = &metadata.artist {
println!(" Artist: {}", artist);
}
if let Some(album) = &metadata.album {
println!(" Album: {}", album);
}
if let Some(genre) = &metadata.genre {
println!(" Genre: {}", genre);
}
if let Some(date) = &metadata.date {
println!(" Date: {}", date);
}
if let Some(track_number) = &metadata.track_number {
println!(" Track: {}", track_number);
}
if let Some(album_art_uri) = &metadata.album_art_uri {
println!(" Album Art: {}", album_art_uri);
}
print!("> ");
io::stdout().flush().ok();
}
}
}
}
Err(_) => {
@@ -722,7 +757,16 @@ fn show_queue(
} else {
for (idx, item) in queue.iter().enumerate() {
let title = item.title.as_deref().unwrap_or("<no title>");
println!(" [{}] {}", idx, title);
let artist = item.artist.as_deref().unwrap_or("");
let album = item.album.as_deref().unwrap_or("");
if !artist.is_empty() && !album.is_empty() {
println!(" [{}] {} - {} ({})", idx, artist, title, album);
} else if !artist.is_empty() {
println!(" [{}] {} - {}", idx, artist, title);
} else {
println!(" [{}] {}", idx, title);
}
}
}

View File

@@ -301,6 +301,8 @@ impl ArylicPlaybackInfo {
} else {
None
},
track_metadata: None,
track_uri: None,
}
}
}

View File

@@ -391,6 +391,8 @@ pub struct PositionInfo {
pub track_duration: Option<String>, // HH:MM:SS or None
pub rel_time: Option<String>, // HH:MM:SS or None
pub abs_time: Option<String>, // HH:MM:SS or None
pub track_metadata: Option<String>, // DIDL-Lite XML or None
pub track_uri: Option<String>, // Current track URI
}
impl AvTransportClient {
@@ -442,11 +444,15 @@ fn parse_position_info(envelope: &SoapEnvelope) -> Result<PositionInfo> {
let track_duration = opt(response, "TrackDuration");
let rel_time = opt(response, "RelTime");
let abs_time = opt(response, "AbsTime");
let track_metadata = opt(response, "TrackMetaData");
let track_uri = opt(response, "TrackURI");
Ok(PositionInfo {
track,
track_duration,
rel_time,
abs_time,
track_metadata,
track_uri,
})
}

View File

@@ -12,6 +12,8 @@ pub struct PlaybackPositionInfo {
pub rel_time: Option<String>, // position courante
pub abs_time: Option<String>, // si pertinent
pub track_duration: Option<String>, // durée totale
pub track_metadata: Option<String>, // DIDL-Lite XML from GetPositionInfo
pub track_uri: Option<String>, // Current track URI
}
pub trait PlaybackPosition {
fn playback_position(&self) -> Result<PlaybackPositionInfo>;

View File

@@ -14,6 +14,7 @@ use crate::capabilities::{
PlaybackPosition, PlaybackPositionInfo, PlaybackState, PlaybackStatus, TransportControl,
VolumeControl,
};
use crate::model::TrackMetadata;
use crate::discovery::DiscoveryManager;
use crate::events::{MediaServerEventBus, RendererEventBus};
use crate::media_server::{
@@ -171,6 +172,22 @@ impl ControlPoint {
});
}
// Extract and emit metadata changes
if let Some(metadata) = extract_track_metadata(&position) {
let metadata_changed = match prev_snapshot.last_metadata.as_ref() {
Some(prev) => prev != &metadata,
None => true,
};
if metadata_changed {
runtime_cp.emit_renderer_event(RendererEvent::MetadataChanged {
id: renderer_id.clone(),
metadata: metadata.clone(),
});
new_snapshot.last_metadata = Some(metadata);
}
}
new_snapshot.position = Some(position);
}
@@ -601,7 +618,6 @@ impl ControlPoint {
let playback = (|| -> anyhow::Result<()> {
renderer.play_uri(&item.uri, "")?;
renderer.play()?;
Ok(())
})();
@@ -809,6 +825,7 @@ struct RendererRuntimeSnapshot {
position: Option<PlaybackPositionInfo>,
last_volume: Option<u16>,
last_mute: Option<bool>,
last_metadata: Option<TrackMetadata>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
@@ -1109,6 +1126,13 @@ fn playback_item_from_entry(server: &MusicServer, entry: &MediaEntry) -> Option<
item.title = Some(entry.title.clone());
item.server_id = Some(server.id().clone());
item.object_id = Some(entry.id.clone());
item.artist = entry.artist.clone();
item.album = entry.album.clone();
item.genre = entry.genre.clone();
item.album_art_uri = entry.album_art_uri.clone();
item.date = entry.date.clone();
item.track_number = entry.track_number.clone();
item.creator = entry.creator.clone();
Some(item)
}
@@ -1206,4 +1230,34 @@ fn playback_position_equal(a: &PlaybackPositionInfo, b: &PlaybackPositionInfo) -
&& a.rel_time == b.rel_time
&& a.abs_time == b.abs_time
&& a.track_duration == b.track_duration
&& a.track_metadata == b.track_metadata
&& a.track_uri == b.track_uri
}
/// Extract TrackMetadata from DIDL-Lite XML in PlaybackPositionInfo.
fn extract_track_metadata(position: &PlaybackPositionInfo) -> Option<TrackMetadata> {
let didl_xml = position.track_metadata.as_ref()?;
// Parse DIDL-Lite XML
let didl = match pmodidl::parse_metadata::<pmodidl::DIDLLite>(didl_xml) {
Ok(parsed) => parsed.data,
Err(err) => {
debug!(error = %err, "Failed to parse DIDL-Lite metadata from GetPositionInfo");
return None;
}
};
// Extract first item metadata
let item = didl.items.first()?;
Some(TrackMetadata {
title: Some(item.title.clone()),
artist: item.artist.clone(),
album: item.album.clone(),
genre: item.genre.clone(),
album_art_uri: item.album_art.clone(),
date: item.date.clone(),
track_number: item.original_track_number.clone(),
creator: item.creator.clone(),
})
}

View File

@@ -227,6 +227,8 @@ impl LinkPlayStatus {
} else {
None
},
track_metadata: None,
track_uri: None,
}
}
}

View File

@@ -47,6 +47,13 @@ pub struct MediaEntry {
pub is_container: bool,
pub class: String,
pub resources: Vec<MediaResource>,
pub artist: Option<String>,
pub album: Option<String>,
pub genre: Option<String>,
pub album_art_uri: Option<String>,
pub date: Option<String>,
pub track_number: Option<String>,
pub creator: Option<String>,
}
/// Backend-agnostic media browsing contract.
@@ -323,6 +330,13 @@ fn map_didl_entries(xml: &str) -> Result<Vec<MediaEntry>> {
is_container: true,
class: container.class,
resources: Vec::new(),
artist: None,
album: None,
genre: None,
album_art_uri: None,
date: None,
track_number: None,
creator: None,
});
}
@@ -344,6 +358,13 @@ fn map_didl_entries(xml: &str) -> Result<Vec<MediaEntry>> {
is_container: false,
class: item.class,
resources,
artist: item.artist,
album: item.album,
genre: item.genre,
album_art_uri: item.album_art,
date: item.date,
track_number: item.original_track_number,
creator: item.creator,
});
}

View File

@@ -4,6 +4,18 @@ use crate::media_server::ServerId;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RendererId(pub String);
#[derive(Clone, Debug, PartialEq)]
pub struct TrackMetadata {
pub title: Option<String>,
pub artist: Option<String>,
pub album: Option<String>,
pub genre: Option<String>,
pub album_art_uri: Option<String>,
pub date: Option<String>,
pub track_number: Option<String>,
pub creator: Option<String>,
}
#[derive(Clone, Debug)]
pub enum RendererProtocol {
UpnpAvOnly,
@@ -79,6 +91,10 @@ pub enum RendererEvent {
id: RendererId,
mute: bool,
},
MetadataChanged {
id: RendererId,
metadata: TrackMetadata,
},
}
#[derive(Clone, Debug)]

View File

@@ -8,6 +8,13 @@ pub struct PlaybackItem {
pub title: Option<String>,
pub server_id: Option<ServerId>,
pub object_id: Option<String>,
pub artist: Option<String>,
pub album: Option<String>,
pub genre: Option<String>,
pub album_art_uri: Option<String>,
pub date: Option<String>,
pub track_number: Option<String>,
pub creator: Option<String>,
}
impl PlaybackItem {
@@ -17,6 +24,13 @@ impl PlaybackItem {
title: None,
server_id: None,
object_id: None,
artist: None,
album: None,
genre: None,
album_art_uri: None,
date: None,
track_number: None,
creator: None,
}
}
}

View File

@@ -290,6 +290,8 @@ impl PlaybackPosition for UpnpRenderer {
rel_time: raw.rel_time,
abs_time: raw.abs_time,
track_duration: raw.track_duration,
track_metadata: raw.track_metadata,
track_uri: raw.track_uri,
})
}
}