From 6cceb3cbfacb6f4416e49321a9b63b4ce7a9f036 Mon Sep 17 00:00:00 2001 From: salmonik Date: Mon, 18 May 2026 21:46:26 +0200 Subject: fix for new symphony API and less debug info in release --- src/audio.rs | 97 +++++++++++++++++++++++++++++++------------------------- src/daemon.rs | 39 +++++++++++++++-------- src/lib.rs | 7 ++++ src/main.rs | 1 + src/queue.rs | 64 ++++++++++++++++++------------------- src/scrobbler.rs | 72 +++++++++++++++++++++++++---------------- 6 files changed, 163 insertions(+), 117 deletions(-) (limited to 'src') diff --git a/src/audio.rs b/src/audio.rs index 62afd93..62d13ff 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -101,7 +101,7 @@ impl AudioEngine { struct AudioData { format: Box, - decoder: Box, + decoder: Box, sample_rate: u32, channels: u16, sample_buffer: Vec, @@ -165,10 +165,10 @@ fn audio_thread( let device_sample_rate = config.sample_rate(); let channels = config.channels(); - eprintln!( + crate::debug_eprintln(format_args!( "Default output: {} Hz, {} channels", device_sample_rate, channels - ); + )); let mut current_stream: Option = None; let audio_data: Arc>> = Arc::new(Mutex::new(None)); @@ -186,13 +186,16 @@ fn audio_thread( match open_audio_file(&path) { Ok(mut data) => { - eprintln!("File: {} Hz, {} channels", data.sample_rate, data.channels); + crate::debug_eprintln(format_args!( + "File: {} Hz, {} channels", + data.sample_rate, data.channels + )); if data.sample_rate != device_sample_rate { - eprintln!( + crate::debug_eprintln(format_args!( "Resampling from {} Hz to {} Hz", data.sample_rate, device_sample_rate - ); + )); data.resample_ratio = Some(device_sample_rate as f64 / data.sample_rate as f64); } @@ -201,28 +204,23 @@ fn audio_thread( let channels = data.channels as usize; for _ in 0..100 { match data.format.next_packet() { - Ok(packet) => match data.decoder.decode(&packet) { + Ok(Some(packet)) => match data.decoder.decode(&packet) { Ok(decoded) => { - let spec = *decoded.spec(); - let mut sample_buf = symphonia::core::audio::SampleBuffer::< - f32, - >::new( - decoded.capacity() as u64, spec - ); - sample_buf.copy_interleaved_ref(decoded); - let samples = sample_buf.samples(); + let num_samples = decoded.samples_interleaved(); + let mut samples = vec![0.0f32; num_samples]; + decoded.copy_to_slice_interleaved(&mut samples); let resampled = if let Some(ratio) = data.resample_ratio { - resample_linear(samples, ratio, channels) + resample_linear(&samples, ratio, channels) } else { - samples.to_vec() + samples }; data.sample_buffer.extend(resampled); } Err(_) => break, }, - Err(_) => break, + _ => break, } } data.buffer_pos = 0; @@ -274,29 +272,26 @@ fn audio_thread( // Decode more data match state.format.next_packet() { - Ok(packet) => { + Ok(Some(packet)) => { match state.decoder.decode(&packet) { Ok(decoded) => { - let spec = *decoded.spec(); - let mut sample_buf = - symphonia::core::audio::SampleBuffer::< - f32, - >::new( - decoded.capacity() as u64, spec - ); - sample_buf - .copy_interleaved_ref(decoded); - let samples = sample_buf.samples(); + let num_samples = + decoded.samples_interleaved(); + let mut samples = + vec![0.0f32; num_samples]; + decoded.copy_to_slice_interleaved( + &mut samples, + ); // Resample if needed let resampled = if let Some(ratio) = state.resample_ratio { resample_linear( - samples, ratio, channels, + &samples, ratio, channels, ) } else { - samples.to_vec() + samples }; // Copy what we can to output @@ -323,7 +318,7 @@ fn audio_thread( Err(_) => break, } } - Err(_) => break, + _ => break, } } @@ -419,36 +414,50 @@ fn audio_thread( } fn open_audio_file(path: &PathBuf) -> Result> { - use symphonia::core::audio::Channels; + use symphonia::core::audio::layouts::CHANNEL_LAYOUT_STEREO; + use symphonia::core::codecs::audio::AudioDecoderOptions; + use symphonia::core::formats::probe::Hint; use symphonia::core::io::MediaSourceStream; let file = std::fs::File::open(path)?; let mss = MediaSourceStream::new(Box::new(file), Default::default()); - let hint = symphonia::core::probe::Hint::new(); - let probed = symphonia::default::get_probe().format( + let hint = Hint::new(); + let format = symphonia::default::get_probe().probe( &hint, mss, - &Default::default(), - &Default::default(), + Default::default(), + Default::default(), )?; - let format = probed.format; let track = format .tracks() .iter() - .find(|t| t.codec_params.sample_rate.is_some()) + .find(|t| { + t.codec_params + .as_ref() + .and_then(|p| p.audio()) + .and_then(|a| a.sample_rate) + .is_some() + }) .ok_or("No suitable track found")?; - let sample_rate = track.codec_params.sample_rate.unwrap_or(44100); - let channels = track + let codec_params = track .codec_params + .as_ref() + .ok_or("No codec parameters")? + .audio() + .ok_or("Not an audio track")?; + + let sample_rate = codec_params.sample_rate.unwrap_or(44100); + let channels = codec_params .channels - .unwrap_or(Channels::FRONT_LEFT | Channels::FRONT_RIGHT) + .clone() + .unwrap_or(CHANNEL_LAYOUT_STEREO) .count() as u16; - let decoder = - symphonia::default::get_codecs().make(&track.codec_params, &Default::default())?; + let decoder = symphonia::default::get_codecs() + .make_audio_decoder(codec_params, &AudioDecoderOptions::default())?; Ok(AudioData { format, diff --git a/src/daemon.rs b/src/daemon.rs index 59fc3d8..8b74357 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -33,10 +33,10 @@ impl ScrobbleState { .as_secs(); let title = track.title.clone().unwrap_or_default(); let artist = track.artist.clone().unwrap_or_default(); - eprintln!( + crate::debug_eprintln(format_args!( "scrobble: tracking start for '{} - {}' (ts={})", artist, title, ts - ); + )); self.track = Some(track); self.start_time = Some(Instant::now()); self.timestamp = Some(ts); @@ -44,7 +44,9 @@ impl ScrobbleState { fn should_scrobble(&self) -> bool { let (Some(track), Some(start)) = (&self.track, self.start_time) else { - eprintln!("scrobble: should_scrobble false — no track or start_time"); + crate::debug_eprintln(format_args!( + "scrobble: should_scrobble false — no track or start_time" + )); return false; }; let elapsed = start.elapsed().as_secs(); @@ -54,10 +56,10 @@ impl ScrobbleState { .map(|d| (d / 2.0) as u64) .unwrap_or(u64::MAX) .min(240); - eprintln!( + crate::debug_eprintln(format_args!( "scrobble: should_scrobble {} (elapsed={}s threshold={}s duration={:?})", result, elapsed, threshold, track.duration_secs - ); + )); result } @@ -158,10 +160,10 @@ fn try_scrobble_current(scrobbler: &RwLock, state: &mut ScrobbleState let artist = track.artist.as_deref().unwrap_or("(unknown artist)"); let title = track.title.as_deref().unwrap_or("(unknown title)"); let album = track.album.as_deref(); - eprintln!( + crate::debug_eprintln(format_args!( "scrobble: sending scrobble for '{} - {}' at ts={}", artist, title, ts - ); + )); if let Ok(s) = scrobbler.read() { s.scrobble(artist, title, album, ts); } @@ -171,10 +173,10 @@ fn try_scrobble_current(scrobbler: &RwLock, state: &mut ScrobbleState .as_ref() .and_then(|t| t.title.as_deref()) .unwrap_or("(no track)"); - eprintln!( + crate::debug_eprintln(format_args!( "scrobble: skipping scrobble for '{}' — not enough play time", title - ); + )); } state.clear(); } @@ -183,7 +185,10 @@ fn send_now_playing(scrobbler: &RwLock, track: &Track) { let artist = track.artist.as_deref().unwrap_or("(unknown artist)"); let title = track.title.as_deref().unwrap_or("(unknown title)"); let album = track.album.as_deref(); - eprintln!("scrobble: sending now_playing for '{} - {}'", artist, title); + crate::debug_eprintln(format_args!( + "scrobble: sending now_playing for '{} - {}'", + artist, title + )); if let Ok(s) = scrobbler.read() { s.now_playing(artist, title, album); } @@ -260,9 +265,17 @@ pub fn start_daemon() -> Result<(), Box> { && let Ok(s) = scrobbler.read() { match s.validate_listenbrainz_token() { - Ok(Some(user)) => eprintln!("scrobble: listenbrainz token valid (user: {})", user), - Ok(None) => eprintln!("scrobble: listenbrainz token invalid, scrobbling disabled"), - Err(e) => eprintln!("scrobble: listenbrainz validation error: {}", e), + Ok(Some(user)) => crate::debug_eprintln(format_args!( + "scrobble: listenbrainz token valid (user: {})", + user + )), + Ok(None) => crate::debug_eprintln(format_args!( + "scrobble: listenbrainz token invalid, scrobbling disabled" + )), + Err(e) => crate::debug_eprintln(format_args!( + "scrobble: listenbrainz validation error: {}", + e + )), } } let scrobble_state = Arc::new(Mutex::new(ScrobbleState::new())); diff --git a/src/lib.rs b/src/lib.rs index 825872f..4cdbb9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,10 @@ +#[inline(always)] +pub fn debug_eprintln(args: core::fmt::Arguments<'_>) { + if cfg!(debug_assertions) { + eprintln!("{}", args); + } +} + pub mod audio; pub mod cli; pub mod config; diff --git a/src/main.rs b/src/main.rs index c1e42d0..fe450dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ mod scrobbler; use clap::Parser; use cli::Cli; +pub use rsp::debug_eprintln; fn main() { let cli = Cli::parse(); diff --git a/src/queue.rs b/src/queue.rs index 26aeb8b..b0acf5f 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -17,28 +17,27 @@ pub struct AudioMetadata { } pub fn extract_metadata(path: &Path) -> Option { + use symphonia::core::formats::probe::Hint; use symphonia::core::io::MediaSourceStream; - use symphonia::core::meta::StandardTagKey; - use symphonia::core::probe::Hint; + use symphonia::core::meta::StandardTag; let file = std::fs::File::open(path).ok()?; let mss = MediaSourceStream::new(Box::new(file), Default::default()); let hint = Hint::new(); let mut probed = symphonia::default::get_probe() - .format(&hint, mss, &Default::default(), &Default::default()) + .probe(&hint, mss, Default::default(), Default::default()) .ok()?; - // Extract duration from format track's codec parameters + // Extract duration from track's num_frames and time_base let duration_secs = probed - .format .tracks() .iter() - .find(|t| t.codec_params.n_frames.is_some() && t.codec_params.time_base.is_some()) + .find(|t| t.num_frames.is_some() && t.time_base.is_some()) .and_then(|t| { - let n_frames = t.codec_params.n_frames?; - let tb = t.codec_params.time_base?; - Some(n_frames as f64 * tb.numer as f64 / tb.denom as f64) + let n_frames = t.num_frames?; + let tb = t.time_base?; + Some(n_frames as f64 * tb.numer.get() as f64 / tb.denom.get() as f64) }); let mut metadata = AudioMetadata { @@ -46,38 +45,39 @@ pub fn extract_metadata(path: &Path) -> Option { ..Default::default() }; - if let Some(meta) = probed.metadata.get() - && let Some(rev) = meta.current() { - for tag in rev.tags() { - match tag.std_key { - Some(StandardTagKey::TrackTitle) => { - metadata.title = Some(tag.value.to_string()); - } - Some(StandardTagKey::Artist) => { - metadata.artist = Some(tag.value.to_string()); - } - Some(StandardTagKey::Album) => { - metadata.album = Some(tag.value.to_string()); + let meta = probed.metadata(); + if let Some(rev) = meta.current() { + for tag in &rev.media.tags { + match &tag.std { + Some(StandardTag::TrackTitle(value)) => { + metadata.title = Some(value.to_string()); + } + Some(StandardTag::Artist(value)) => { + metadata.artist = Some(value.to_string()); + } + Some(StandardTag::Album(value)) => { + metadata.album = Some(value.to_string()); + } + _ => {} } - _ => {} } } } if metadata.title.is_none() && metadata.artist.is_none() && metadata.album.is_none() { - let mut format = probed.format; - if let Some(rev) = format.metadata().current() { - for tag in rev.tags() { - match tag.std_key { - Some(StandardTagKey::TrackTitle) => { - metadata.title = Some(tag.value.to_string()); + let meta = probed.metadata(); + if let Some(rev) = meta.current() { + for tag in &rev.media.tags { + match &tag.std { + Some(StandardTag::TrackTitle(value)) => { + metadata.title = Some(value.to_string()); } - Some(StandardTagKey::Artist) => { - metadata.artist = Some(tag.value.to_string()); + Some(StandardTag::Artist(value)) => { + metadata.artist = Some(value.to_string()); } - Some(StandardTagKey::Album) => { - metadata.album = Some(tag.value.to_string()); + Some(StandardTag::Album(value)) => { + metadata.album = Some(value.to_string()); } _ => {} } diff --git a/src/scrobbler.rs b/src/scrobbler.rs index 2e79933..93cb3c2 100644 --- a/src/scrobbler.rs +++ b/src/scrobbler.rs @@ -47,10 +47,10 @@ impl Scrobbler { .send()?; if !resp.status().is_success() { - eprintln!( + crate::debug_eprintln(format_args!( "scrobble: listenbrainz token validation failed: {}", resp.status() - ); + )); return Ok(None); } @@ -62,13 +62,18 @@ impl Scrobbler { .and_then(|v| v.as_str()) .map(|s| s.to_string()); if let Some(ref u) = user { - eprintln!("scrobble: listenbrainz token valid (user: {})", u); + crate::debug_eprintln(format_args!( + "scrobble: listenbrainz token valid (user: {})", + u + )); } else { - eprintln!("scrobble: listenbrainz token valid"); + crate::debug_eprintln(format_args!("scrobble: listenbrainz token valid")); } Ok(user) } else { - eprintln!("scrobble: listenbrainz token invalid — scrobbling will fail"); + crate::debug_eprintln(format_args!( + "scrobble: listenbrainz token invalid — scrobbling will fail" + )); Ok(None) } } @@ -82,22 +87,29 @@ impl Scrobbler { && !secret.is_empty() && !session_key.is_empty() { - eprintln!("scrobble: now_playing -> lastfm '{} - {}'", artist, track); + crate::debug_eprintln(format_args!( + "scrobble: now_playing -> lastfm '{} - {}'", + artist, track + )); let _ = self.send_lastfm_now_playing(api_key, secret, session_key, artist, track, album); } else { - eprintln!("scrobble: now_playing -> lastfm skipped (no credentials)"); + crate::debug_eprintln(format_args!( + "scrobble: now_playing -> lastfm skipped (no credentials)" + )); } if let Some(token) = &self.listenbrainz_token && !token.is_empty() { - eprintln!( + crate::debug_eprintln(format_args!( "scrobble: now_playing -> listenbrainz '{} - {}'", artist, track - ); + )); let _ = self.send_listenbrainz_now_playing(token, artist, track, album); } else { - eprintln!("scrobble: now_playing -> listenbrainz skipped (no token)"); + crate::debug_eprintln(format_args!( + "scrobble: now_playing -> listenbrainz skipped (no token)" + )); } } @@ -110,10 +122,10 @@ impl Scrobbler { && !secret.is_empty() && !session_key.is_empty() { - eprintln!( + crate::debug_eprintln(format_args!( "scrobble: scrobble -> lastfm '{} - {}' (ts={})", artist, track, timestamp - ); + )); let _ = self.send_lastfm_scrobble( api_key, secret, @@ -124,18 +136,22 @@ impl Scrobbler { timestamp, ); } else { - eprintln!("scrobble: scrobble -> lastfm skipped (no credentials)"); + crate::debug_eprintln(format_args!( + "scrobble: scrobble -> lastfm skipped (no credentials)" + )); } if let Some(token) = &self.listenbrainz_token && !token.is_empty() { - eprintln!( + crate::debug_eprintln(format_args!( "scrobble: scrobble -> listenbrainz '{} - {}' (ts={})", artist, track, timestamp - ); + )); let _ = self.send_listenbrainz_scrobble(token, artist, track, album, timestamp); } else { - eprintln!("scrobble: scrobble -> listenbrainz skipped (no token)"); + crate::debug_eprintln(format_args!( + "scrobble: scrobble -> listenbrainz skipped (no token)" + )); } } @@ -177,13 +193,13 @@ impl Scrobbler { let resp = self.client.post(LASTFM_API_URL).form(¶ms).send()?; if resp.status().is_success() { - eprintln!("scrobble: lastfm now_playing ok"); + crate::debug_eprintln(format_args!("scrobble: lastfm now_playing ok")); } else { - eprintln!( + crate::debug_eprintln(format_args!( "scrobble: lastfm now_playing error: {} {}", resp.status(), resp.text().unwrap_or_default() - ); + )); } Ok(()) } @@ -217,13 +233,13 @@ impl Scrobbler { let resp = self.client.post(LASTFM_API_URL).form(¶ms).send()?; if resp.status().is_success() { - eprintln!("scrobble: lastfm scrobble ok"); + crate::debug_eprintln(format_args!("scrobble: lastfm scrobble ok")); } else { - eprintln!( + crate::debug_eprintln(format_args!( "scrobble: lastfm scrobble error: {} {}", resp.status(), resp.text().unwrap_or_default() - ); + )); } Ok(()) } @@ -257,13 +273,13 @@ impl Scrobbler { .json(&payload) .send()?; if resp.status().is_success() { - eprintln!("scrobble: listenbrainz now_playing ok"); + crate::debug_eprintln(format_args!("scrobble: listenbrainz now_playing ok")); } else { - eprintln!( + crate::debug_eprintln(format_args!( "scrobble: listenbrainz now_playing error: {} {}", resp.status(), resp.text().unwrap_or_default() - ); + )); } Ok(()) } @@ -299,13 +315,13 @@ impl Scrobbler { .json(&payload) .send()?; if resp.status().is_success() { - eprintln!("scrobble: listenbrainz scrobble ok"); + crate::debug_eprintln(format_args!("scrobble: listenbrainz scrobble ok")); } else { - eprintln!( + crate::debug_eprintln(format_args!( "scrobble: listenbrainz scrobble error: {} {}", resp.status(), resp.text().unwrap_or_default() - ); + )); } Ok(()) } -- cgit v1.2.3