Added tracks to db, beginning to get playlist creation
This commit is contained in:
33
src/db.rs
33
src/db.rs
@@ -15,6 +15,11 @@ pub fn open(path: &str) -> Result<Connection, rusqlite::Error> {
|
|||||||
similar_name TEXT NOT NULL,
|
similar_name TEXT NOT NULL,
|
||||||
match_score REAL NOT NULL,
|
match_score REAL NOT NULL,
|
||||||
PRIMARY KEY (artist_mbid, similar_name)
|
PRIMARY KEY (artist_mbid, similar_name)
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS tracks (
|
||||||
|
path TEXT PRIMARY KEY,
|
||||||
|
artist_mbid TEXT NOT NULL REFERENCES artists(mbid),
|
||||||
|
recording_mbid TEXT
|
||||||
);",
|
);",
|
||||||
)?;
|
)?;
|
||||||
Ok(conn)
|
Ok(conn)
|
||||||
@@ -28,6 +33,34 @@ pub fn artist_exists(conn: &Connection, mbid: &str) -> Result<bool, rusqlite::Er
|
|||||||
Ok(count > 0)
|
Ok(count > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_available_similar_artists(
|
||||||
|
conn: &Connection,
|
||||||
|
artist_mbid: &str,
|
||||||
|
) -> Result<Vec<(String, f64)>, rusqlite::Error> {
|
||||||
|
let mut stmt = conn.prepare(
|
||||||
|
"SELECT sa.similar_name, sa.match_score
|
||||||
|
FROM similar_artists sa
|
||||||
|
JOIN artists a ON a.mbid = sa.similar_mbid
|
||||||
|
WHERE sa.artist_mbid = ?1
|
||||||
|
ORDER BY sa.match_score DESC",
|
||||||
|
)?;
|
||||||
|
let rows = stmt.query_map([artist_mbid], |row| Ok((row.get(0)?, row.get(1)?)))?;
|
||||||
|
rows.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_track(
|
||||||
|
conn: &Connection,
|
||||||
|
path: &str,
|
||||||
|
artist_mbid: &str,
|
||||||
|
recording_mbid: Option<&str>,
|
||||||
|
) -> Result<(), rusqlite::Error> {
|
||||||
|
conn.execute(
|
||||||
|
"INSERT OR IGNORE INTO tracks (path, artist_mbid, recording_mbid) VALUES (?1, ?2, ?3)",
|
||||||
|
rusqlite::params![path, artist_mbid, recording_mbid],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn insert_artist_with_similar(
|
pub fn insert_artist_with_similar(
|
||||||
conn: &Connection,
|
conn: &Connection,
|
||||||
mbid: &str,
|
mbid: &str,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ impl LastfmClient {
|
|||||||
artist_mbid: &str,
|
artist_mbid: &str,
|
||||||
) -> Result<Vec<SimilarArtist>, Box<dyn std::error::Error>> {
|
) -> Result<Vec<SimilarArtist>, Box<dyn std::error::Error>> {
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}?method=artist.getSimilar&mbid={}&api_key={}&format=json",
|
"{}?method=artist.getSimilar&mbid={}&api_key={}&limit=500&format=json",
|
||||||
BASE_URL, artist_mbid, self.api_key
|
BASE_URL, artist_mbid, self.api_key
|
||||||
);
|
);
|
||||||
let body: String = ureq::get(&url).call()?.body_mut().read_to_string()?;
|
let body: String = ureq::get(&url).call()?.body_mut().read_to_string()?;
|
||||||
|
|||||||
106
src/main.rs
106
src/main.rs
@@ -6,12 +6,32 @@ mod metadata;
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
fn usage(program: &str) -> ! {
|
||||||
|
eprintln!("Usage:");
|
||||||
|
eprintln!(" {program} index [-v] <directory>");
|
||||||
|
eprintln!(" {program} build <file>");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args: Vec<String> = env::args().collect();
|
let args: Vec<String> = env::args().collect();
|
||||||
let verbose = args.iter().any(|a| a == "-v");
|
|
||||||
let rest: Vec<&String> = args.iter().skip(1).filter(|a| *a != "-v").collect();
|
|
||||||
|
|
||||||
if rest.len() != 2 || rest[0] != "index" {
|
if args.len() < 2 {
|
||||||
|
usage(&args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
match args[1].as_str() {
|
||||||
|
"index" => cmd_index(&args),
|
||||||
|
"build" => cmd_build(&args),
|
||||||
|
_ => usage(&args[0]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_index(args: &[String]) {
|
||||||
|
let verbose = args.iter().any(|a| a == "-v");
|
||||||
|
let rest: Vec<&String> = args.iter().skip(2).filter(|a| *a != "-v").collect();
|
||||||
|
|
||||||
|
if rest.len() != 1 {
|
||||||
eprintln!("Usage: {} index [-v] <directory>", args[0]);
|
eprintln!("Usage: {} index [-v] <directory>", args[0]);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
@@ -26,7 +46,7 @@ fn main() {
|
|||||||
|
|
||||||
let conn = db::open("playlists.db").expect("failed to open database");
|
let conn = db::open("playlists.db").expect("failed to open database");
|
||||||
let lastfm = lastfm::LastfmClient::new(api_key);
|
let lastfm = lastfm::LastfmClient::new(api_key);
|
||||||
let dir = Path::new(rest[1].as_str());
|
let dir = Path::new(rest[0].as_str());
|
||||||
|
|
||||||
for path in filesystem::walk_music_files(dir) {
|
for path in filesystem::walk_music_files(dir) {
|
||||||
let artist_mbid = match metadata::read_artist_mbid(&path) {
|
let artist_mbid = match metadata::read_artist_mbid(&path) {
|
||||||
@@ -38,6 +58,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let recording_mbid = metadata::read_track_mbid(&path).ok().flatten();
|
||||||
|
|
||||||
let already_indexed = match db::artist_exists(&conn, &artist_mbid) {
|
let already_indexed = match db::artist_exists(&conn, &artist_mbid) {
|
||||||
Ok(exists) => exists,
|
Ok(exists) => exists,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -49,29 +71,69 @@ fn main() {
|
|||||||
let artist_name = metadata::read_artist_name(&path).ok().flatten();
|
let artist_name = metadata::read_artist_name(&path).ok().flatten();
|
||||||
let display_name = artist_name.as_deref().unwrap_or(&artist_mbid);
|
let display_name = artist_name.as_deref().unwrap_or(&artist_mbid);
|
||||||
|
|
||||||
if already_indexed {
|
if !already_indexed {
|
||||||
if verbose {
|
if verbose {
|
||||||
println!("Skipping {display_name} (already indexed)");
|
println!("Indexing {display_name}...");
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if verbose {
|
match lastfm.get_similar_artists(&artist_mbid) {
|
||||||
println!("Indexing {display_name}...");
|
Ok(similar) => {
|
||||||
}
|
if let Err(e) = db::insert_artist_with_similar(
|
||||||
|
&conn,
|
||||||
match lastfm.get_similar_artists(&artist_mbid) {
|
&artist_mbid,
|
||||||
Ok(similar) => {
|
artist_name.as_deref(),
|
||||||
if let Err(e) = db::insert_artist_with_similar(
|
&similar,
|
||||||
&conn,
|
) {
|
||||||
&artist_mbid,
|
eprintln!("DB error inserting artist {artist_mbid}: {e}");
|
||||||
artist_name.as_deref(),
|
continue;
|
||||||
&similar,
|
}
|
||||||
) {
|
}
|
||||||
eprintln!("DB error inserting artist {artist_mbid}: {e}");
|
Err(e) => {
|
||||||
|
eprintln!("Last.fm error for {artist_mbid}: {e}");
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("Last.fm error for {artist_mbid}: {e}"),
|
} else if verbose {
|
||||||
|
println!("Skipping {display_name} (already indexed)");
|
||||||
|
}
|
||||||
|
|
||||||
|
let path_str = path.to_string_lossy();
|
||||||
|
if let Err(e) = db::insert_track(&conn, &path_str, &artist_mbid, recording_mbid.as_deref()) {
|
||||||
|
eprintln!("DB error inserting track {}: {e}", path.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_build(args: &[String]) {
|
||||||
|
if args.len() != 3 {
|
||||||
|
eprintln!("Usage: {} build <file>", args[0]);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = Path::new(&args[2]);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let conn = db::open("playlists.db").expect("failed to open database");
|
||||||
|
|
||||||
|
match db::get_available_similar_artists(&conn, &artist_mbid) {
|
||||||
|
Ok(artists) => {
|
||||||
|
for (name, score) in &artists {
|
||||||
|
println!("{name} ({score:.4})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("DB error: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,3 +56,14 @@ pub fn read_artist_mbid(path: &Path) -> Result<Option<String>, lofty::error::Lof
|
|||||||
|
|
||||||
Ok(tag.get_string(ItemKey::MusicBrainzArtistId).map(String::from))
|
Ok(tag.get_string(ItemKey::MusicBrainzArtistId).map(String::from))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extract the MusicBrainz recording ID from a music file.
|
||||||
|
pub fn read_track_mbid(path: &Path) -> Result<Option<String>, lofty::error::LoftyError> {
|
||||||
|
let tagged_file = lofty::read_from_path(path)?;
|
||||||
|
|
||||||
|
let Some(tag) = tagged_file.primary_tag().or_else(|| tagged_file.first_tag()) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(tag.get_string(ItemKey::MusicBrainzRecordingId).map(String::from))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user