From c972182b3bcfccc3e4a0c3a8bc6da91b048802ef Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Fri, 28 Nov 2025 18:41:53 +0100 Subject: [PATCH] =?UTF-8?q?On=20applique=20le=20nouveau=20trait=20=C3=A0?= =?UTF-8?q?=20PMOdIdl.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 15 +++- pmodidl/Cargo.toml | 2 + pmodidl/src/lib.rs | 186 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 11716945..328f7e5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3173,10 +3173,12 @@ version = "0.1.0" dependencies = [ "bevy_reflect", "bevy_reflect_derive", + "pmoutils", "quick-xml 0.38.4", "serde", "utoipa", "utoipa-swagger-ui", + "xmltree 0.10.3", ] [[package]] @@ -3229,6 +3231,7 @@ dependencies = [ "pmoserver", "pmosource", "pmoupnp", + "pmoutils", "quick-xml 0.38.4", "serde", "serde_json", @@ -3423,7 +3426,7 @@ dependencies = [ "url", "utoipa", "uuid", - "xmltree", + "xmltree 0.11.0", ] [[package]] @@ -3436,6 +3439,7 @@ dependencies = [ "quick-xml 0.38.4", "sysinfo", "users", + "xmltree 0.10.3", ] [[package]] @@ -5804,6 +5808,15 @@ version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" +[[package]] +name = "xmltree" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" +dependencies = [ + "xml-rs", +] + [[package]] name = "xmltree" version = "0.11.0" diff --git a/pmodidl/Cargo.toml b/pmodidl/Cargo.toml index 3ed5c8a0..d9fa43db 100644 --- a/pmodidl/Cargo.toml +++ b/pmodidl/Cargo.toml @@ -10,3 +10,5 @@ utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } quick-xml = { version = "0.38.3", features = ["serialize"] } bevy_reflect = "0.17.1" bevy_reflect_derive = "0.17.1" +pmoutils = { path = "../pmoutils" } +xmltree = "0.10" diff --git a/pmodidl/src/lib.rs b/pmodidl/src/lib.rs index a85d5e0f..c39bd6eb 100644 --- a/pmodidl/src/lib.rs +++ b/pmodidl/src/lib.rs @@ -3,8 +3,10 @@ //! Parser et utilitaires pour le format DIDL-Lite utilisé dans UPnP/DLNA. use bevy_reflect::Reflect; +use pmoutils::ToXmlElement; use serde::{Deserialize, Serialize}; use std::fmt::Write; +use xmltree::{Element, XMLNode}; // ============= Couche d'abstraction générique ============= @@ -274,6 +276,26 @@ impl Default for DIDLLite { } impl DIDLLite { + /// Applique les namespaces sur un élément xmltree. + fn set_namespaces(&self, elem: &mut Element) { + elem.attributes.insert("xmlns".into(), self.xmlns.clone()); + if let Some(ref upnp) = self.xmlns_upnp { + elem.attributes.insert("xmlns:upnp".into(), upnp.clone()); + } + if let Some(ref dc) = self.xmlns_dc { + elem.attributes.insert("xmlns:dc".into(), dc.clone()); + } + if let Some(ref dlna) = self.xmlns_dlna { + elem.attributes.insert("xmlns:dlna".into(), dlna.clone()); + } + if let Some(ref sec) = self.xmlns_sec { + elem.attributes.insert("xmlns:sec".into(), sec.clone()); + } + if let Some(ref pv) = self.xmlns_pv { + elem.attributes.insert("xmlns:pv".into(), pv.clone()); + } + } + /// Itère sur tous les containers de manière récursive pub fn all_containers(&self) -> impl Iterator { AllContainersIter::new(&self.containers) @@ -378,6 +400,19 @@ impl Container { } impl Item { + /// Formate la date pour satisfaire les clients stricts (YYYY-MM-DD). Si seule + /// l'année est fournie, on complète avec "-01-01". + fn normalized_date(&self) -> Option { + self.date.as_ref().map(|d| { + let trimmed = d.trim(); + if trimmed.len() == 4 && trimmed.chars().all(|c| c.is_ascii_digit()) { + format!("{}-01-01", trimmed) + } else { + trimmed.to_string() + } + }) + } + /// Itère sur les ressources audio uniquement pub fn audio_resources(&self) -> impl Iterator { self.resources @@ -493,6 +528,157 @@ impl Item { } } +// ============= Implémentation ToXmlElement ============= + +fn text_element(name: &str, value: &str) -> Element { + let mut e = Element::new(name); + e.children.push(XMLNode::Text(value.to_string())); + e +} + +impl ToXmlElement for DIDLLite { + fn to_xml_element(&self) -> Element { + let mut root = Element::new("DIDL-Lite"); + self.set_namespaces(&mut root); + for c in &self.containers { + root.children.push(XMLNode::Element(c.to_xml_element())); + } + for i in &self.items { + root.children.push(XMLNode::Element(i.to_xml_element())); + } + root + } +} + +impl ToXmlElement for Container { + fn to_xml_element(&self) -> Element { + let mut elem = Element::new("container"); + elem.attributes.insert("id".into(), self.id.clone()); + elem.attributes.insert("parentID".into(), self.parent_id.clone()); + if let Some(ref r) = self.restricted { + elem.attributes.insert("restricted".into(), r.clone()); + } + if let Some(ref cc) = self.child_count { + elem.attributes.insert("childCount".into(), cc.clone()); + } + if let Some(ref searchable) = self.searchable { + elem.attributes.insert("searchable".into(), searchable.clone()); + } + + elem.children.push(XMLNode::Element(text_element("dc:title", &self.title))); + elem.children.push(XMLNode::Element(text_element("upnp:class", &self.class))); + + for c in &self.containers { + elem.children.push(XMLNode::Element(c.to_xml_element())); + } + for i in &self.items { + elem.children.push(XMLNode::Element(i.to_xml_element())); + } + + elem + } +} + +impl ToXmlElement for Item { + fn to_xml_element(&self) -> Element { + let mut elem = Element::new("item"); + elem.attributes.insert("id".into(), self.id.clone()); + elem.attributes.insert("parentID".into(), self.parent_id.clone()); + if let Some(ref r) = self.restricted { + elem.attributes.insert("restricted".into(), r.clone()); + } + + elem.children.push(XMLNode::Element(text_element("dc:title", &self.title))); + + if let Some(ref c) = self.creator { + elem.children.push(XMLNode::Element(text_element("dc:creator", c))); + } + + elem.children + .push(XMLNode::Element(text_element("upnp:class", &self.class))); + + if let Some(ref artist) = self.artist { + elem.children + .push(XMLNode::Element(text_element("upnp:artist", artist))); + } + if let Some(ref album) = self.album { + elem.children + .push(XMLNode::Element(text_element("upnp:album", album))); + } + if let Some(ref genre) = self.genre { + elem.children + .push(XMLNode::Element(text_element("upnp:genre", genre))); + } + if let Some(ref art) = self.album_art { + elem.children + .push(XMLNode::Element(text_element("upnp:albumArtURI", art))); + } + if let Some(date) = self.normalized_date() { + elem.children + .push(XMLNode::Element(text_element("dc:date", &date))); + } + if let Some(ref track) = self.original_track_number { + elem.children.push(XMLNode::Element(text_element( + "upnp:originalTrackNumber", + track, + ))); + } + + for res in &self.resources { + elem.children.push(XMLNode::Element(res.to_xml_element())); + } + for desc in &self.descriptions { + elem.children.push(XMLNode::Element(desc.to_xml_element())); + } + + elem + } +} + +impl ToXmlElement for Resource { + fn to_xml_element(&self) -> Element { + let mut elem = Element::new("res"); + elem.attributes + .insert("protocolInfo".into(), self.protocol_info.clone()); + if let Some(ref bps) = self.bits_per_sample { + elem.attributes.insert("bitsPerSample".into(), bps.clone()); + } + if let Some(ref freq) = self.sample_frequency { + elem.attributes.insert("sampleFrequency".into(), freq.clone()); + } + if let Some(ref ch) = self.nr_audio_channels { + elem.attributes.insert("nrAudioChannels".into(), ch.clone()); + } + if let Some(ref dur) = self.duration { + elem.attributes.insert("duration".into(), dur.clone()); + } + elem.children.push(XMLNode::Text(self.url.clone())); + elem + } +} + +impl ToXmlElement for Description { + fn to_xml_element(&self) -> Element { + let mut elem = Element::new("desc"); + if let Some(ref id) = self.id { + elem.attributes.insert("id".into(), id.clone()); + } + if let Some(ref ns) = self.namespace { + elem.attributes.insert("nameSpace".into(), ns.clone()); + } + if let Some(ref gain) = self.track_gain { + elem.children + .push(XMLNode::Element(text_element("track_gain", gain))); + } + if let Some(ref peak) = self.track_peak { + elem.children + .push(XMLNode::Element(text_element("track_peak", peak))); + } + elem + } +} + + // ============= Itérateurs personnalisés ============= struct AllContainersIter<'a> {