Added TUI

This commit is contained in:
Connor Johnstone
2026-03-02 23:08:13 -05:00
parent 97e5a8f5df
commit 59d0674d77
5 changed files with 388 additions and 23 deletions

View File

@@ -2,6 +2,7 @@ mod db;
mod filesystem;
mod lastfm;
mod metadata;
mod tui;
use std::collections::HashMap;
use std::env;
@@ -13,7 +14,7 @@ use rand::prelude::*;
fn usage(program: &str) -> ! {
eprintln!("Usage:");
eprintln!(" {program} index [-v] <directory>");
eprintln!(" {program} build [-v] [-n COUNT] <file>");
eprintln!(" {program} build [-v] [-n COUNT] [file]");
std::process::exit(1);
}
@@ -137,24 +138,11 @@ fn cmd_build(args: &[String]) {
}
}
if rest.len() != 1 {
eprintln!("Usage: {} build [-v] [-n COUNT] <file>", args[0]);
if rest.len() > 1 {
eprintln!("Usage: {} build [-v] [-n COUNT] [file]", args[0]);
std::process::exit(1);
}
let path = Path::new(rest[0].as_str());
let artist_mbid = match metadata::read_artist_mbid(path) {
Ok(Some(mbid)) => mbid,
Ok(None) => {
eprintln!("{}: no artist MBID found", path.display());
std::process::exit(1);
}
Err(e) => {
eprintln!("{}: could not read artist MBID: {e}", path.display());
std::process::exit(1);
}
};
dotenvy::dotenv().ok();
let api_key = env::var("LASTFM_API_KEY").unwrap_or_default();
if api_key.is_empty() {
@@ -165,12 +153,54 @@ fn cmd_build(args: &[String]) {
let conn = db::open("playlists.db").expect("failed to open database");
let lastfm = lastfm::LastfmClient::new(api_key);
let seed_name = metadata::read_artist_name(path)
.ok()
.flatten()
.unwrap_or_else(|| artist_mbid.clone());
let (artist_mbid, seed_name) = if let Some(file_arg) = rest.first() {
let path = Path::new(file_arg.as_str());
let mbid = match metadata::read_artist_mbid(path) {
Ok(Some(mbid)) => mbid,
Ok(None) => {
eprintln!("{}: no artist MBID found", path.display());
std::process::exit(1);
}
Err(e) => {
eprintln!("{}: could not read artist MBID: {e}", path.display());
std::process::exit(1);
}
};
let name = metadata::read_artist_name(path)
.ok()
.flatten()
.unwrap_or_else(|| mbid.clone());
(mbid, name)
} else {
let artists = match db::get_all_artists(&conn) {
Ok(a) => a,
Err(e) => {
eprintln!("DB error: {e}");
std::process::exit(1);
}
};
if artists.is_empty() {
eprintln!("No artists in database. Run 'index' first.");
std::process::exit(1);
}
match tui::run_artist_picker(&artists) {
Some(selection) => selection,
None => std::process::exit(0),
}
};
let similar = match db::get_available_similar_artists(&conn, &artist_mbid) {
build_playlist(&conn, &lastfm, &artist_mbid, &seed_name, count, verbose);
}
fn build_playlist(
conn: &rusqlite::Connection,
lastfm: &lastfm::LastfmClient,
artist_mbid: &str,
seed_name: &str,
count: usize,
verbose: bool,
) {
let similar = match db::get_available_similar_artists(conn, artist_mbid) {
Ok(a) => a,
Err(e) => {
eprintln!("DB error: {e}");
@@ -180,7 +210,7 @@ fn cmd_build(args: &[String]) {
// Seed artist + similar artists: (mbid, name, match_score)
let mut artists: Vec<(String, String, f64)> = vec![
(artist_mbid.clone(), seed_name, 1.0),
(artist_mbid.to_string(), seed_name.to_string(), 1.0),
];
artists.extend(similar);
@@ -188,7 +218,7 @@ fn cmd_build(args: &[String]) {
let mut playlist: Vec<(f64, f64, f64, String, String)> = Vec::new();
for (mbid, name, match_score) in &artists {
let local_tracks = match db::get_local_tracks_for_artist(&conn, mbid) {
let local_tracks = match db::get_local_tracks_for_artist(conn, mbid) {
Ok(t) => t,
Err(e) => {
eprintln!("DB error for {name}: {e}");