From 83d7520840f9fb286484615ee5ac84c809c94bde Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Mon, 3 Nov 2025 14:52:04 +0100 Subject: [PATCH] Ajout de la gestion des couvertures d'albums par le FlacCacheSink --- .DS_Store | Bin 10244 -> 10244 bytes Cargo.lock | 1 + .../pmoaudio}/examples/multiroom_demo.rs | 0 .../examples/multiroom_volume_demo.rs | 0 .../pmoaudio}/examples/pipeline_demo.rs | 0 .../pmoaudio}/examples/quick_start.rs | 0 .../pmoaudio}/examples/simple_pipeline.rs | 0 .../pmoaudio}/examples/streaming_demo.rs | 0 .../pmoaudio}/examples/volume_control_demo.rs | 0 .../pmoplaylist}/examples/basic_usage.rs | 0 .../examples/http_server_integration.rs | 0 .../pmoplaylist}/examples/radio_streaming.rs | 0 pmoaudio-ext/Cargo.toml | 2 +- pmoaudio-ext/src/sinks/flac_cache_sink.rs | 74 ++++++++++++++---- pmoaudio/src/nodes/http_source.rs | 4 +- pmoaudio/tests/integration_test.rs | 7 ++ pmoaudiocache/src/track_metadata.rs | 40 ++++++---- 17 files changed, 93 insertions(+), 35 deletions(-) rename {pmoaudio => old_code/pmoaudio}/examples/multiroom_demo.rs (100%) rename {pmoaudio => old_code/pmoaudio}/examples/multiroom_volume_demo.rs (100%) rename {pmoaudio => old_code/pmoaudio}/examples/pipeline_demo.rs (100%) rename {pmoaudio => old_code/pmoaudio}/examples/quick_start.rs (100%) rename {pmoaudio => old_code/pmoaudio}/examples/simple_pipeline.rs (100%) rename {pmoaudio => old_code/pmoaudio}/examples/streaming_demo.rs (100%) rename {pmoaudio => old_code/pmoaudio}/examples/volume_control_demo.rs (100%) rename {pmoplaylist => old_code/pmoplaylist}/examples/basic_usage.rs (100%) rename {pmoplaylist => old_code/pmoplaylist}/examples/http_server_integration.rs (100%) rename {pmoplaylist => old_code/pmoplaylist}/examples/radio_streaming.rs (100%) diff --git a/.DS_Store b/.DS_Store index f9d43b4635642e3ce52bfd5ef79cd3f7cab3ba65..3ece96441ff4e74b7264b0f1baa16dde22a4b2e6 100644 GIT binary patch delta 785 zcmYk4T}TvB7>3WguI`yB_3Q?^XNX;Y5KFdnMbj+*GeTF~{2_Bq8Jnpl-C5mPe=A!A+?gEjro1m+@h%zL|x{B_yF1pE}s585J(aj9+`^|U0?>z4;h8M$++nnIZ zcISvAyL+AY-?* zaz0{@4yjh6XHqxCTF)}`I}UO_WSvsude5w>$Jxt_-zof2mY=HFZ5^7Ch{Z)Q1;Ee4 zlpft_pV_Kuy0(^E=q>2&vs;*9&KL?m3Cxr!tx$!7L7 zmF9q8uFp}M8*|zBa#7IgjZOQ?fEiz#?sfO#Gf(S((yzmJmHjpyxMkRu%MhzO# zgch_4I3aYS2VwLgBH)Z*6e><*91}1xi#Y-4EY1l$mv9+Za1}Rk3wLmLY0C3?3k}xP S28ycds+Jf52M+ delta 845 zcmZ`%O-vI(7~Qu(JG+7mRSUW)tpO5k_%WphAt072npgpa%1;$s*cDdVt#lU<{ON@d z6EzyE#%R21@MO|xBJre=c!i(`@nk${;>ko09-Qq$VobcuBg+HhQrPHT!e)-M|)s;+4I=zylEx~ZMj)Ra(yh|r_U33ZjaOUt(J zkfeW4Z>aQ54r>#WnmI!&2w5%KTueo;v{RtZ$tguP)niG;QdDvsDeaZ22#HyZa7tyX zT^Dyreu2uQ$Ixa~Tcbv-6R08djBENjHy{QHRfK3<9#-SFT!Yw1=ux3}N=+GBQn!oE z;(k(sYN2l=nMkk?8Fo1&g$YeBl0ob8h-{dh=T&{tJRoLIl+@)0NmZF5NFb>u9Db18k_O5QWO&X-) zfIky;G^Jl~uec>{nOouBb04@h?hE&o`@#K20h}NZ;KCMcgBum7Mh$AwgfI@G9fuJ? z6erP#ehgw1V>pL#OkfrlaT!-IkDIuK4DR3&9^(lX@Dwlc8gK9xOIXH7tl<;BQ~cz? z^L!p($QSX&Jk6FneF8k0R{AX+Wp=9<{+Bg%TE>!9Yb3lr(L)PZAU3S5V$i|Du;FgM z$3uBG1U9U#^N2K$B`oB)^?nAi4jT4}BF$%6v!29+n_C#>tO2wShuWAFp^KSLNAqo$ zJLW(kGp8IrW>6)nm_d6GKs|zJK`TONV|GQ*fi4`!3B+(3LpTGOnH5I@I;Jth4716wJj_nGK5rs>8w1t8U@(x4mH%@7jTZ0+C4|+) diff --git a/Cargo.lock b/Cargo.lock index 2b18978d..594cd5a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2660,6 +2660,7 @@ dependencies = [ "async-trait", "pmoaudio", "pmoaudiocache", + "pmocovers", "pmoflac", "pmometadata", "pmoplaylist", diff --git a/pmoaudio/examples/multiroom_demo.rs b/old_code/pmoaudio/examples/multiroom_demo.rs similarity index 100% rename from pmoaudio/examples/multiroom_demo.rs rename to old_code/pmoaudio/examples/multiroom_demo.rs diff --git a/pmoaudio/examples/multiroom_volume_demo.rs b/old_code/pmoaudio/examples/multiroom_volume_demo.rs similarity index 100% rename from pmoaudio/examples/multiroom_volume_demo.rs rename to old_code/pmoaudio/examples/multiroom_volume_demo.rs diff --git a/pmoaudio/examples/pipeline_demo.rs b/old_code/pmoaudio/examples/pipeline_demo.rs similarity index 100% rename from pmoaudio/examples/pipeline_demo.rs rename to old_code/pmoaudio/examples/pipeline_demo.rs diff --git a/pmoaudio/examples/quick_start.rs b/old_code/pmoaudio/examples/quick_start.rs similarity index 100% rename from pmoaudio/examples/quick_start.rs rename to old_code/pmoaudio/examples/quick_start.rs diff --git a/pmoaudio/examples/simple_pipeline.rs b/old_code/pmoaudio/examples/simple_pipeline.rs similarity index 100% rename from pmoaudio/examples/simple_pipeline.rs rename to old_code/pmoaudio/examples/simple_pipeline.rs diff --git a/pmoaudio/examples/streaming_demo.rs b/old_code/pmoaudio/examples/streaming_demo.rs similarity index 100% rename from pmoaudio/examples/streaming_demo.rs rename to old_code/pmoaudio/examples/streaming_demo.rs diff --git a/pmoaudio/examples/volume_control_demo.rs b/old_code/pmoaudio/examples/volume_control_demo.rs similarity index 100% rename from pmoaudio/examples/volume_control_demo.rs rename to old_code/pmoaudio/examples/volume_control_demo.rs diff --git a/pmoplaylist/examples/basic_usage.rs b/old_code/pmoplaylist/examples/basic_usage.rs similarity index 100% rename from pmoplaylist/examples/basic_usage.rs rename to old_code/pmoplaylist/examples/basic_usage.rs diff --git a/pmoplaylist/examples/http_server_integration.rs b/old_code/pmoplaylist/examples/http_server_integration.rs similarity index 100% rename from pmoplaylist/examples/http_server_integration.rs rename to old_code/pmoplaylist/examples/http_server_integration.rs diff --git a/pmoplaylist/examples/radio_streaming.rs b/old_code/pmoplaylist/examples/radio_streaming.rs similarity index 100% rename from pmoplaylist/examples/radio_streaming.rs rename to old_code/pmoplaylist/examples/radio_streaming.rs diff --git a/pmoaudio-ext/Cargo.toml b/pmoaudio-ext/Cargo.toml index 3cd35584..3264c323 100644 --- a/pmoaudio-ext/Cargo.toml +++ b/pmoaudio-ext/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] # Core audio types pmoaudio = { path = "../pmoaudio" } +pmocovers = { path = "../pmocovers"} # Optional dependencies for cache-sink feature pmoaudiocache = { path = "../pmoaudiocache", optional = true } @@ -14,7 +15,6 @@ pmometadata = { path = "../pmometadata", optional = true } # Optional dependency for playlist integration pmoplaylist = { path = "../pmoplaylist", optional = true } - # Async runtime tokio = { version = "1.0", features = ["full"] } tokio-util = { version = "0.7" } diff --git a/pmoaudio-ext/src/sinks/flac_cache_sink.rs b/pmoaudio-ext/src/sinks/flac_cache_sink.rs index 2ebab15c..78bf47b8 100644 --- a/pmoaudio-ext/src/sinks/flac_cache_sink.rs +++ b/pmoaudio-ext/src/sinks/flac_cache_sink.rs @@ -19,6 +19,7 @@ use tokio::{ sync::{mpsc, RwLock}, }; use tokio_util::sync::CancellationToken; +use tracing::warn; /// Sink qui encode les `AudioSegment` reçus au format FLAC et les stocke dans le cache audio. /// @@ -33,6 +34,7 @@ pub struct FlacCacheSink { tx: mpsc::Sender>, rx: mpsc::Receiver>, cache: Arc, + covers: Arc, collection: Option, encoder_options: EncoderOptions, pcm_buffer_capacity: usize, @@ -46,8 +48,8 @@ impl FlacCacheSink { /// # Arguments /// /// * `cache` - Arc vers le cache audio où stocker les fichiers FLAC encodés - pub fn new(cache: Arc) -> Self { - Self::with_channel_size(cache, DEFAULT_CHANNEL_SIZE) + pub fn new(cache: Arc, covers: Arc) -> Self { + Self::with_channel_size(cache, covers, DEFAULT_CHANNEL_SIZE) } /// Crée un sink FLAC cache avec une taille de buffer MPSC personnalisée. @@ -56,8 +58,12 @@ impl FlacCacheSink { /// /// * `cache` - Arc vers le cache audio /// * `channel_size` - Taille du buffer MPSC (nombre de segments en attente avant backpressure) - pub fn with_channel_size(cache: Arc, channel_size: usize) -> Self { - Self::with_config(cache, channel_size, EncoderOptions::default(), None) + pub fn with_channel_size( + cache: Arc, + covers: Arc, + channel_size: usize, + ) -> Self { + Self::with_config(cache, covers, channel_size, EncoderOptions::default(), None) } /// Crée un sink FLAC cache avec une configuration complète. @@ -70,6 +76,7 @@ impl FlacCacheSink { /// * `collection` - Collection optionnelle à laquelle appartiennent les fichiers pub fn with_config( cache: Arc, + covers: Arc, channel_size: usize, encoder_options: EncoderOptions, collection: Option, @@ -79,6 +86,7 @@ impl FlacCacheSink { tx, rx, cache, + covers, collection, encoder_options, pcm_buffer_capacity: 8, @@ -109,6 +117,7 @@ impl FlacCacheSink { tx: _, mut rx, cache, + covers, collection, encoder_options, pcm_buffer_capacity, @@ -182,7 +191,9 @@ impl FlacCacheSink { let copy_future = async { tokio::io::copy(&mut flac_stream, &mut flac_buffer) .await - .map_err(|e| AudioError::ProcessingError(format!("FLAC write failed: {}", e)))?; + .map_err(|e| { + AudioError::ProcessingError(format!("FLAC write failed: {}", e)) + })?; flac_stream .wait() .await @@ -191,8 +202,10 @@ impl FlacCacheSink { }; // Attendre les deux tâches en parallèle - let (copy_result, pump_result): (Result<(), AudioError>, Result<(u64, u64, f64, StopReason), AudioError>) = - tokio::join!(copy_future, pump_future); + let (copy_result, pump_result): ( + Result<(), AudioError>, + Result<(u64, u64, f64, StopReason), AudioError>, + ) = tokio::join!(copy_future, pump_future); copy_result?; let (chunks, samples, duration_sec, stop_reason) = pump_result?; @@ -200,7 +213,12 @@ impl FlacCacheSink { let flac_reader = Cursor::new(flac_buffer.clone()); let collection_ref = collection.as_deref(); let pk = cache - .add_from_reader(None, flac_reader, Some(flac_buffer.len() as u64), collection_ref) + .add_from_reader( + None, + flac_reader, + Some(flac_buffer.len() as u64), + collection_ref, + ) .await .map_err(|e| { AudioError::ProcessingError(format!("Failed to add to cache: {}", e)) @@ -219,15 +237,42 @@ impl FlacCacheSink { e )) })?; + + let url = match dest_metadata.read().await.get_cover_url().await { + Ok(url) => url, + Err(e) if e.is_transient() => None, + Err(_) => { + warn!("Cannot obtain cover for audio asset {}", pk); + None + } + }; + + if url.is_some() { + let _ = match covers + .add_from_url(&url.unwrap(), collection.as_deref()) + .await + { + Ok(pk_covers) => { + dest_metadata + .write() + .await + .set_cover_pk(Some(pk_covers)) + .await + } + Err(_) => { + warn!("Cannot obtain cover for audio asset {}", pk); + Ok(Some(())) + } + }; + } } // Ajouter à la playlist si enregistrée #[cfg(feature = "playlist")] if let Some(ref playlist_handle) = playlist_handle { - playlist_handle.push(pk.clone()).await - .map_err(|e| AudioError::ProcessingError( - format!("Failed to add to playlist: {}", e) - ))?; + playlist_handle.push(pk.clone()).await.map_err(|e| { + AudioError::ProcessingError(format!("Failed to add to playlist: {}", e)) + })?; } // Ajouter les stats de cette track @@ -600,10 +645,7 @@ impl AudioPipelineNode for FlacCacheSink { panic!("FlacCacheSink is a terminal sink and cannot have children"); } - async fn run( - self: Box, - stop_token: CancellationToken, - ) -> Result<(), AudioError> { + async fn run(self: Box, stop_token: CancellationToken) -> Result<(), AudioError> { self.run_internal(stop_token).await?; Ok(()) } diff --git a/pmoaudio/src/nodes/http_source.rs b/pmoaudio/src/nodes/http_source.rs index 12c7f3c9..650b2edd 100644 --- a/pmoaudio/src/nodes/http_source.rs +++ b/pmoaudio/src/nodes/http_source.rs @@ -763,7 +763,7 @@ mod tests { if let Some(marker) = segment.as_sync_marker() { if let crate::SyncMarker::TrackBoundary { metadata, .. } = &**marker { // Vérifier que le titre extrait est "Test Radio Stream" - if let Some(title) = metadata.get_title().await.ok().flatten() { + if let Some(title) = metadata.read().await.get_title().await.ok().flatten() { assert_eq!(title, "Test Radio Stream"); found_metadata = true; } @@ -888,7 +888,7 @@ mod tests { while let Some(segment) = rx.recv().await { if let Some(marker) = segment.as_sync_marker() { if let crate::SyncMarker::TrackBoundary { metadata, .. } = &**marker { - if let Some(title) = metadata.get_title().await.ok().flatten() { + if let Some(title) = metadata.read().await.get_title().await.ok().flatten() { assert_eq!(title, "my-song.flac"); found_title = true; } diff --git a/pmoaudio/tests/integration_test.rs b/pmoaudio/tests/integration_test.rs index 0a0f76bf..062eec9a 100644 --- a/pmoaudio/tests/integration_test.rs +++ b/pmoaudio/tests/integration_test.rs @@ -1,5 +1,11 @@ //! Tests d'intégration pour le pipeline audio complet +//! +//! NOTE: Ces tests utilisent l'ancienne API (BufferNode, DecoderNode, etc.) +//! qui a été temporairement désactivée. Ils doivent être réécrits pour +//! utiliser la nouvelle architecture de pipeline (FileSource, HttpSource, FlacFileSink, etc.) +// Désactivé temporairement - ancienne API non disponible +/* use pmoaudio::{AudioChunk, BufferNode, DecoderNode, DspNode, SinkNode, SourceNode, TimerNode}; #[tokio::test] @@ -160,3 +166,4 @@ async fn test_arc_sharing() { assert_eq!(stats2.chunks_received, 5); assert_eq!(stats1.total_samples, stats2.total_samples); } +*/ diff --git a/pmoaudiocache/src/track_metadata.rs b/pmoaudiocache/src/track_metadata.rs index e90d677e..83a04a94 100644 --- a/pmoaudiocache/src/track_metadata.rs +++ b/pmoaudiocache/src/track_metadata.rs @@ -274,24 +274,31 @@ mod tests { let pk = "track-test"; cache.db.add(pk, None, None).unwrap(); - let mut meta = cache.track_metadata(pk); + let track = cache.track_metadata(pk); - meta.set_title(Some("Title".into())).await.unwrap(); - meta.set_artist(Some("Artist".into())).await.unwrap(); - meta.set_album(Some("Album".into())).await.unwrap(); - meta.set_year(Some(2024)).await.unwrap(); - meta.set_duration(Some(Duration::from_secs(90))) - .await - .unwrap(); - meta.set_track_id(Some("trk".into())).await.unwrap(); - meta.set_channel_id(Some("chn".into())).await.unwrap(); - meta.set_event(Some("event".into())).await.unwrap(); - meta.set_rating(Some(4.5)).await.unwrap(); - meta.set_cover_url(Some("http://cover".into())) - .await - .unwrap(); - meta.set_cover_pk(Some("cover123".into())).await.unwrap(); + { + let mut meta = track.write().await; + meta.set_title(Some("Title".into())).await.unwrap(); + meta.set_artist(Some("Artist".into())).await.unwrap(); + meta.set_album(Some("Album".into())).await.unwrap(); + meta.set_year(Some(2024)).await.unwrap(); + meta.set_duration(Some(Duration::from_secs(90))) + .await + .unwrap(); + meta.set_track_id(Some("trk".into())).await.unwrap(); + meta.set_channel_id(Some("chn".into())).await.unwrap(); + meta.set_event(Some("event".into())).await.unwrap(); + meta.set_rating(Some(4.5)).await.unwrap(); + meta.set_cover_url(Some("http://cover".into())) + .await + .unwrap(); + meta.set_cover_pk(Some("cover123".into())).await.unwrap(); + } + { + let meta = track.read().await; + + assert_eq!(meta.get_title().await.unwrap(), Some("Title".into())); assert_eq!(meta.get_artist().await.unwrap(), Some("Artist".into())); assert_eq!(meta.get_album().await.unwrap(), Some("Album".into())); @@ -311,4 +318,5 @@ mod tests { assert_eq!(meta.get_cover_pk().await.unwrap(), Some("cover123".into())); assert!(meta.get_updated_at().await.unwrap().is_some()); } + } }