Krolyxon
Commit 4993431

rmptui4993431

add metadata in directory tree

Author
krolxon
Date
2024-04-29 15:00 +0530
Parent
51fbf42
Commit
49934311862747e33550ccfebbc9cbbef6a3455d

+179 −42

6 files changed

  1. M src/app.rs +1 −1
  2. M src/browser.rs +57 −1
  3. M src/connection.rs +2 −27
  4. M src/handler.rs +2 −0
  5. M src/main.rs +2 −10
  6. M src/ui.rs +115 −3
M src/app.rs View
@@ -170,7 +170,7 @@ impl App {
170170 self.selected_tab = match self.selected_tab {
171171 SelectedTab::Queue => SelectedTab::DirectoryBrowser,
172172 SelectedTab::DirectoryBrowser => SelectedTab::Playlists,
173− SelectedTab::Playlists => SelectedTab::Queue,
173+ SelectedTab::Playlists => SelectedTab::DirectoryBrowser,
174174 };
175175 }
176176
M src/browser.rs View
@@ -1,3 +1,8 @@
1+use std::ffi::OsStr;
2+use std::path::Path;
3+
4+use mpd::Song;
5+
16 use crate::{app::AppResult, connection::Connection};
27
38 #[derive(Debug)]
@@ -7,6 +12,24 @@ pub struct FileBrowser {
712 pub prev_selected: usize,
813 pub path: String,
914 pub prev_path: String,
15+ pub songs: Vec<Song>,
16+}
17+
18+// https://stackoverflow.com/questions/72392835/check-if-a-file-is-of-a-given-type
19+pub trait FileExtension {
20+ fn has_extension<S: AsRef<str>>(&self, extensions: &[S]) -> bool;
21+}
22+
23+impl<P: AsRef<Path>> FileExtension for P {
24+ fn has_extension<S: AsRef<str>>(&self, extensions: &[S]) -> bool {
25+ if let Some(ref extension) = self.as_ref().extension().and_then(OsStr::to_str) {
26+ return extensions
27+ .iter()
28+ .any(|x| x.as_ref().eq_ignore_ascii_case(extension));
29+ }
30+
31+ false
32+ }
1033 }
1134
1235 impl FileBrowser {
@@ -17,6 +40,7 @@ impl FileBrowser {
1740 prev_selected: 0,
1841 path: ".".to_string(),
1942 prev_path: ".".to_string(),
43+ songs: vec![],
2044 }
2145 }
2246
@@ -26,9 +50,41 @@ impl FileBrowser {
2650 .conn
2751 .listfiles(self.path.as_str())?
2852 .into_iter()
29− .filter(|(f, _)| f == "directory" || f == "file")
53+ .filter(|(f, l)| {
54+ f == "directory"
55+ || f == "file" && Path::new(l).has_extension(&["mp3", "ogg", "flac", "m4a", "wav", "aac" ,"opus", "ape", "wma", "mpc", "aiff", "dff", "mp2", "mka"])
56+ })
3057 .collect::<Vec<(String, String)>>();
3158
59+ self.songs.clear();
60+ for (t, song) in self.filetree.iter() {
61+ if t == "file" {
62+ let v = conn
63+ .conn
64+ .lsinfo(Song {
65+ file: conn
66+ .get_full_path(song)
67+ .unwrap_or_else(|| "Not a song".to_string()),
68+ ..Default::default()
69+ })
70+ .unwrap_or_else(|_| {
71+ vec![Song {
72+ file: "Not a song".to_string(),
73+ ..Default::default()
74+ }]
75+ });
76+
77+ self.songs.push(v.get(0).unwrap().clone());
78+ } else {
79+ let v = Song {
80+ file: "".to_string(),
81+ ..Default::default()
82+ };
83+
84+ self.songs.push(v);
85+ }
86+ }
87+
3288 Ok(())
3389 }
3490
M src/connection.rs View
@@ -27,14 +27,7 @@ impl Connection {
2727
2828 let empty_song = Song {
2929 file: "No Song playing or in Queue".to_string(),
30− artist: None,
31− title: None,
32− duration: None,
33− last_mod: None,
34− name: None,
35− place: None,
36− range: None,
37− tags: vec![("".to_string(), "".to_string())],
30+ ..Default::default()
3831 };
3932
4033 let songs_filenames: Vec<String> = conn
@@ -70,7 +63,6 @@ impl Connection {
7063 /// Fzf prompt for selecting song
7164 pub fn play_fzf(&mut self) -> Result<()> {
7265 is_installed("fzf").map_err(|ex| ex)?;
73−
7466 let ss = &self.songs_filenames;
7567 let fzf_choice = rust_fzf::select(ss.clone(), Vec::new()).unwrap();
7668 let index = get_choice_index(&self.songs_filenames, fzf_choice.get(0).unwrap());
@@ -174,29 +166,12 @@ impl Connection {
174166 pub fn get_song_with_only_filename(&self, filename: &str) -> Song {
175167 Song {
176168 file: filename.to_string(),
177− artist: None,
178− title: None,
179− duration: None,
180− last_mod: None,
181− name: None,
182− place: None,
183− range: None,
184− tags: vec![("".to_string(), "".to_string())],
169+ ..Default::default()
185170 }
186171 }
187172
188173 /// Given a song name from a directory, it returns the full path of the song in the database
189174 pub fn get_full_path(&self, short_path: &str) -> Option<String> {
190− // let list = self
191− // .songs_filenames
192− // .iter()
193− // .map(|f| f.as_str())
194− // .collect::<Vec<&str>>();
195− // let (filename, _) = rust_fuzzy_search::fuzzy_search_sorted(&short_path, &list)
196− // .get(0)
197− // .unwrap()
198− // .clone();
199−
200175 for (i, f) in self.songs_filenames.iter().enumerate() {
201176 if f.contains(short_path) {
202177 return Some(self.songs_filenames.get(i).unwrap().to_string());
M src/handler.rs View
@@ -62,6 +62,7 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
6262 }
6363 }
6464 }
65+
6566 }
6667
6768 match key_event.code {
@@ -199,6 +200,7 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
199200 app.conn
200201 .load_playlist(app.pl_list.list.get(app.pl_list.index).unwrap())?;
201202 }
203+
202204 }
203205 app.conn.update_status();
204206 }
M src/main.rs View
@@ -26,7 +26,7 @@ pub type Error = Box<dyn std::error::Error>;
2626 fn main() -> AppResult<()> {
2727 let args = Args::parse();
2828 let env_host = env::var("MPD_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
29− let env_port = env::var("MPD_PORT").unwrap_or_else(|_| "6600".to_string());
29+ let env_port = env::var("MPD_PORT").unwrap_or_else(|_| "6600".to_string());
3030 let mut app = App::builder(format!("{}:{}", env_host, env_port).as_str())?;
3131
3232 if !args.tui {
@@ -38,15 +38,7 @@ fn main() -> AppResult<()> {
3838 Some(Command::Status) => app.conn.status(),
3939 Some(Command::Pause) => app.conn.pause(),
4040 Some(Command::Toggle) => app.conn.toggle_pause(),
41− _ => {
42− // let mut vec: Vec<RSong> = Vec::new();
43− // for filename in app.conn.songs_filenames {
44− // let song = RSong::new(&mut app.conn.conn, filename);
45− // vec.push(song);
46− // }
47− // println!("{:#?}", vec);
48−
49− }
41+ _ => {}
5042 }
5143 }
5244 Ok(())
M src/ui.rs View
@@ -1,3 +1,5 @@
1+use std::time::Duration;
2+
13 use crate::app::{App, SelectedTab};
24 use ratatui::{
35 prelude::*,
@@ -30,7 +32,8 @@ pub fn render(app: &mut App, frame: &mut Frame) {
3032 match app.selected_tab {
3133 SelectedTab::Queue => draw_queue(frame, app, layout[0]),
3234 SelectedTab::Playlists => draw_playlists(frame, app, layout[0]),
33− SelectedTab::DirectoryBrowser => draw_directory_browser(frame, app, layout[0]),
35+ SelectedTab::DirectoryBrowser => draw_song_browser(frame, app, layout[0]),
36+ // SelectedTab::SongBrowser => draw_song_browser(frame, app, layout[0]),
3437 }
3538
3639 match app.inputmode {
@@ -63,6 +66,7 @@ fn draw_directory_browser(frame: &mut Frame, app: &mut App, size: Rect) {
6366 if status {
6467 list.push(ListItem::new(s.clone().magenta().bold()));
6568 } else {
69+ // list.push(ListItem::new(s.clone()));
6670 list.push(ListItem::new(s.clone()));
6771 }
6872 } else {
@@ -75,7 +79,7 @@ fn draw_directory_browser(frame: &mut Frame, app: &mut App, size: Rect) {
7579 let list = List::new(list)
7680 .block(
7781 Block::default()
78− .title(format!("File Browser: {}", app.browser.path.clone()).bold())
82+ .title(format!("Directory Browser: {}", app.browser.path.clone()).bold())
7983 .title(
8084 Title::from(format!("Total Songs: {}", total_songs).bold().green())
8185 .alignment(Alignment::Center),
@@ -101,12 +105,120 @@ fn draw_directory_browser(frame: &mut Frame, app: &mut App, size: Rect) {
101105 frame.render_stateful_widget(list, size, &mut song_state);
102106 }
103107
108+/// Draws the song browser
109+fn draw_song_browser(frame: &mut Frame, app: &mut App, size: Rect) {
110+ let total_songs = app.conn.conn.stats().unwrap().songs.to_string();
111+
112+ let rows = app.browser.filetree.iter().enumerate().map(|(i, (t, s))| {
113+ if t == "file" {
114+ let song = app.browser.songs.get(i).unwrap().clone();
115+
116+ // metadata
117+ let title = song.clone().title.unwrap_or_else(|| song.clone().file);
118+ let artist = song.clone().artist.unwrap_or_default().cyan();
119+ let album = song
120+ .tags
121+ .iter()
122+ .filter(|(x, _)| x == "Album")
123+ .map(|(_, l)| l.clone())
124+ .collect::<Vec<String>>()
125+ .join("");
126+
127+ let track = song
128+ .tags
129+ .iter()
130+ .filter(|(x, _)| x == "Track")
131+ .map(|(_, l)| l.clone())
132+ .collect::<Vec<String>>()
133+ .join("");
134+
135+ let time = humantime::format_duration(
136+ song.clone().duration.unwrap_or_else(|| Duration::new(0, 0)),
137+ );
138+
139+ let mut status: bool = false;
140+ for sn in app.queue_list.list.iter() {
141+ if sn.contains(s) {
142+ status = true;
143+ }
144+ }
145+
146+ if status {
147+ let row = Row::new(vec![
148+ Cell::from(artist),
149+ Cell::from(track.green()),
150+ Cell::from(title),
151+ Cell::from(album),
152+ Cell::from(time.to_string().red()),
153+ ]);
154+ row.magenta().bold()
155+ } else {
156+ let row = Row::new(vec![
157+ Cell::from(artist),
158+ Cell::from(track.green()),
159+ Cell::from(title),
160+ Cell::from(album),
161+ Cell::from(time.to_string().red()),
162+ ]);
163+ row
164+ }
165+ } else {
166+ let row = Row::new(vec![Cell::from(format!("[{}]", *s))]);
167+ row
168+ }
169+ });
170+
171+ let mut state = TableState::new();
172+ let header = ["Artist", "Track", "Title", "Album", "Time"]
173+ .into_iter()
174+ .map(Cell::from)
175+ .collect::<Row>()
176+ .bold()
177+ .height(1);
178+ let table = Table::new(
179+ rows,
180+ [
181+ Constraint::Percentage(33),
182+ Constraint::Percentage(3),
183+ Constraint::Percentage(30),
184+ Constraint::Percentage(30),
185+ Constraint::Percentage(3),
186+ ],
187+ )
188+ .block(
189+ Block::default()
190+ .title(format!("Song Browser: {}", app.browser.path.clone()).bold())
191+ .title(
192+ Title::from(format!("Total Songs: {}", total_songs).bold().green())
193+ .alignment(Alignment::Center),
194+ )
195+ .title(
196+ Title::from(format!("Volume: {}%", app.conn.volume).bold().green())
197+ .alignment(Alignment::Right),
198+ )
199+ .borders(Borders::ALL),
200+ )
201+ .highlight_style(
202+ Style::default()
203+ .add_modifier(Modifier::REVERSED)
204+ .fg(Color::Cyan)
205+ .bg(Color::Black),
206+ )
207+ .highlight_symbol(">>")
208+ .header(header);
209+
210+ state.select(Some(app.browser.selected));
211+ frame.render_stateful_widget(table, size, &mut state);
212+}
213+
104214 /// draws playing queue
105215 fn draw_queue(frame: &mut Frame, app: &mut App, size: Rect) {
106216 let mut queue_state = ListState::default();
107217 let title = Block::default()
108218 .title(Title::from("Play Queue".green().bold()))
109− .title(Title::from(format!("({} items)", app.queue_list.list.len()).bold()))
219+ .title(Title::from(
220+ format!("({} items)", app.queue_list.list.len()).bold(),
221+ ))
110222 .title(
111223 Title::from(format!("Volume: {}%", app.conn.volume).bold().green())
112224 .alignment(Alignment::Right),