feat: Add download_block example for Radio Paradise
Add a new example that demonstrates downloading a complete Radio Paradise
block and saving each track as a separate FLAC file.
The example:
- Takes a channel ID as argument (0-3)
- Fetches current block metadata
- Creates an output directory ./rp_channel_{id}block{blockid}
- Uses RadioParadiseStreamSource to stream and decode the block
- Uses FlacFileSink to automatically detect TrackBoundary markers
- Saves each track as a separate FLAC file with metadata
Example usage:
cargo run --example download_block --features=pmoaudio -- 0
This demonstrates the full pipeline integration between pmoparadise
and pmoaudio, showing how RadioParadiseStreamSource and FlacFileSink
work together to handle multi-track FLAC blocks seamlessly.
This commit is contained in:
205
pmoparadise/examples/download_block.rs
Normal file
205
pmoparadise/examples/download_block.rs
Normal file
@@ -0,0 +1,205 @@
|
||||
//! Télécharge un bloc complet de Radio Paradise et sauvegarde toutes les pistes en FLAC
|
||||
//!
|
||||
//! Ce programme démontre l'utilisation de la chaîne :
|
||||
//! 1. RadioParadiseStreamSource - Télécharge et décode un bloc FLAC de Radio Paradise
|
||||
//! 2. FlacFileSink - Sauvegarde automatiquement chaque piste dans un fichier FLAC séparé
|
||||
//!
|
||||
//! La nouvelle architecture AudioPipelineNode permet de :
|
||||
//! - Télécharger et décoder automatiquement les blocs FLAC de Radio Paradise
|
||||
//! - Détecter les limites de pistes (TrackBoundary)
|
||||
//! - Sauvegarder automatiquement chaque piste dans un fichier séparé
|
||||
//! - Gérer proprement l'arrêt du pipeline avec un CancellationToken
|
||||
//!
|
||||
//! Usage:
|
||||
//! cargo run --example download_block -- <channel_id>
|
||||
//!
|
||||
//! Exemple:
|
||||
//! cargo run --example download_block -- 0 # Main Mix
|
||||
//! cargo run --example download_block -- 1 # Mellow Mix
|
||||
//! cargo run --example download_block -- 2 # Rock Mix
|
||||
//! cargo run --example download_block -- 3 # World/Etc Mix
|
||||
|
||||
use pmoaudio::{AudioPipelineNode, FlacFileSink};
|
||||
use pmoparadise::{RadioParadiseClient, RadioParadiseStreamSource};
|
||||
use std::env;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialiser tracing pour le debug
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::from_default_env()
|
||||
.add_directive(tracing::Level::INFO.into()),
|
||||
)
|
||||
.init();
|
||||
|
||||
// Récupérer les arguments
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() != 2 {
|
||||
eprintln!("Usage: {} <channel_id>", args[0]);
|
||||
eprintln!();
|
||||
eprintln!("Downloads a complete Radio Paradise block and saves all tracks as FLAC files.");
|
||||
eprintln!();
|
||||
eprintln!("Channel IDs:");
|
||||
eprintln!(" 0 - Main Mix (eclectic, diverse mix)");
|
||||
eprintln!(" 1 - Mellow Mix (smooth, chilled music)");
|
||||
eprintln!(" 2 - Rock Mix (classic & modern rock)");
|
||||
eprintln!(" 3 - World/Etc Mix (global sounds)");
|
||||
eprintln!();
|
||||
eprintln!("Example:");
|
||||
eprintln!(" {} 0 # Download Main Mix", args[0]);
|
||||
eprintln!(" {} 2 # Download Rock Mix", args[0]);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let channel_id: u8 = match args[1].parse() {
|
||||
Ok(id) => id,
|
||||
Err(_) => {
|
||||
eprintln!("Error: channel_id must be a number between 0 and 3");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
if channel_id > 3 {
|
||||
eprintln!("Error: channel_id must be between 0 and 3");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
println!("=== Radio Paradise Block Downloader ===");
|
||||
println!();
|
||||
println!("Channel ID: {}", channel_id);
|
||||
println!();
|
||||
|
||||
// Créer le client Radio Paradise pour le channel spécifié
|
||||
println!("Fetching current block metadata...");
|
||||
let client = RadioParadiseClient::builder()
|
||||
.channel(channel_id)
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
// Récupérer le bloc actuel
|
||||
let block = client.get_block(None).await?;
|
||||
|
||||
println!("Block Information:");
|
||||
println!(" Event ID: {}", block.event);
|
||||
println!(" Songs: {}", block.song_count());
|
||||
println!(
|
||||
" Duration: {:.1} minutes",
|
||||
block.length as f64 / 60000.0
|
||||
);
|
||||
println!();
|
||||
|
||||
// Afficher la liste des pistes
|
||||
println!("Tracklist:");
|
||||
for (index, song) in block.songs_ordered() {
|
||||
println!(
|
||||
" {:2}. {} - {} ({})",
|
||||
index + 1,
|
||||
song.artist,
|
||||
song.title,
|
||||
song.album.as_deref().unwrap_or("Unknown Album")
|
||||
);
|
||||
}
|
||||
println!();
|
||||
|
||||
// Créer le répertoire de sortie
|
||||
let output_dir = format!("./rp_channel_{}block{}", channel_id, block.event);
|
||||
std::fs::create_dir_all(&output_dir)?;
|
||||
println!("Output directory: {}", output_dir);
|
||||
println!();
|
||||
|
||||
// Créer le pipeline: RadioParadiseStreamSource → FlacFileSink
|
||||
let mut source = RadioParadiseStreamSource::new(client);
|
||||
|
||||
// Ajouter le bloc à télécharger
|
||||
source.push_block_id(block.event);
|
||||
|
||||
// Créer le sink qui sauvegarde chaque piste dans un fichier séparé
|
||||
let base_path = format!("{}/track.flac", output_dir);
|
||||
let sink = FlacFileSink::new(&base_path);
|
||||
|
||||
// Construire la chaîne: source → sink
|
||||
source.register(Box::new(sink));
|
||||
|
||||
// Créer un token d'arrêt
|
||||
let stop_token = CancellationToken::new();
|
||||
|
||||
// Gérer Ctrl+C pour arrêt propre
|
||||
let stop_token_clone = stop_token.clone();
|
||||
tokio::spawn(async move {
|
||||
tokio::signal::ctrl_c().await.ok();
|
||||
println!("\n\nReceived Ctrl+C, stopping...");
|
||||
stop_token_clone.cancel();
|
||||
});
|
||||
|
||||
// Lancer tout le pipeline
|
||||
println!("Downloading and processing block...");
|
||||
println!("Press Ctrl+C to stop.");
|
||||
println!();
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let result = Box::new(source).run(stop_token).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
// Vérifier le résultat
|
||||
match result {
|
||||
Ok(()) => {
|
||||
println!();
|
||||
println!(
|
||||
"✓ Download completed successfully in {:.2}s",
|
||||
elapsed.as_secs_f64()
|
||||
);
|
||||
println!(" Output directory: {}", output_dir);
|
||||
println!();
|
||||
|
||||
// Afficher les fichiers créés
|
||||
let entries = std::fs::read_dir(&output_dir)?;
|
||||
let mut files: Vec<_> = entries
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| {
|
||||
e.path()
|
||||
.extension()
|
||||
.and_then(|s| s.to_str())
|
||||
.map(|s| s == "flac")
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.collect();
|
||||
files.sort_by_key(|e| e.path());
|
||||
|
||||
println!("Files created:");
|
||||
for (i, entry) in files.iter().enumerate() {
|
||||
let path = entry.path();
|
||||
let metadata = std::fs::metadata(&path)?;
|
||||
let size_mb = metadata.len() as f64 / (1024.0 * 1024.0);
|
||||
println!(
|
||||
" {:2}. {} ({:.2} MB)",
|
||||
i + 1,
|
||||
path.file_name().unwrap().to_string_lossy(),
|
||||
size_mb
|
||||
);
|
||||
}
|
||||
println!();
|
||||
|
||||
// Calculer la taille totale
|
||||
let total_size: u64 = files
|
||||
.iter()
|
||||
.filter_map(|e| std::fs::metadata(e.path()).ok())
|
||||
.map(|m| m.len())
|
||||
.sum();
|
||||
println!(
|
||||
"Total size: {:.2} MB",
|
||||
total_size as f64 / (1024.0 * 1024.0)
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!();
|
||||
eprintln!("✗ Download error: {}", e);
|
||||
eprintln!();
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user