diff --git a/README.md b/README.md index 37199a3..d958b1d 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ makepkg -si ### Install script -On Arch, Debian/Ubuntu, Fedora, openSUSE, Gentoo & Void the install script installs the dependencies and builds Drift with cargo: +On Arch, Debian/Ubuntu, Fedora and Void, the install script installs the dependencies and builds Drift with cargo: ```bash git clone https://github.com/socatlolmeow/drift.git @@ -134,7 +134,6 @@ Press `/` to search, `Enter` to play, `a` to queue and `?` for the full list of | `:shuffle` `:repeat [off\|all\|one]` | playback modes | | `:add [N]` `:addall` `:playall` | queue results | | `:save name` `:load name` `:append name` | work with playlists | -| `:savealbum [name]` `:saveresults [name]` | save album or results directly as a playlist | | `:addto name` `:delpl name` | add a song to / delete a playlist | | `:import URL` | import a YouTube / YouTube Music playlist | | `:album [query]` | open the selected song's album, or search albums | @@ -168,9 +167,6 @@ popular_first = false [ui] accent = "cyan" # a color name or a hex value like "#FF8800" - -[discord] -client_id = "" # change this for Discord RPC to work in Drift ``` Set `DRIFT_CONFIG_DIR` or `DRIFT_STATE_DIR` to change where Drift keeps its config, playlists and session. diff --git a/src/app.rs b/src/app.rs index 1f49e89..952b95f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -9,7 +9,7 @@ use ratatui::widgets::TableState; use crate::command::{self, Amount, Command, COMMAND_NAMES}; use crate::config::{self, Config}; use crate::lyrics::{self, Lyrics}; -use crate::model::{split_artists, Collection, RepeatMode, Track}; +use crate::model::{Collection, RepeatMode, Track}; use crate::discord::Discord; use crate::mpris::{Cmd, Mpris}; use crate::player::{self, Player, PlayerEvent}; @@ -37,10 +37,6 @@ pub enum AppEvent { title: String, result: Result, String>, }, - SavePlaylist { - name: String, - result: Result, String>, - }, Media(Cmd), Audio { id: String, @@ -118,9 +114,6 @@ pub enum MenuAction { Artist, Similar, Album, - SaveTrackAlbum, - SaveAlbum, - SaveResults, PlaylistMenu, PickPlaylist(String), NewPlaylist, @@ -550,7 +543,6 @@ impl App { ('g', 'a') => self.go_to_artist(), ('g', 's') => self.go_similar(), ('g', 'b') => self.go_album(), - ('g', 'B') => self.save_album(None), _ => {} } } @@ -696,7 +688,6 @@ impl App { items.push(menu_item('s', "Similar songs", MenuAction::Similar)); if track.album.is_some() { items.push(menu_item('b', "Show album", MenuAction::Album)); - items.push(menu_item('B', "Save album to playlist", MenuAction::SaveTrackAlbum)); } items.push(menu_item('l', "Add to playlist…", MenuAction::PlaylistMenu)); if in_queue { @@ -708,11 +699,6 @@ impl App { if self.view == View::Results { items.push(menu_item('Q', "Queue all results", MenuAction::QueueAll)); items.push(menu_item('P', "Play all results", MenuAction::PlayAll)); - if self.results_title.starts_with("Album: ") { - items.push(menu_item('S', "Save album as playlist…", MenuAction::SaveAlbum)); - } else { - items.push(menu_item('S', "Save results as playlist…", MenuAction::SaveResults)); - } } if in_queue { items.push(menu_item('c', "Clear queue", MenuAction::ClearQueue)); @@ -820,21 +806,6 @@ impl App { MenuAction::Artist => self.go_to_artist(), MenuAction::Similar => self.go_similar(), MenuAction::Album => self.go_album(), - MenuAction::SaveTrackAlbum => { - if let Some(t) = track { - let album_title = t.album.clone().unwrap_or_else(|| "Album".to_string()); - self.save_album_background(t, album_title); - } - } - MenuAction::SaveAlbum => { - let default_name = self.results_title.strip_prefix("Album: ").unwrap_or("").trim(); - if !default_name.is_empty() { - self.prefill_command(&format!("savealbum \"{default_name}\"")); - } else { - self.prefill_command("savealbum "); - } - } - MenuAction::SaveResults => self.prefill_command("saveresults "), MenuAction::PlaylistMenu => {} MenuAction::PickPlaylist(name) => self.add_to_playlist(&name), MenuAction::NewPlaylist => self.prefill_command("addto "), @@ -1163,8 +1134,6 @@ impl App { } Command::Save(name) => self.save_playlist(&name, false), Command::SaveForce(name) => self.save_playlist(&name, true), - Command::SaveAlbum(name) => self.save_album(name), - Command::SaveResults(name) => self.save_results_as_playlist(name), Command::Load(name) => match self.store.resolve(&name) { Some(n) => self.load_playlist(&n, true), None => self.error(format!("no playlist named '{name}'")), @@ -1314,56 +1283,6 @@ impl App { }); } - fn save_album(&mut self, name: Option) { - if self.view == View::Results - && self.results_title.starts_with("Album: ") - && !self.results.is_empty() - { - return self.save_results_as_playlist(name); - } - - if let Some(track) = self.artist_target() { - if let Some(album) = track.album.clone() { - let target_name = name.unwrap_or(album); - return self.save_album_background(track, target_name); - } - } - - if self.view == View::Results && !self.results.is_empty() { - return self.save_results_as_playlist(name); - } - - self.error("No album to save — open an album or select a song with an album"); - } - - fn save_album_background(&mut self, track: Track, target_name: String) { - let Some(album) = track.album else { - return self.error("No album known for this song"); - }; - let artist = split_artists(&track.artist) - .into_iter() - .next() - .unwrap_or_default(); - self.busy = Some(format!("Saving album \u{201c}{album}\u{201d}")); - let tx = self.tx.clone(); - std::thread::spawn(move || { - let result = (|| -> Result, String> { - let found = ytmusic::search_collections(&format!("{album} {artist}"), Kind::Album)?; - let want = album.to_lowercase(); - let pick = found - .iter() - .find(|c| c.title.to_lowercase() == want) - .or(found.first()) - .ok_or_else(|| "album not found".to_string())?; - ytmusic::browse_tracks(&pick.browse_id, &pick.title, &pick.artist) - })(); - let _ = tx.send(AppEvent::SavePlaylist { - name: target_name, - result, - }); - }); - } - fn start_collection_search(&mut self, query: String, kind: Kind) { let (what, title) = match kind { Kind::Album => ("albums", format!("Albums: {query}")), @@ -1387,7 +1306,6 @@ impl App { } match key.code { KeyCode::Esc | KeyCode::Char('q') => self.close_collection_pick(), - KeyCode::Char('s') | KeyCode::Char('S') => self.save_picked_collection(), KeyCode::Char('j') | KeyCode::Down => self.pick_sel = (self.pick_sel + 1).min(len - 1), KeyCode::Char('k') | KeyCode::Up => self.pick_sel = self.pick_sel.saturating_sub(1), KeyCode::Char('g') | KeyCode::Home => self.pick_sel = 0, @@ -1422,23 +1340,6 @@ impl App { }); } - fn save_picked_collection(&mut self) { - let c = self.picks.get(self.pick_sel).cloned(); - self.close_collection_pick(); - let Some(c) = c else { return }; - let kind = if c.browse_id.starts_with("VL") { "Playlist" } else { "Album" }; - let target_name = c.title.clone(); - self.busy = Some(format!("Saving {kind} \u{201c}{target_name}\u{201d}")); - let tx = self.tx.clone(); - std::thread::spawn(move || { - let result = ytmusic::browse_tracks(&c.browse_id, &c.title, &c.artist); - let _ = tx.send(AppEvent::SavePlaylist { - name: target_name, - result, - }); - }); - } - fn on_media(&mut self, cmd: Cmd) { match cmd { Cmd::Play => match self.play_state { @@ -1591,29 +1492,6 @@ impl App { } } AppEvent::Media(cmd) => self.on_media(cmd), - AppEvent::SavePlaylist { name, result } => { - self.busy = None; - match result { - Ok(tracks) if tracks.is_empty() => { - self.error(format!("Album \u{201c}{name}\u{201d} contains no tracks")); - } - Ok(tracks) => { - let n = tracks.len(); - let key = self.store.resolve_exact(&name).unwrap_or(name); - self.store.playlists.insert(key.clone(), tracks); - if self.persist() { - self.info(format!( - "Saved {n} tracks to playlist \u{201c}{key}\u{201d}" - )); - let sel = self.selected(); - self.set_selected(sel); - } - } - Err(e) => { - self.error(format!("Could not save album \u{201c}{name}\u{201d}: {e}")) - } - } - } AppEvent::Lyrics { id, result } => { if self.lyrics_id.as_deref() != Some(id.as_str()) { return; @@ -2301,40 +2179,6 @@ impl App { } } - fn save_results_as_playlist(&mut self, name: Option) { - if self.results.is_empty() { - return self.error("No results to save — search or open an album first"); - } - let target_name = match name { - Some(n) if !n.trim().is_empty() => n.trim().to_string(), - _ => { - if let Some(rest) = self.results_title.strip_prefix("Album: ") { - rest.trim().to_string() - } else if let Some(rest) = self.results_title.strip_prefix("Playlist: ") { - rest.trim().to_string() - } else if let Some(album) = self.results.iter().find_map(|t| t.album.as_deref()) { - album.to_string() - } else { - return self.error( - "No album name found; usage: :savealbum or :saveresults ", - ); - } - } - }; - let n = self.results.len(); - let key = self.store.resolve_exact(&target_name).unwrap_or(target_name); - self.store - .playlists - .insert(key.clone(), self.results.clone()); - if self.persist() { - self.info(format!( - "Saved {n} tracks to playlist \u{201c}{key}\u{201d}" - )); - let sel = self.selected(); - self.set_selected(sel); - } - } - fn add_to_playlist(&mut self, name: &str) { let i = self.selected(); let track = match self.view { @@ -2699,46 +2543,6 @@ mod tests { check_playlist_playback(&mut app); - // Test savealbum and saveresults - let mut t1 = track("song1111111", "Dreams"); - t1.album = Some("Rumours".into()); - let mut t2 = track("song2222222", "Go Your Own Way"); - t2.album = Some("Rumours".into()); - app.results = vec![t1.clone(), t2.clone()]; - app.results_title = "Album: Rumours".into(); - app.view = View::Results; - - // :savealbum with no args uses album name from results_title - app.run_command("savealbum"); - assert_eq!(app.store.playlists.get("Rumours").map(Vec::len), Some(2)); - - // :savealbum with custom name - app.run_command(r#"savealbum "Fleetwood Mac - Rumours""#); - assert_eq!( - app.store - .playlists - .get("Fleetwood Mac - Rumours") - .map(Vec::len), - Some(2) - ); - - // :saveresults with custom name - app.run_command(r#"saveresults "Classic Hits""#); - assert_eq!( - app.store.playlists.get("Classic Hits").map(Vec::len), - Some(2) - ); - - // Test AppEvent::SavePlaylist background event - app.on_event(AppEvent::SavePlaylist { - name: "Background Album".into(), - result: Ok(vec![track("bg111111111", "BG Track")]), - }); - assert_eq!( - app.store.playlists.get("Background Album").map(Vec::len), - Some(1) - ); - let _ = std::fs::remove_dir_all(&dir); } } diff --git a/src/command.rs b/src/command.rs index 3f6ada2..ccb116d 100644 --- a/src/command.rs +++ b/src/command.rs @@ -31,8 +31,6 @@ pub enum Command { Repeat(Option), Save(String), SaveForce(String), - SaveAlbum(Option), - SaveResults(Option), Load(String), Append(String), DeletePlaylist(String), @@ -69,8 +67,6 @@ pub const COMMAND_NAMES: &[&str] = &[ "shuffle", "repeat", "save", - "savealbum", - "saveresults", "load", "append", "delpl", @@ -178,8 +174,6 @@ pub fn parse(line: &str) -> Result, String> { }, "save" | "w" => Save(name_arg(arg, "save")?), "save!" | "w!" => SaveForce(name_arg(arg, "save!")?), - "savealbum" | "sa" => SaveAlbum((!arg.is_empty()).then(|| arg.to_string())), - "saveresults" | "sr" => SaveResults((!arg.is_empty()).then(|| arg.to_string())), "load" | "e" => Load(name_arg(arg, "load")?), "append" => Append(name_arg(arg, "append")?), "delpl" | "deletepl" | "delete" => DeletePlaylist(name_arg(arg, "delpl")?), @@ -290,18 +284,5 @@ mod tests { parse("save! my mix"), Ok(Some(Command::SaveForce("my mix".into()))) ); - assert_eq!(parse("savealbum"), Ok(Some(Command::SaveAlbum(None)))); - assert_eq!( - parse(r#"savealbum "Abbey Road""#), - Ok(Some(Command::SaveAlbum(Some("Abbey Road".into())))) - ); - assert_eq!( - parse("saveresults"), - Ok(Some(Command::SaveResults(None))) - ); - assert_eq!( - parse(r#"saveresults "My Hits""#), - Ok(Some(Command::SaveResults(Some("My Hits".into())))) - ); } } diff --git a/src/ui.rs b/src/ui.rs index e49af1a..6894886 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -806,8 +806,6 @@ const CMD_INFO: &[(&str, &str)] = &[ ("shuffle", "toggle shuffle"), ("repeat", "repeat · off | all | one"), ("save", "save the queue as a playlist"), - ("savealbum", "save album as a playlist · savealbum [name]"), - ("saveresults", "save results as a playlist · saveresults [name]"), ("load", "load a playlist into the queue"), ("append", "append a playlist to the queue"), ("delpl", "delete a playlist"), @@ -1134,14 +1132,6 @@ const HELP: &[(&str, &str)] = &[ ":save n :load n", "save the queue as a playlist / load one (:save! n replaces an existing one)", ), - ( - ":savealbum [n]", - "save album or current album results as a playlist", - ), - ( - ":saveresults [n]", - "save current results as a playlist", - ), ( ":append n :delpl n", "append a playlist to the queue / delete it", @@ -1291,10 +1281,7 @@ fn draw_collection_picker(f: &mut Frame, app: &App, area: Rect) { } }) .collect(); - lines.push(Line::styled( - " Enter select · s save playlist · j k move · Esc cancel", - dim(), - )); + lines.push(Line::styled(" Enter select · j k move · Esc cancel", dim())); f.render_widget(Clear, popup); f.render_widget( Paragraph::new(lines).block(