aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio.rs97
-rw-r--r--src/daemon.rs39
-rw-r--r--src/lib.rs7
-rw-r--r--src/main.rs1
-rw-r--r--src/queue.rs64
-rw-r--r--src/scrobbler.rs72
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
102struct AudioData { 102struct 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
421fn open_audio_file(path: &PathBuf) -> Result<AudioData, Box<dyn std::error::Error>> { 416fn 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()));
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 @@
1#[inline(always)]
2pub fn debug_eprintln(args: core::fmt::Arguments<'_>) {
3 if cfg!(debug_assertions) {
4 eprintln!("{}", args);
5 }
6}
7
1pub mod audio; 8pub mod audio;
2pub mod cli; 9pub mod cli;
3pub mod config; 10pub 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
11use clap::Parser; 11use clap::Parser;
12use cli::Cli; 12use cli::Cli;
13pub use rsp::debug_eprintln;
13 14
14fn main() { 15fn 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
19pub fn extract_metadata(path: &Path) -> Option<AudioMetadata> { 19pub 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(&params).send()?; 194 let resp = self.client.post(LASTFM_API_URL).form(&params).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(&params).send()?; 234 let resp = self.client.post(LASTFM_API_URL).form(&params).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 }