diff --git a/src/main.rs b/src/main.rs index 025b0bd..0651048 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,7 @@ fn db_path() -> PathBuf { fn usage(program: &str) -> ! { eprintln!("Usage:"); eprintln!(" {program} index [-v] [-f] "); - eprintln!(" {program} build [-v] [-m|-a] [-s|-r] [-n COUNT] [file]"); + eprintln!(" {program} build [-v] [-m|-a] [-s|-r] [-n COUNT] [-p LEVEL] [file]"); std::process::exit(1); } @@ -154,8 +154,9 @@ fn cmd_build(args: &[String]) { std::process::exit(1); } - // Parse -n COUNT + // Parse -n COUNT and -p LEVEL let mut count: usize = 20; + let mut popularity_bias: u8 = 5; let mut rest: Vec<&String> = Vec::new(); let mut iter = args.iter().skip(2); while let Some(arg) = iter.next() { @@ -175,13 +176,27 @@ fn cmd_build(args: &[String]) { std::process::exit(1); } } + } else if arg == "-p" { + match iter.next() { + Some(val) => match val.parse::() { + Ok(n) if n <= 10 => popularity_bias = n, + _ => { + eprintln!("Error: -p requires an integer 0-10"); + std::process::exit(1); + } + }, + None => { + eprintln!("Error: -p requires a value"); + std::process::exit(1); + } + } } else { rest.push(arg); } } if rest.len() > 1 { - eprintln!("Usage: {} build [-v] [-m|-a] [-s|-r] [-n COUNT] [file]", args[0]); + eprintln!("Usage: {} build [-v] [-m|-a] [-s|-r] [-n COUNT] [-p LEVEL] [file]", args[0]); std::process::exit(1); } @@ -224,9 +239,13 @@ fn cmd_build(args: &[String]) { } }; - build_playlist(&conn, &artist_mbid, &seed_name, count, verbose, mpd, airsonic, shuffle, random); + build_playlist(&conn, &artist_mbid, &seed_name, count, verbose, mpd, airsonic, shuffle, random, popularity_bias); } +const POPULARITY_EXPONENTS: [f64; 11] = [ + 0.0, 0.03, 0.08, 0.15, 0.25, 0.35, 0.55, 0.85, 1.30, 1.80, 2.50, +]; + fn build_playlist( conn: &rusqlite::Connection, artist_mbid: &str, @@ -237,6 +256,7 @@ fn build_playlist( airsonic: bool, shuffle: bool, random: bool, + popularity_bias: u8, ) { let similar = match db::get_available_similar_artists(conn, artist_mbid) { Ok(a) => a, @@ -302,13 +322,13 @@ fn build_playlist( let Some(playcount) = playcount else { continue }; let popularity = if playcount > 0 { - (playcount as f64 / max_playcount as f64).powf(0.15) + (playcount as f64 / max_playcount as f64).powf(POPULARITY_EXPONENTS[popularity_bias as usize]) } else { 0.0 }; let similarity = (match_score.exp()) / std::f64::consts::E; - let total = similarity * (1.0 + popularity); + let total = similarity * popularity; playlist.push((total, popularity, similarity, name.clone(), track_path.clone())); } } @@ -337,12 +357,10 @@ fn build_playlist( let tracks: Vec = selected.iter().map(|(_, _, p)| p.clone()).collect(); - let local_dir = env::var("LOCAL_MUSIC_DIR").unwrap_or_default(); - if mpd { - let mpd_dir = env::var("MPD_MUSIC_DIR").unwrap_or_default(); - if local_dir.is_empty() || mpd_dir.is_empty() { - eprintln!("Error: LOCAL_MUSIC_DIR and MPD_MUSIC_DIR must be set"); + let music_dir = env::var("MPD_MUSIC_DIR").unwrap_or_default(); + if music_dir.is_empty() { + eprintln!("Error: MPD_MUSIC_DIR not set"); std::process::exit(1); } let mut client = match mpd::MpdClient::connect() { @@ -352,7 +370,7 @@ fn build_playlist( std::process::exit(1); } }; - client.queue_playlist(&tracks, &local_dir, &mpd_dir); + client.queue_playlist(&tracks, &music_dir); } else if airsonic { let client = match airsonic::AirsonicClient::new() { Ok(c) => c, diff --git a/src/mpd.rs b/src/mpd.rs index 0d80472..1d8de72 100644 --- a/src/mpd.rs +++ b/src/mpd.rs @@ -83,7 +83,7 @@ impl MpdClient { } } - pub fn queue_playlist(&mut self, tracks: &[String], local_dir: &str, mpd_dir: &str) { + pub fn queue_playlist(&mut self, tracks: &[String], music_dir: &str) { if let Err(e) = self.send_command("clear") { eprintln!("MPD clear: {e}"); return; @@ -92,7 +92,7 @@ impl MpdClient { let mut failed: Vec = Vec::new(); for track in tracks { - let uri = Self::track_to_uri(track, local_dir, mpd_dir); + let uri = Self::track_to_uri(track, music_dir); let escaped = uri.replace('\\', "\\\\").replace('"', "\\\""); if self.send_command(&format!("add \"{escaped}\"")).is_err() { failed.push(uri.to_string()); @@ -117,12 +117,10 @@ impl MpdClient { } } - fn track_to_uri(track: &str, local_dir: &str, mpd_dir: &str) -> String { - let relative = track - .strip_prefix(local_dir) + fn track_to_uri<'a>(track: &'a str, music_dir: &str) -> &'a str { + track + .strip_prefix(music_dir) .map(|p| p.trim_start_matches('/')) - .unwrap_or(track); - let mpd_base = mpd_dir.trim_end_matches('/'); - format!("{mpd_base}/{relative}") + .unwrap_or(track) } }