From 956e3be3808f918923aef3259ea5b28bfc055b03 Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 22 May 2026 17:57:08 +0200 Subject: Fix metadata extraction for symphonia 0.6 and add Albums shuffle mode --- src/config.rs | 2 +- src/daemon.rs | 7 ++- src/playback.rs | 1 + src/queue.rs | 130 ++++++++++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 115 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/config.rs b/src/config.rs index 5fd2f1b..63b0164 100644 --- a/src/config.rs +++ b/src/config.rs @@ -200,7 +200,7 @@ impl Config { self.volume = Some(value.parse()?); } "shuffle" => { - if value == "on" || value == "all" || value == "album" { + if value == "on" || value == "all" || value == "album" || value == "albums" { self.shuffle = Some(value.to_string()); } else if value == "off" || value == "none" { self.shuffle = None; diff --git a/src/daemon.rs b/src/daemon.rs index 8b74357..b18ecc9 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -248,6 +248,7 @@ pub fn start_daemon() -> Result<(), Box> { match shuffle_str.as_str() { "on" | "all" => q.set_shuffle(ShuffleMode::All), "album" => q.set_shuffle(ShuffleMode::Album), + "albums" => q.set_shuffle(ShuffleMode::Albums), _ => {} } } @@ -633,6 +634,7 @@ fn process_command( ShuffleMode::None => "Shuffle: off\n".to_string(), ShuffleMode::All => "Shuffle: on (all)\n".to_string(), ShuffleMode::Album => "Shuffle: on (album)\n".to_string(), + ShuffleMode::Albums => "Shuffle: on (albums)\n".to_string(), } } else { "Error accessing queue\n".to_string() @@ -647,11 +649,14 @@ fn process_command( } else if mode_str == "album" { q.set_shuffle(ShuffleMode::Album); "Shuffle: on (album)\n".to_string() + } else if mode_str == "albums" { + q.set_shuffle(ShuffleMode::Albums); + "Shuffle: on (albums)\n".to_string() } else if mode_str == "off" || mode_str == "none" { q.set_shuffle(ShuffleMode::None); "Shuffle: off\n".to_string() } else { - "Usage: shuffle [on|off|album]\n".to_string() + "Usage: shuffle [on|off|album|albums]\n".to_string() }; // Save to config if let Ok(mut config) = Config::load() { diff --git a/src/playback.rs b/src/playback.rs index d9dd1f9..f16bf8f 100644 --- a/src/playback.rs +++ b/src/playback.rs @@ -24,6 +24,7 @@ pub enum ShuffleMode { None, All, Album, + Albums, } #[allow(dead_code)] diff --git a/src/queue.rs b/src/queue.rs index b0acf5f..9d09fec 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -6,6 +6,7 @@ pub enum ShuffleMode { None, All, Album, + Albums, } #[derive(Debug, Default, Clone)] @@ -46,28 +47,8 @@ pub fn extract_metadata(path: &Path) -> Option { }; { - 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 meta = probed.metadata(); - if let Some(rev) = meta.current() { + let mut meta = probed.metadata(); + if let Some(rev) = meta.skip_to_latest() { for tag in &rev.media.tags { match &tag.std { Some(StandardTag::TrackTitle(value)) => { @@ -183,6 +164,12 @@ impl Queue { self.update_shuffle_indices(); } + #[allow(dead_code)] + pub fn push_track(&mut self, track: Track) { + self.tracks.push(track); + self.update_shuffle_indices(); + } + pub fn insert_after_current(&mut self, path: PathBuf) { if self.tracks.is_empty() { self.tracks.push(Track::from_path(path)); @@ -213,6 +200,11 @@ impl Queue { self.shuffle = ShuffleMode::Album; self.update_shuffle_indices_album(); } + ShuffleMode::Albums => { + self.original_tracks = self.tracks.clone(); + self.shuffle = ShuffleMode::Albums; + self.update_shuffle_indices_albums(); + } } } @@ -268,6 +260,9 @@ impl Queue { ShuffleMode::Album => { self.update_shuffle_indices_album(); } + ShuffleMode::Albums => { + self.update_shuffle_indices_albums(); + } ShuffleMode::None => {} } } @@ -285,6 +280,62 @@ impl Queue { } } + fn update_shuffle_indices_albums(&mut self) { + let len = self.tracks.len(); + self.shuffled_indices = (0..len).collect(); + + if len <= 1 { + return; + } + + let pos = self.position.min(len.saturating_sub(1)); + if pos + 1 >= len { + return; + } + + let range_start = pos + 1; + let mut rng = rand::rng(); + + // Build album groups within the range, shuffling tracks within each group + let mut groups: Vec> = Vec::new(); + let mut current_group: Vec = Vec::new(); + let mut current_album: Option<&str> = None; + + for i in range_start..len { + let album = self.tracks.get(i).and_then(|t| t.album.as_deref()); + + if current_group.is_empty() { + current_group.push(i); + current_album = album; + } else if album != current_album { + // Shuffle tracks within this album group before adding it + if current_group.len() > 1 { + current_group.shuffle(&mut rng); + } + groups.push(std::mem::take(&mut current_group)); + current_group.push(i); + current_album = album; + } else { + current_group.push(i); + } + } + if !current_group.is_empty() { + if current_group.len() > 1 { + current_group.shuffle(&mut rng); + } + groups.push(current_group); + } + + // Shuffle the album group order + groups.shuffle(&mut rng); + + let mut new_indices: Vec = self.shuffled_indices[..=pos].to_vec(); + for group in &groups { + new_indices.extend(group); + } + self.shuffled_indices = new_indices; + } + fn shuffle_album_range(&mut self, range: std::ops::Range, rng: &mut impl rand::Rng) { if range.start >= range.end { return; @@ -327,7 +378,7 @@ impl Queue { self.shuffled_indices .get(self.position) .and_then(|&idx| self.tracks.get(idx)) - } else if self.shuffle == ShuffleMode::Album { + } else if self.shuffle == ShuffleMode::Album || self.shuffle == ShuffleMode::Albums { self.shuffled_indices .get(self.position) .and_then(|&idx| self.tracks.get(idx)) @@ -397,6 +448,39 @@ impl Queue { ShuffleMode::Album => { self.shuffle_album_range(0..self.tracks.len(), &mut rng); } + ShuffleMode::Albums => { + let mut groups: Vec> = Vec::new(); + let mut current_group: Vec = Vec::new(); + let mut current_album: Option<&str> = None; + + for i in 0..self.tracks.len() { + let album = self.tracks.get(i).and_then(|t| t.album.as_deref()); + + if current_group.is_empty() { + current_group.push(i); + current_album = album; + } else if album != current_album { + if current_group.len() > 1 { + current_group.shuffle(&mut rng); + } + groups.push(std::mem::take(&mut current_group)); + current_group.push(i); + current_album = album; + } else { + current_group.push(i); + } + } + if !current_group.is_empty() { + if current_group.len() > 1 { + current_group.shuffle(&mut rng); + } + groups.push(current_group); + } + + groups.shuffle(&mut rng); + + self.shuffled_indices = groups.concat(); + } ShuffleMode::None => {} } } -- cgit v1.2.3