diff options
| author | salmonik <raisersoft@proton.me> | 2026-05-18 21:46:26 +0200 |
|---|---|---|
| committer | salmonik <raisersoft@proton.me> | 2026-05-18 21:46:26 +0200 |
| commit | 6cceb3cbfacb6f4416e49321a9b63b4ce7a9f036 (patch) | |
| tree | 9d883ada0e6cfb4fd533635885dfb4459f200128 /src | |
| parent | first public release. (diff) | |
| download | rsplayer-6cceb3cbfacb6f4416e49321a9b63b4ce7a9f036.tar.gz rsplayer-6cceb3cbfacb6f4416e49321a9b63b4ce7a9f036.zip | |
fix for new symphony API and less debug info in release
Diffstat (limited to '')
| -rw-r--r-- | src/audio.rs | 97 | ||||
| -rw-r--r-- | src/daemon.rs | 39 | ||||
| -rw-r--r-- | src/lib.rs | 7 | ||||
| -rw-r--r-- | src/main.rs | 1 | ||||
| -rw-r--r-- | src/queue.rs | 64 | ||||
| -rw-r--r-- | src/scrobbler.rs | 72 |
6 files changed, 163 insertions, 117 deletions
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 { | |||
| 101 | 101 | ||
| 102 | struct AudioData { | 102 | struct AudioData { |
| 103 | format: Box<dyn symphonia::core::formats::FormatReader>, | 103 | format: Box<dyn symphonia::core::formats::FormatReader>, |
| 104 | decoder: Box<dyn symphonia::core::codecs::Decoder>, | 104 | decoder: Box<dyn symphonia::core::codecs::audio::AudioDecoder>, |
| 105 | sample_rate: u32, | 105 | sample_rate: u32, |
| 106 | channels: u16, | 106 | channels: u16, |
| 107 | sample_buffer: Vec<f32>, | 107 | sample_buffer: Vec<f32>, |
| @@ -165,10 +165,10 @@ fn audio_thread( | |||
| 165 | 165 | ||
| 166 | let device_sample_rate = config.sample_rate(); | 166 | let device_sample_rate = config.sample_rate(); |
| 167 | let channels = config.channels(); | 167 | let channels = config.channels(); |
| 168 | eprintln!( | 168 | crate::debug_eprintln(format_args!( |
| 169 | "Default output: {} Hz, {} channels", | 169 | "Default output: {} Hz, {} channels", |
| 170 | device_sample_rate, channels | 170 | device_sample_rate, channels |
| 171 | ); | 171 | )); |
| 172 | 172 | ||
| 173 | let mut current_stream: Option<cpal::Stream> = None; | 173 | let mut current_stream: Option<cpal::Stream> = None; |
| 174 | let audio_data: Arc<Mutex<Option<AudioData>>> = Arc::new(Mutex::new(None)); | 174 | let audio_data: Arc<Mutex<Option<AudioData>>> = Arc::new(Mutex::new(None)); |
| @@ -186,13 +186,16 @@ fn audio_thread( | |||
| 186 | 186 | ||
| 187 | match open_audio_file(&path) { | 187 | match open_audio_file(&path) { |
| 188 | Ok(mut data) => { | 188 | Ok(mut data) => { |
| 189 | eprintln!("File: {} Hz, {} channels", data.sample_rate, data.channels); | 189 | crate::debug_eprintln(format_args!( |
| 190 | "File: {} Hz, {} channels", | ||
| 191 | data.sample_rate, data.channels | ||
| 192 | )); | ||
| 190 | 193 | ||
| 191 | if data.sample_rate != device_sample_rate { | 194 | if data.sample_rate != device_sample_rate { |
| 192 | eprintln!( | 195 | crate::debug_eprintln(format_args!( |
| 193 | "Resampling from {} Hz to {} Hz", | 196 | "Resampling from {} Hz to {} Hz", |
| 194 | data.sample_rate, device_sample_rate | 197 | data.sample_rate, device_sample_rate |
| 195 | ); | 198 | )); |
| 196 | data.resample_ratio = | 199 | data.resample_ratio = |
| 197 | Some(device_sample_rate as f64 / data.sample_rate as f64); | 200 | Some(device_sample_rate as f64 / data.sample_rate as f64); |
| 198 | } | 201 | } |
| @@ -201,28 +204,23 @@ fn audio_thread( | |||
| 201 | let channels = data.channels as usize; | 204 | let channels = data.channels as usize; |
| 202 | for _ in 0..100 { | 205 | for _ in 0..100 { |
| 203 | match data.format.next_packet() { | 206 | match data.format.next_packet() { |
| 204 | Ok(packet) => match data.decoder.decode(&packet) { | 207 | Ok(Some(packet)) => match data.decoder.decode(&packet) { |
| 205 | Ok(decoded) => { | 208 | Ok(decoded) => { |
| 206 | let spec = *decoded.spec(); | 209 | let num_samples = decoded.samples_interleaved(); |
| 207 | let mut sample_buf = symphonia::core::audio::SampleBuffer::< | 210 | let mut samples = vec![0.0f32; num_samples]; |
| 208 | f32, | 211 | decoded.copy_to_slice_interleaved(&mut samples); |
| 209 | >::new( | ||
| 210 | decoded.capacity() as u64, spec | ||
| 211 | ); | ||
| 212 | sample_buf.copy_interleaved_ref(decoded); | ||
| 213 | let samples = sample_buf.samples(); | ||
| 214 | 212 | ||
| 215 | let resampled = if let Some(ratio) = data.resample_ratio { | 213 | let resampled = if let Some(ratio) = data.resample_ratio { |
| 216 | resample_linear(samples, ratio, channels) | 214 | resample_linear(&samples, ratio, channels) |
| 217 | } else { | 215 | } else { |
| 218 | samples.to_vec() | 216 | samples |
| 219 | }; | 217 | }; |
| 220 | 218 | ||
| 221 | data.sample_buffer.extend(resampled); | 219 | data.sample_buffer.extend(resampled); |
| 222 | } | 220 | } |
| 223 | Err(_) => break, | 221 | Err(_) => break, |
| 224 | }, | 222 | }, |
| 225 | Err(_) => break, | 223 | _ => break, |
| 226 | } | 224 | } |
| 227 | } | 225 | } |
| 228 | data.buffer_pos = 0; | 226 | data.buffer_pos = 0; |
| @@ -274,29 +272,26 @@ fn audio_thread( | |||
| 274 | 272 | ||
| 275 | // Decode more data | 273 | // Decode more data |
| 276 | match state.format.next_packet() { | 274 | match state.format.next_packet() { |
| 277 | Ok(packet) => { | 275 | Ok(Some(packet)) => { |
| 278 | match state.decoder.decode(&packet) { | 276 | match state.decoder.decode(&packet) { |
| 279 | Ok(decoded) => { | 277 | Ok(decoded) => { |
| 280 | let spec = *decoded.spec(); | 278 | let num_samples = |
| 281 | let mut sample_buf = | 279 | decoded.samples_interleaved(); |
| 282 | symphonia::core::audio::SampleBuffer::< | 280 | let mut samples = |
| 283 | f32, | 281 | vec![0.0f32; num_samples]; |
| 284 | >::new( | 282 | decoded.copy_to_slice_interleaved( |
| 285 | decoded.capacity() as u64, spec | 283 | &mut samples, |
| 286 | ); | 284 | ); |
| 287 | sample_buf | ||
| 288 | .copy_interleaved_ref(decoded); | ||
| 289 | let samples = sample_buf.samples(); | ||
| 290 | 285 | ||
| 291 | // Resample if needed | 286 | // Resample if needed |
| 292 | let resampled = if let Some(ratio) = | 287 | let resampled = if let Some(ratio) = |
| 293 | state.resample_ratio | 288 | state.resample_ratio |
| 294 | { | 289 | { |
| 295 | resample_linear( | 290 | resample_linear( |
| 296 | samples, ratio, channels, | 291 | &samples, ratio, channels, |
| 297 | ) | 292 | ) |
| 298 | } else { | 293 | } else { |
| 299 | samples.to_vec() | 294 | samples |
| 300 | }; | 295 | }; |
| 301 | 296 | ||
| 302 | // Copy what we can to output | 297 | // Copy what we can to output |
| @@ -323,7 +318,7 @@ fn audio_thread( | |||
| 323 | Err(_) => break, | 318 | Err(_) => break, |
| 324 | } | 319 | } |
| 325 | } | 320 | } |
| 326 | Err(_) => break, | 321 | _ => break, |
| 327 | } | 322 | } |
| 328 | } | 323 | } |
| 329 | 324 | ||
| @@ -419,36 +414,50 @@ fn audio_thread( | |||
| 419 | } | 414 | } |
| 420 | 415 | ||
| 421 | fn open_audio_file(path: &PathBuf) -> Result<AudioData, Box<dyn std::error::Error>> { | 416 | fn open_audio_file(path: &PathBuf) -> Result<AudioData, Box<dyn std::error::Error>> { |
| 422 | use symphonia::core::audio::Channels; | 417 | use symphonia::core::audio::layouts::CHANNEL_LAYOUT_STEREO; |
| 418 | use symphonia::core::codecs::audio::AudioDecoderOptions; | ||
| 419 | use symphonia::core::formats::probe::Hint; | ||
| 423 | use symphonia::core::io::MediaSourceStream; | 420 | use symphonia::core::io::MediaSourceStream; |
| 424 | 421 | ||
| 425 | let file = std::fs::File::open(path)?; | 422 | let file = std::fs::File::open(path)?; |
| 426 | let mss = MediaSourceStream::new(Box::new(file), Default::default()); | 423 | let mss = MediaSourceStream::new(Box::new(file), Default::default()); |
| 427 | 424 | ||
| 428 | let hint = symphonia::core::probe::Hint::new(); | 425 | let hint = Hint::new(); |
| 429 | let probed = symphonia::default::get_probe().format( | 426 | let format = symphonia::default::get_probe().probe( |
| 430 | &hint, | 427 | &hint, |
| 431 | mss, | 428 | mss, |
| 432 | &Default::default(), | 429 | Default::default(), |
| 433 | &Default::default(), | 430 | Default::default(), |
| 434 | )?; | 431 | )?; |
| 435 | 432 | ||
| 436 | let format = probed.format; | ||
| 437 | let track = format | 433 | let track = format |
| 438 | .tracks() | 434 | .tracks() |
| 439 | .iter() | 435 | .iter() |
| 440 | .find(|t| t.codec_params.sample_rate.is_some()) | 436 | .find(|t| { |
| 437 | t.codec_params | ||
| 438 | .as_ref() | ||
| 439 | .and_then(|p| p.audio()) | ||
| 440 | .and_then(|a| a.sample_rate) | ||
| 441 | .is_some() | ||
| 442 | }) | ||
| 441 | .ok_or("No suitable track found")?; | 443 | .ok_or("No suitable track found")?; |
| 442 | 444 | ||
| 443 | let sample_rate = track.codec_params.sample_rate.unwrap_or(44100); | 445 | let codec_params = track |
| 444 | let channels = track | ||
| 445 | .codec_params | 446 | .codec_params |
| 447 | .as_ref() | ||
| 448 | .ok_or("No codec parameters")? | ||
| 449 | .audio() | ||
| 450 | .ok_or("Not an audio track")?; | ||
| 451 | |||
| 452 | let sample_rate = codec_params.sample_rate.unwrap_or(44100); | ||
| 453 | let channels = codec_params | ||
| 446 | .channels | 454 | .channels |
| 447 | .unwrap_or(Channels::FRONT_LEFT | Channels::FRONT_RIGHT) | 455 | .clone() |
| 456 | .unwrap_or(CHANNEL_LAYOUT_STEREO) | ||
| 448 | .count() as u16; | 457 | .count() as u16; |
| 449 | 458 | ||
| 450 | let decoder = | 459 | let decoder = symphonia::default::get_codecs() |
| 451 | symphonia::default::get_codecs().make(&track.codec_params, &Default::default())?; | 460 | .make_audio_decoder(codec_params, &AudioDecoderOptions::default())?; |
| 452 | 461 | ||
| 453 | Ok(AudioData { | 462 | Ok(AudioData { |
| 454 | format, | 463 | 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 { | |||
| 33 | .as_secs(); | 33 | .as_secs(); |
| 34 | let title = track.title.clone().unwrap_or_default(); | 34 | let title = track.title.clone().unwrap_or_default(); |
| 35 | let artist = track.artist.clone().unwrap_or_default(); | 35 | let artist = track.artist.clone().unwrap_or_default(); |
| 36 | eprintln!( | 36 | crate::debug_eprintln(format_args!( |
| 37 | "scrobble: tracking start for '{} - {}' (ts={})", | 37 | "scrobble: tracking start for '{} - {}' (ts={})", |
| 38 | artist, title, ts | 38 | artist, title, ts |
| 39 | ); | 39 | )); |
| 40 | self.track = Some(track); | 40 | self.track = Some(track); |
| 41 | self.start_time = Some(Instant::now()); | 41 | self.start_time = Some(Instant::now()); |
| 42 | self.timestamp = Some(ts); | 42 | self.timestamp = Some(ts); |
| @@ -44,7 +44,9 @@ impl ScrobbleState { | |||
| 44 | 44 | ||
| 45 | fn should_scrobble(&self) -> bool { | 45 | fn should_scrobble(&self) -> bool { |
| 46 | let (Some(track), Some(start)) = (&self.track, self.start_time) else { | 46 | let (Some(track), Some(start)) = (&self.track, self.start_time) else { |
| 47 | eprintln!("scrobble: should_scrobble false — no track or start_time"); | 47 | crate::debug_eprintln(format_args!( |
| 48 | "scrobble: should_scrobble false — no track or start_time" | ||
| 49 | )); | ||
| 48 | return false; | 50 | return false; |
| 49 | }; | 51 | }; |
| 50 | let elapsed = start.elapsed().as_secs(); | 52 | let elapsed = start.elapsed().as_secs(); |
| @@ -54,10 +56,10 @@ impl ScrobbleState { | |||
| 54 | .map(|d| (d / 2.0) as u64) | 56 | .map(|d| (d / 2.0) as u64) |
| 55 | .unwrap_or(u64::MAX) | 57 | .unwrap_or(u64::MAX) |
| 56 | .min(240); | 58 | .min(240); |
| 57 | eprintln!( | 59 | crate::debug_eprintln(format_args!( |
| 58 | "scrobble: should_scrobble {} (elapsed={}s threshold={}s duration={:?})", | 60 | "scrobble: should_scrobble {} (elapsed={}s threshold={}s duration={:?})", |
| 59 | result, elapsed, threshold, track.duration_secs | 61 | result, elapsed, threshold, track.duration_secs |
| 60 | ); | 62 | )); |
| 61 | result | 63 | result |
| 62 | } | 64 | } |
| 63 | 65 | ||
| @@ -158,10 +160,10 @@ fn try_scrobble_current(scrobbler: &RwLock<Scrobbler>, state: &mut ScrobbleState | |||
| 158 | let artist = track.artist.as_deref().unwrap_or("(unknown artist)"); | 160 | let artist = track.artist.as_deref().unwrap_or("(unknown artist)"); |
| 159 | let title = track.title.as_deref().unwrap_or("(unknown title)"); | 161 | let title = track.title.as_deref().unwrap_or("(unknown title)"); |
| 160 | let album = track.album.as_deref(); | 162 | let album = track.album.as_deref(); |
| 161 | eprintln!( | 163 | crate::debug_eprintln(format_args!( |
| 162 | "scrobble: sending scrobble for '{} - {}' at ts={}", | 164 | "scrobble: sending scrobble for '{} - {}' at ts={}", |
| 163 | artist, title, ts | 165 | artist, title, ts |
| 164 | ); | 166 | )); |
| 165 | if let Ok(s) = scrobbler.read() { | 167 | if let Ok(s) = scrobbler.read() { |
| 166 | s.scrobble(artist, title, album, ts); | 168 | s.scrobble(artist, title, album, ts); |
| 167 | } | 169 | } |
| @@ -171,10 +173,10 @@ fn try_scrobble_current(scrobbler: &RwLock<Scrobbler>, state: &mut ScrobbleState | |||
| 171 | .as_ref() | 173 | .as_ref() |
| 172 | .and_then(|t| t.title.as_deref()) | 174 | .and_then(|t| t.title.as_deref()) |
| 173 | .unwrap_or("(no track)"); | 175 | .unwrap_or("(no track)"); |
| 174 | eprintln!( | 176 | crate::debug_eprintln(format_args!( |
| 175 | "scrobble: skipping scrobble for '{}' — not enough play time", | 177 | "scrobble: skipping scrobble for '{}' — not enough play time", |
| 176 | title | 178 | title |
| 177 | ); | 179 | )); |
| 178 | } | 180 | } |
| 179 | state.clear(); | 181 | state.clear(); |
| 180 | } | 182 | } |
| @@ -183,7 +185,10 @@ fn send_now_playing(scrobbler: &RwLock<Scrobbler>, track: &Track) { | |||
| 183 | let artist = track.artist.as_deref().unwrap_or("(unknown artist)"); | 185 | let artist = track.artist.as_deref().unwrap_or("(unknown artist)"); |
| 184 | let title = track.title.as_deref().unwrap_or("(unknown title)"); | 186 | let title = track.title.as_deref().unwrap_or("(unknown title)"); |
| 185 | let album = track.album.as_deref(); | 187 | let album = track.album.as_deref(); |
| 186 | eprintln!("scrobble: sending now_playing for '{} - {}'", artist, title); | 188 | crate::debug_eprintln(format_args!( |
| 189 | "scrobble: sending now_playing for '{} - {}'", | ||
| 190 | artist, title | ||
| 191 | )); | ||
| 187 | if let Ok(s) = scrobbler.read() { | 192 | if let Ok(s) = scrobbler.read() { |
| 188 | s.now_playing(artist, title, album); | 193 | s.now_playing(artist, title, album); |
| 189 | } | 194 | } |
| @@ -260,9 +265,17 @@ pub fn start_daemon() -> Result<(), Box<dyn std::error::Error>> { | |||
| 260 | && let Ok(s) = scrobbler.read() | 265 | && let Ok(s) = scrobbler.read() |
| 261 | { | 266 | { |
| 262 | match s.validate_listenbrainz_token() { | 267 | match s.validate_listenbrainz_token() { |
| 263 | Ok(Some(user)) => eprintln!("scrobble: listenbrainz token valid (user: {})", user), | 268 | Ok(Some(user)) => crate::debug_eprintln(format_args!( |
| 264 | Ok(None) => eprintln!("scrobble: listenbrainz token invalid, scrobbling disabled"), | 269 | "scrobble: listenbrainz token valid (user: {})", |
| 265 | Err(e) => eprintln!("scrobble: listenbrainz validation error: {}", e), | 270 | user |
| 271 | )), | ||
| 272 | Ok(None) => crate::debug_eprintln(format_args!( | ||
| 273 | "scrobble: listenbrainz token invalid, scrobbling disabled" | ||
| 274 | )), | ||
| 275 | Err(e) => crate::debug_eprintln(format_args!( | ||
| 276 | "scrobble: listenbrainz validation error: {}", | ||
| 277 | e | ||
| 278 | )), | ||
| 266 | } | 279 | } |
| 267 | } | 280 | } |
| 268 | let scrobble_state = Arc::new(Mutex::new(ScrobbleState::new())); | 281 | let scrobble_state = Arc::new(Mutex::new(ScrobbleState::new())); |
| @@ -1,3 +1,10 @@ | |||
| 1 | #[inline(always)] | ||
| 2 | pub fn debug_eprintln(args: core::fmt::Arguments<'_>) { | ||
| 3 | if cfg!(debug_assertions) { | ||
| 4 | eprintln!("{}", args); | ||
| 5 | } | ||
| 6 | } | ||
| 7 | |||
| 1 | pub mod audio; | 8 | pub mod audio; |
| 2 | pub mod cli; | 9 | pub mod cli; |
| 3 | pub mod config; | 10 | 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; | |||
| 10 | 10 | ||
| 11 | use clap::Parser; | 11 | use clap::Parser; |
| 12 | use cli::Cli; | 12 | use cli::Cli; |
| 13 | pub use rsp::debug_eprintln; | ||
| 13 | 14 | ||
| 14 | fn main() { | 15 | fn main() { |
| 15 | let cli = Cli::parse(); | 16 | 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 { | |||
| 17 | } | 17 | } |
| 18 | 18 | ||
| 19 | pub fn extract_metadata(path: &Path) -> Option<AudioMetadata> { | 19 | pub fn extract_metadata(path: &Path) -> Option<AudioMetadata> { |
| 20 | use symphonia::core::formats::probe::Hint; | ||
| 20 | use symphonia::core::io::MediaSourceStream; | 21 | use symphonia::core::io::MediaSourceStream; |
| 21 | use symphonia::core::meta::StandardTagKey; | 22 | use symphonia::core::meta::StandardTag; |
| 22 | use symphonia::core::probe::Hint; | ||
| 23 | 23 | ||
| 24 | let file = std::fs::File::open(path).ok()?; | 24 | let file = std::fs::File::open(path).ok()?; |
| 25 | let mss = MediaSourceStream::new(Box::new(file), Default::default()); | 25 | let mss = MediaSourceStream::new(Box::new(file), Default::default()); |
| 26 | 26 | ||
| 27 | let hint = Hint::new(); | 27 | let hint = Hint::new(); |
| 28 | let mut probed = symphonia::default::get_probe() | 28 | let mut probed = symphonia::default::get_probe() |
| 29 | .format(&hint, mss, &Default::default(), &Default::default()) | 29 | .probe(&hint, mss, Default::default(), Default::default()) |
| 30 | .ok()?; | 30 | .ok()?; |
| 31 | 31 | ||
| 32 | // Extract duration from format track's codec parameters | 32 | // Extract duration from track's num_frames and time_base |
| 33 | let duration_secs = probed | 33 | let duration_secs = probed |
| 34 | .format | ||
| 35 | .tracks() | 34 | .tracks() |
| 36 | .iter() | 35 | .iter() |
| 37 | .find(|t| t.codec_params.n_frames.is_some() && t.codec_params.time_base.is_some()) | 36 | .find(|t| t.num_frames.is_some() && t.time_base.is_some()) |
| 38 | .and_then(|t| { | 37 | .and_then(|t| { |
| 39 | let n_frames = t.codec_params.n_frames?; | 38 | let n_frames = t.num_frames?; |
| 40 | let tb = t.codec_params.time_base?; | 39 | let tb = t.time_base?; |
| 41 | Some(n_frames as f64 * tb.numer as f64 / tb.denom as f64) | 40 | Some(n_frames as f64 * tb.numer.get() as f64 / tb.denom.get() as f64) |
| 42 | }); | 41 | }); |
| 43 | 42 | ||
| 44 | let mut metadata = AudioMetadata { | 43 | let mut metadata = AudioMetadata { |
| @@ -46,38 +45,39 @@ pub fn extract_metadata(path: &Path) -> Option<AudioMetadata> { | |||
| 46 | ..Default::default() | 45 | ..Default::default() |
| 47 | }; | 46 | }; |
| 48 | 47 | ||
| 49 | if let Some(meta) = probed.metadata.get() | ||
| 50 | && let Some(rev) = meta.current() | ||
| 51 | { | 48 | { |
| 52 | for tag in rev.tags() { | 49 | let meta = probed.metadata(); |
| 53 | match tag.std_key { | 50 | if let Some(rev) = meta.current() { |
| 54 | Some(StandardTagKey::TrackTitle) => { | 51 | for tag in &rev.media.tags { |
| 55 | metadata.title = Some(tag.value.to_string()); | 52 | match &tag.std { |
| 56 | } | 53 | Some(StandardTag::TrackTitle(value)) => { |
| 57 | Some(StandardTagKey::Artist) => { | 54 | metadata.title = Some(value.to_string()); |
| 58 | metadata.artist = Some(tag.value.to_string()); | 55 | } |
| 59 | } | 56 | Some(StandardTag::Artist(value)) => { |
| 60 | Some(StandardTagKey::Album) => { | 57 | metadata.artist = Some(value.to_string()); |
| 61 | metadata.album = Some(tag.value.to_string()); | 58 | } |
| 59 | Some(StandardTag::Album(value)) => { | ||
| 60 | metadata.album = Some(value.to_string()); | ||
| 61 | } | ||
| 62 | _ => {} | ||
| 62 | } | 63 | } |
| 63 | _ => {} | ||
| 64 | } | 64 | } |
| 65 | } | 65 | } |
| 66 | } | 66 | } |
| 67 | 67 | ||
| 68 | if metadata.title.is_none() && metadata.artist.is_none() && metadata.album.is_none() { | 68 | if metadata.title.is_none() && metadata.artist.is_none() && metadata.album.is_none() { |
| 69 | let mut format = probed.format; | 69 | let meta = probed.metadata(); |
| 70 | if let Some(rev) = format.metadata().current() { | 70 | if let Some(rev) = meta.current() { |
| 71 | for tag in rev.tags() { | 71 | for tag in &rev.media.tags { |
| 72 | match tag.std_key { | 72 | match &tag.std { |
| 73 | Some(StandardTagKey::TrackTitle) => { | 73 | Some(StandardTag::TrackTitle(value)) => { |
| 74 | metadata.title = Some(tag.value.to_string()); | 74 | metadata.title = Some(value.to_string()); |
| 75 | } | 75 | } |
| 76 | Some(StandardTagKey::Artist) => { | 76 | Some(StandardTag::Artist(value)) => { |
| 77 | metadata.artist = Some(tag.value.to_string()); | 77 | metadata.artist = Some(value.to_string()); |
| 78 | } | 78 | } |
| 79 | Some(StandardTagKey::Album) => { | 79 | Some(StandardTag::Album(value)) => { |
| 80 | metadata.album = Some(tag.value.to_string()); | 80 | metadata.album = Some(value.to_string()); |
| 81 | } | 81 | } |
| 82 | _ => {} | 82 | _ => {} |
| 83 | } | 83 | } |
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 { | |||
| 47 | .send()?; | 47 | .send()?; |
| 48 | 48 | ||
| 49 | if !resp.status().is_success() { | 49 | if !resp.status().is_success() { |
| 50 | eprintln!( | 50 | crate::debug_eprintln(format_args!( |
| 51 | "scrobble: listenbrainz token validation failed: {}", | 51 | "scrobble: listenbrainz token validation failed: {}", |
| 52 | resp.status() | 52 | resp.status() |
| 53 | ); | 53 | )); |
| 54 | return Ok(None); | 54 | return Ok(None); |
| 55 | } | 55 | } |
| 56 | 56 | ||
| @@ -62,13 +62,18 @@ impl Scrobbler { | |||
| 62 | .and_then(|v| v.as_str()) | 62 | .and_then(|v| v.as_str()) |
| 63 | .map(|s| s.to_string()); | 63 | .map(|s| s.to_string()); |
| 64 | if let Some(ref u) = user { | 64 | if let Some(ref u) = user { |
| 65 | eprintln!("scrobble: listenbrainz token valid (user: {})", u); | 65 | crate::debug_eprintln(format_args!( |
| 66 | "scrobble: listenbrainz token valid (user: {})", | ||
| 67 | u | ||
| 68 | )); | ||
| 66 | } else { | 69 | } else { |
| 67 | eprintln!("scrobble: listenbrainz token valid"); | 70 | crate::debug_eprintln(format_args!("scrobble: listenbrainz token valid")); |
| 68 | } | 71 | } |
| 69 | Ok(user) | 72 | Ok(user) |
| 70 | } else { | 73 | } else { |
| 71 | eprintln!("scrobble: listenbrainz token invalid — scrobbling will fail"); | 74 | crate::debug_eprintln(format_args!( |
| 75 | "scrobble: listenbrainz token invalid — scrobbling will fail" | ||
| 76 | )); | ||
| 72 | Ok(None) | 77 | Ok(None) |
| 73 | } | 78 | } |
| 74 | } | 79 | } |
| @@ -82,22 +87,29 @@ impl Scrobbler { | |||
| 82 | && !secret.is_empty() | 87 | && !secret.is_empty() |
| 83 | && !session_key.is_empty() | 88 | && !session_key.is_empty() |
| 84 | { | 89 | { |
| 85 | eprintln!("scrobble: now_playing -> lastfm '{} - {}'", artist, track); | 90 | crate::debug_eprintln(format_args!( |
| 91 | "scrobble: now_playing -> lastfm '{} - {}'", | ||
| 92 | artist, track | ||
| 93 | )); | ||
| 86 | let _ = | 94 | let _ = |
| 87 | self.send_lastfm_now_playing(api_key, secret, session_key, artist, track, album); | 95 | self.send_lastfm_now_playing(api_key, secret, session_key, artist, track, album); |
| 88 | } else { | 96 | } else { |
| 89 | eprintln!("scrobble: now_playing -> lastfm skipped (no credentials)"); | 97 | crate::debug_eprintln(format_args!( |
| 98 | "scrobble: now_playing -> lastfm skipped (no credentials)" | ||
| 99 | )); | ||
| 90 | } | 100 | } |
| 91 | if let Some(token) = &self.listenbrainz_token | 101 | if let Some(token) = &self.listenbrainz_token |
| 92 | && !token.is_empty() | 102 | && !token.is_empty() |
| 93 | { | 103 | { |
| 94 | eprintln!( | 104 | crate::debug_eprintln(format_args!( |
| 95 | "scrobble: now_playing -> listenbrainz '{} - {}'", | 105 | "scrobble: now_playing -> listenbrainz '{} - {}'", |
| 96 | artist, track | 106 | artist, track |
| 97 | ); | 107 | )); |
| 98 | let _ = self.send_listenbrainz_now_playing(token, artist, track, album); | 108 | let _ = self.send_listenbrainz_now_playing(token, artist, track, album); |
| 99 | } else { | 109 | } else { |
| 100 | eprintln!("scrobble: now_playing -> listenbrainz skipped (no token)"); | 110 | crate::debug_eprintln(format_args!( |
| 111 | "scrobble: now_playing -> listenbrainz skipped (no token)" | ||
| 112 | )); | ||
| 101 | } | 113 | } |
| 102 | } | 114 | } |
| 103 | 115 | ||
| @@ -110,10 +122,10 @@ impl Scrobbler { | |||
| 110 | && !secret.is_empty() | 122 | && !secret.is_empty() |
| 111 | && !session_key.is_empty() | 123 | && !session_key.is_empty() |
| 112 | { | 124 | { |
| 113 | eprintln!( | 125 | crate::debug_eprintln(format_args!( |
| 114 | "scrobble: scrobble -> lastfm '{} - {}' (ts={})", | 126 | "scrobble: scrobble -> lastfm '{} - {}' (ts={})", |
| 115 | artist, track, timestamp | 127 | artist, track, timestamp |
| 116 | ); | 128 | )); |
| 117 | let _ = self.send_lastfm_scrobble( | 129 | let _ = self.send_lastfm_scrobble( |
| 118 | api_key, | 130 | api_key, |
| 119 | secret, | 131 | secret, |
| @@ -124,18 +136,22 @@ impl Scrobbler { | |||
| 124 | timestamp, | 136 | timestamp, |
| 125 | ); | 137 | ); |
| 126 | } else { | 138 | } else { |
| 127 | eprintln!("scrobble: scrobble -> lastfm skipped (no credentials)"); | 139 | crate::debug_eprintln(format_args!( |
| 140 | "scrobble: scrobble -> lastfm skipped (no credentials)" | ||
| 141 | )); | ||
| 128 | } | 142 | } |
| 129 | if let Some(token) = &self.listenbrainz_token | 143 | if let Some(token) = &self.listenbrainz_token |
| 130 | && !token.is_empty() | 144 | && !token.is_empty() |
| 131 | { | 145 | { |
| 132 | eprintln!( | 146 | crate::debug_eprintln(format_args!( |
| 133 | "scrobble: scrobble -> listenbrainz '{} - {}' (ts={})", | 147 | "scrobble: scrobble -> listenbrainz '{} - {}' (ts={})", |
| 134 | artist, track, timestamp | 148 | artist, track, timestamp |
| 135 | ); | 149 | )); |
| 136 | let _ = self.send_listenbrainz_scrobble(token, artist, track, album, timestamp); | 150 | let _ = self.send_listenbrainz_scrobble(token, artist, track, album, timestamp); |
| 137 | } else { | 151 | } else { |
| 138 | eprintln!("scrobble: scrobble -> listenbrainz skipped (no token)"); | 152 | crate::debug_eprintln(format_args!( |
| 153 | "scrobble: scrobble -> listenbrainz skipped (no token)" | ||
| 154 | )); | ||
| 139 | } | 155 | } |
| 140 | } | 156 | } |
| 141 | 157 | ||
| @@ -177,13 +193,13 @@ impl Scrobbler { | |||
| 177 | 193 | ||
| 178 | let resp = self.client.post(LASTFM_API_URL).form(¶ms).send()?; | 194 | let resp = self.client.post(LASTFM_API_URL).form(¶ms).send()?; |
| 179 | if resp.status().is_success() { | 195 | if resp.status().is_success() { |
| 180 | eprintln!("scrobble: lastfm now_playing ok"); | 196 | crate::debug_eprintln(format_args!("scrobble: lastfm now_playing ok")); |
| 181 | } else { | 197 | } else { |
| 182 | eprintln!( | 198 | crate::debug_eprintln(format_args!( |
| 183 | "scrobble: lastfm now_playing error: {} {}", | 199 | "scrobble: lastfm now_playing error: {} {}", |
| 184 | resp.status(), | 200 | resp.status(), |
| 185 | resp.text().unwrap_or_default() | 201 | resp.text().unwrap_or_default() |
| 186 | ); | 202 | )); |
| 187 | } | 203 | } |
| 188 | Ok(()) | 204 | Ok(()) |
| 189 | } | 205 | } |
| @@ -217,13 +233,13 @@ impl Scrobbler { | |||
| 217 | 233 | ||
| 218 | let resp = self.client.post(LASTFM_API_URL).form(¶ms).send()?; | 234 | let resp = self.client.post(LASTFM_API_URL).form(¶ms).send()?; |
| 219 | if resp.status().is_success() { | 235 | if resp.status().is_success() { |
| 220 | eprintln!("scrobble: lastfm scrobble ok"); | 236 | crate::debug_eprintln(format_args!("scrobble: lastfm scrobble ok")); |
| 221 | } else { | 237 | } else { |
| 222 | eprintln!( | 238 | crate::debug_eprintln(format_args!( |
| 223 | "scrobble: lastfm scrobble error: {} {}", | 239 | "scrobble: lastfm scrobble error: {} {}", |
| 224 | resp.status(), | 240 | resp.status(), |
| 225 | resp.text().unwrap_or_default() | 241 | resp.text().unwrap_or_default() |
| 226 | ); | 242 | )); |
| 227 | } | 243 | } |
| 228 | Ok(()) | 244 | Ok(()) |
| 229 | } | 245 | } |
| @@ -257,13 +273,13 @@ impl Scrobbler { | |||
| 257 | .json(&payload) | 273 | .json(&payload) |
| 258 | .send()?; | 274 | .send()?; |
| 259 | if resp.status().is_success() { | 275 | if resp.status().is_success() { |
| 260 | eprintln!("scrobble: listenbrainz now_playing ok"); | 276 | crate::debug_eprintln(format_args!("scrobble: listenbrainz now_playing ok")); |
| 261 | } else { | 277 | } else { |
| 262 | eprintln!( | 278 | crate::debug_eprintln(format_args!( |
| 263 | "scrobble: listenbrainz now_playing error: {} {}", | 279 | "scrobble: listenbrainz now_playing error: {} {}", |
| 264 | resp.status(), | 280 | resp.status(), |
| 265 | resp.text().unwrap_or_default() | 281 | resp.text().unwrap_or_default() |
| 266 | ); | 282 | )); |
| 267 | } | 283 | } |
| 268 | Ok(()) | 284 | Ok(()) |
| 269 | } | 285 | } |
| @@ -299,13 +315,13 @@ impl Scrobbler { | |||
| 299 | .json(&payload) | 315 | .json(&payload) |
| 300 | .send()?; | 316 | .send()?; |
| 301 | if resp.status().is_success() { | 317 | if resp.status().is_success() { |
| 302 | eprintln!("scrobble: listenbrainz scrobble ok"); | 318 | crate::debug_eprintln(format_args!("scrobble: listenbrainz scrobble ok")); |
| 303 | } else { | 319 | } else { |
| 304 | eprintln!( | 320 | crate::debug_eprintln(format_args!( |
| 305 | "scrobble: listenbrainz scrobble error: {} {}", | 321 | "scrobble: listenbrainz scrobble error: {} {}", |
| 306 | resp.status(), | 322 | resp.status(), |
| 307 | resp.text().unwrap_or_default() | 323 | resp.text().unwrap_or_default() |
| 308 | ); | 324 | )); |
| 309 | } | 325 | } |
| 310 | Ok(()) | 326 | Ok(()) |
| 311 | } | 327 | } |
