Added a popularity adjuster
This commit is contained in:
42
src/main.rs
42
src/main.rs
@@ -28,7 +28,7 @@ fn db_path() -> PathBuf {
|
||||
fn usage(program: &str) -> ! {
|
||||
eprintln!("Usage:");
|
||||
eprintln!(" {program} index [-v] [-f] <directory>");
|
||||
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::<u8>() {
|
||||
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<String> = 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,
|
||||
|
||||
14
src/mpd.rs
14
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<String> = 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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user