aboutsummaryrefslogtreecommitdiffstats
path: root/src/queue.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/queue.rs')
-rw-r--r--src/queue.rs250
1 files changed, 250 insertions, 0 deletions
diff --git a/src/queue.rs b/src/queue.rs
new file mode 100644
index 0000000..53b104f
--- /dev/null
+++ b/src/queue.rs
@@ -0,0 +1,250 @@
1use rand::seq::SliceRandom;
2use std::path::{Path, PathBuf};
3
4#[derive(Debug, Clone, PartialEq)]
5pub enum ShuffleMode {
6 None,
7 All,
8 Album,
9}
10
11#[derive(Debug, Default, Clone)]
12pub struct AudioMetadata {
13 pub title: Option<String>,
14 pub artist: Option<String>,
15 pub album: Option<String>,
16}
17
18pub fn extract_metadata(path: &Path) -> Option<AudioMetadata> {
19 use symphonia::core::io::MediaSourceStream;
20 use symphonia::core::meta::StandardTagKey;
21 use symphonia::core::probe::Hint;
22
23 let file = std::fs::File::open(path).ok()?;
24 let mss = MediaSourceStream::new(Box::new(file), Default::default());
25
26 let hint = Hint::new();
27 let mut probed = symphonia::default::get_probe()
28 .format(&hint, mss, &Default::default(), &Default::default())
29 .ok()?;
30
31 let mut metadata = AudioMetadata::default();
32
33 if let Some(meta) = probed.metadata.get()
34 && let Some(rev) = meta.current()
35 {
36 for tag in rev.tags() {
37 match tag.std_key {
38 Some(StandardTagKey::TrackTitle) => {
39 metadata.title = Some(tag.value.to_string());
40 }
41 Some(StandardTagKey::Artist) => {
42 metadata.artist = Some(tag.value.to_string());
43 }
44 Some(StandardTagKey::Album) => {
45 metadata.album = Some(tag.value.to_string());
46 }
47 _ => {}
48 }
49 }
50 }
51
52 if metadata.title.is_none() && metadata.artist.is_none() && metadata.album.is_none() {
53 let mut format = probed.format;
54 if let Some(rev) = format.metadata().current() {
55 for tag in rev.tags() {
56 match tag.std_key {
57 Some(StandardTagKey::TrackTitle) => {
58 metadata.title = Some(tag.value.to_string());
59 }
60 Some(StandardTagKey::Artist) => {
61 metadata.artist = Some(tag.value.to_string());
62 }
63 Some(StandardTagKey::Album) => {
64 metadata.album = Some(tag.value.to_string());
65 }
66 _ => {}
67 }
68 }
69 }
70 }
71
72 if metadata.title.is_some() || metadata.artist.is_some() || metadata.album.is_some() {
73 Some(metadata)
74 } else {
75 None
76 }
77}
78
79#[derive(Debug, Clone)]
80pub struct Track {
81 pub path: PathBuf,
82 pub title: Option<String>,
83 pub artist: Option<String>,
84 pub album: Option<String>,
85}
86
87impl Track {
88 pub fn from_path(path: PathBuf) -> Self {
89 let metadata = extract_metadata(&path);
90 let metadata = metadata.as_ref();
91 Track {
92 path,
93 title: metadata.and_then(|m| m.title.clone()),
94 artist: metadata.and_then(|m| m.artist.clone()),
95 album: metadata.and_then(|m| m.album.clone()),
96 }
97 }
98
99 pub fn display_string(&self, index: usize) -> String {
100 let album_artist = self
101 .artist
102 .clone()
103 .or_else(|| self.album.clone())
104 .unwrap_or_default();
105 let title = self.title.clone().unwrap_or_else(|| {
106 self.path
107 .file_name()
108 .and_then(|n| n.to_str())
109 .unwrap_or("Unknown")
110 .to_string()
111 });
112
113 if album_artist.is_empty() {
114 format!("{}. {}", index + 1, title)
115 } else {
116 format!("{}. {} - {}", index + 1, album_artist, title)
117 }
118 }
119}
120
121pub struct Queue {
122 tracks: Vec<Track>,
123 position: usize,
124 shuffle: ShuffleMode,
125 shuffled_indices: Vec<usize>,
126}
127
128impl Default for Queue {
129 fn default() -> Self {
130 Self::new()
131 }
132}
133
134impl Queue {
135 pub fn new() -> Self {
136 Queue {
137 tracks: Vec::new(),
138 position: 0,
139 shuffle: ShuffleMode::None,
140 shuffled_indices: Vec::new(),
141 }
142 }
143
144 pub fn add_track(&mut self, path: PathBuf) {
145 self.tracks.push(Track::from_path(path));
146 self.update_shuffle_indices();
147 }
148
149 pub fn insert_after_current(&mut self, path: PathBuf) {
150 if self.tracks.is_empty() {
151 self.tracks.push(Track::from_path(path));
152 } else {
153 let insert_idx = self.position + 1;
154 self.tracks.insert(insert_idx, Track::from_path(path));
155 }
156 self.update_shuffle_indices();
157 }
158
159 pub fn track_at(&self, idx: usize) -> Option<&Track> {
160 self.tracks.get(idx)
161 }
162
163 pub fn set_shuffle(&mut self, mode: ShuffleMode) {
164 self.shuffle = mode;
165 self.update_shuffle_indices();
166 }
167
168 fn update_shuffle_indices(&mut self) {
169 let len = self.tracks.len();
170 self.shuffled_indices = (0..len).collect();
171 if let ShuffleMode::All = self.shuffle {
172 let mut rng = rand::rng();
173 let pos = self.position.min(len.saturating_sub(1));
174 if pos + 1 < len {
175 self.shuffled_indices[pos + 1..].shuffle(&mut rng);
176 }
177 }
178 }
179
180 pub fn current(&self) -> Option<&Track> {
181 if self.tracks.is_empty() {
182 None
183 } else if self.shuffle == ShuffleMode::All {
184 self.shuffled_indices
185 .get(self.position)
186 .and_then(|&idx| self.tracks.get(idx))
187 } else if self.shuffle == ShuffleMode::Album {
188 self.shuffled_indices
189 .get(self.position)
190 .and_then(|&idx| self.tracks.get(idx))
191 } else {
192 self.tracks.get(self.position)
193 }
194 }
195
196 #[allow(clippy::should_implement_trait)]
197 pub fn next(&mut self) -> Option<PathBuf> {
198 let max_pos = self.tracks.len().saturating_sub(1);
199 if self.position < max_pos {
200 self.position += 1;
201 self.current().map(|t| t.path.clone())
202 } else {
203 None
204 }
205 }
206
207 pub fn previous(&mut self) -> Option<PathBuf> {
208 if self.position > 0 {
209 self.position -= 1;
210 self.current().map(|t| t.path.clone())
211 } else {
212 None
213 }
214 }
215
216 pub fn is_empty(&self) -> bool {
217 self.tracks.is_empty()
218 }
219
220 pub fn len(&self) -> usize {
221 self.tracks.len()
222 }
223
224 pub fn position(&self) -> usize {
225 self.position
226 }
227
228 pub fn set_position(&mut self, pos: usize) {
229 self.position = pos;
230 }
231
232 #[allow(dead_code)]
233 pub fn tracks(&self) -> &[Track] {
234 &self.tracks
235 }
236
237 pub fn shuffle_mode(&self) -> &ShuffleMode {
238 &self.shuffle
239 }
240
241 pub fn shuffled_indices(&self) -> &[usize] {
242 &self.shuffled_indices
243 }
244
245 pub fn clear(&mut self) {
246 self.tracks.clear();
247 self.position = 0;
248 self.shuffled_indices.clear();
249 }
250}