Add support for multiple music folders

This commit is contained in:
CDrummond 2022-03-14 11:24:16 +00:00
parent cf729aaef3
commit a6284c3761
5 changed files with 103 additions and 67 deletions

View File

@ -1,6 +1,8 @@
0.1.0 0.1.0
----- -----
1. Add support for analysing CUE files. 1. Add support for analysing CUE files.
2. Support up to 5 music folders (music, music_1, music_2, music_3, and
music_4).
0.0.2 0.0.2
----- -----

View File

@ -102,7 +102,8 @@ ignore=ignore.txt
The following items are supported: The following items are supported:
* `music` specifies the location of your music collection - e.g. `c:\Users\user\Music` * `music` specifies the location of your music collection - e.g. `c:\Users\user\Music`
for windows. This default to `Music` within the user's home folder. for windows. This default to `Music` within the user's home folder. Up to 4 other
music folders may be specified via `music_1`, `music_2`, `music_3`, and `music_4`
* `db` specifies the name and location of the database file used to store the * `db` specifies the name and location of the database file used to store the
analysis results. This will default to `bliss.db` in the current folder. analysis results. This will default to `bliss.db` in the current folder.
* `lms` specifies the hostname, or IP address, of your LMS server. This is used * `lms` specifies the hostname, or IP address, of your LMS server. This is used

View File

@ -287,54 +287,61 @@ pub fn analyse_new_cue_tracks(db:&db::Db, mpath: &PathBuf, cue_tracks:Vec<cue::C
Ok(()) Ok(())
} }
pub fn analyse_files(db_path: &str, mpath: &PathBuf, dry_run:bool, keep_old:bool, max_num_tracks:usize) { pub fn analyse_files(db_path: &str, mpaths: &Vec<PathBuf>, dry_run:bool, keep_old:bool, max_num_tracks:usize) {
let mut track_paths:Vec<String> = Vec::new();
let mut cue_tracks:Vec<cue::CueTrack> = Vec::new();
let mut db = db::Db::new(&String::from(db_path)); let mut db = db::Db::new(&String::from(db_path));
let cur = PathBuf::from(mpath);
db.init(); db.init();
log::info!("Looking for new tracks");
get_file_list(&mut db, mpath, &cur, &mut track_paths, &mut cue_tracks); for path in mpaths {
log::info!("Num new tracks: {}", track_paths.len()); let mpath = path.clone();
if !cue_tracks.is_empty() { let cur = path.clone();
log::info!("Num new cue tracks: {}", cue_tracks.len()); let mut track_paths:Vec<String> = Vec::new();
} let mut cue_tracks:Vec<cue::CueTrack> = Vec::new();
if !dry_run && max_num_tracks>0 && track_paths.len()>max_num_tracks {
log::info!("Only analysing {} tracks", max_num_tracks); log::info!("Looking for new tracks in {}", mpath.to_string_lossy());
track_paths.truncate(max_num_tracks); get_file_list(&mut db, &mpath, &cur, &mut track_paths, &mut cue_tracks);
} log::info!("Num new tracks: {}", track_paths.len());
if !dry_run && max_num_tracks>0 && cue_tracks.len()>max_num_tracks {
log::info!("Only analysing {} cue tracks", max_num_tracks);
cue_tracks.truncate(max_num_tracks);
}
if !keep_old {
db.remove_old(mpath, dry_run);
}
if !dry_run {
if track_paths.len()>0 {
match analyse_new_files(&db, mpath, track_paths) {
Ok(_) => { },
Err(e) => { log::error!("Analysis returned error: {}", e); }
}
} else {
log::info!("No new tracks to analyse");
}
if !cue_tracks.is_empty() { if !cue_tracks.is_empty() {
match analyse_new_cue_tracks(&db, mpath, cue_tracks) { log::info!("Num new cue tracks: {}", cue_tracks.len());
Ok(_) => { }, }
Err(e) => { log::error!("Cue analysis returned error: {}", e); } if !dry_run && max_num_tracks>0 && track_paths.len()>max_num_tracks {
log::info!("Only analysing {} tracks", max_num_tracks);
track_paths.truncate(max_num_tracks);
}
if !dry_run && max_num_tracks>0 && cue_tracks.len()>max_num_tracks {
log::info!("Only analysing {} cue tracks", max_num_tracks);
cue_tracks.truncate(max_num_tracks);
}
if !dry_run {
if track_paths.len()>0 {
match analyse_new_files(&db, &mpath, track_paths) {
Ok(_) => { },
Err(e) => { log::error!("Analysis returned error: {}", e); }
}
} else {
log::info!("No new tracks to analyse");
}
if !cue_tracks.is_empty() {
match analyse_new_cue_tracks(&db, &mpath, cue_tracks) {
Ok(_) => { },
Err(e) => { log::error!("Cue analysis returned error: {}", e); }
}
} }
} }
} }
if !keep_old {
db.remove_old(mpaths, dry_run);
}
db.close(); db.close();
} }
pub fn read_tags(db_path: &str, mpath: &PathBuf) { pub fn read_tags(db_path: &str, mpaths: &Vec<PathBuf>) {
let db = db::Db::new(&String::from(db_path)); let db = db::Db::new(&String::from(db_path));
db.init(); db.init();
db.update_tags(&mpath); db.update_tags(&mpaths);
db.close(); db.close();
} }

View File

@ -10,7 +10,7 @@ use bliss_audio::{Analysis, AnalysisIndex};
use indicatif::{ProgressBar, ProgressStyle}; use indicatif::{ProgressBar, ProgressStyle};
use rusqlite::{Connection, params}; use rusqlite::{Connection, params};
use std::convert::TryInto; use std::convert::TryInto;
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use std::process; use std::process;
use crate::cue; use crate::cue;
use crate::tags; use crate::tags;
@ -150,7 +150,7 @@ impl Db {
} }
} }
pub fn remove_old(&self, mpath:&Path, dry_run:bool) { pub fn remove_old(&self, mpaths: &Vec<PathBuf>, dry_run:bool) {
log::info!("Looking for non-existant tracks"); log::info!("Looking for non-existant tracks");
let mut stmt = self.conn.prepare("SELECT File FROM Tracks;").unwrap(); let mut stmt = self.conn.prepare("SELECT File FROM Tracks;").unwrap();
let track_iter = stmt.query_map([], |row| { let track_iter = stmt.query_map([], |row| {
@ -169,13 +169,23 @@ impl Db {
if cfg!(windows) { if cfg!(windows) {
db_path = db_path.replace("/", "\\"); db_path = db_path.replace("/", "\\");
} }
let path = mpath.join(PathBuf::from(db_path.clone())); let mut exists = false;
//log::debug!("Check if '{}' exists.", path.to_string_lossy());
if !path.exists() { for mpath in mpaths {
let path = mpath.join(PathBuf::from(db_path.clone()));
//log::debug!("Check if '{}' exists.", path.to_string_lossy());
if path.exists() {
exists = true;
break;
}
}
if !exists {
to_remove.push(orig_path); to_remove.push(orig_path);
} }
} }
let num_to_remove = to_remove.len(); let num_to_remove = to_remove.len();
log::info!("Num non-existant tracks: {}", num_to_remove); log::info!("Num non-existant tracks: {}", num_to_remove);
if !dry_run && num_to_remove>0 { if !dry_run && num_to_remove>0 {
@ -207,7 +217,7 @@ impl Db {
count count
} }
pub fn update_tags(&self, mpath:&PathBuf) { pub fn update_tags(&self, mpaths: &Vec<PathBuf>) {
let total = self.get_track_count(); let total = self.get_track_count();
if total>0 { if total>0 {
let pb = ProgressBar::new(total.try_into().unwrap()); let pb = ProgressBar::new(total.try_into().unwrap());
@ -242,15 +252,22 @@ impl Db {
duration:dbtags.duration duration:dbtags.duration
}; };
pb.set_message(format!("{}", dbtags.file)); pb.set_message(format!("{}", dbtags.file));
let path = String::from(mpath.join(&dbtags.file).to_string_lossy());
let ftags = tags::read(&path); for mpath in mpaths {
if ftags.title.is_empty() && ftags.artist.is_empty() && ftags.album_artist.is_empty() && ftags.album.is_empty() && ftags.genre.is_empty() { let track_path = mpath.join(&dbtags.file);
log::error!("Failed to read tags of '{}'", dbtags.file); if track_path.exists() {
} else if ftags.duration!=dtags.duration || ftags.title!=dtags.title || ftags.artist!=dtags.artist || ftags.album_artist!=dtags.album_artist || ftags.album!=dtags.album || ftags.genre!=dtags.genre { let path = String::from(track_path.to_string_lossy());
match self.conn.execute("UPDATE Tracks SET Title=?, Artist=?, AlbumArtist=?, Album=?, Genre=?, Duration=? WHERE rowid=?;", let ftags = tags::read(&path);
params![ftags.title, ftags.artist, ftags.album_artist, ftags.album, ftags.genre, ftags.duration, dbtags.rowid]) { if ftags.title.is_empty() && ftags.artist.is_empty() && ftags.album_artist.is_empty() && ftags.album.is_empty() && ftags.genre.is_empty() {
Ok(_) => { updated += 1; }, log::error!("Failed to read tags of '{}'", dbtags.file);
Err(e) => { log::error!("Failed to update tags of '{}'. {}", dbtags.file, e); } } else if ftags.duration!=dtags.duration || ftags.title!=dtags.title || ftags.artist!=dtags.artist || ftags.album_artist!=dtags.album_artist || ftags.album!=dtags.album || ftags.genre!=dtags.genre {
match self.conn.execute("UPDATE Tracks SET Title=?, Artist=?, AlbumArtist=?, Album=?, Genre=?, Duration=? WHERE rowid=?;",
params![ftags.title, ftags.artist, ftags.album_artist, ftags.album, ftags.genre, ftags.duration, dbtags.rowid]) {
Ok(_) => { updated += 1; },
Err(e) => { log::error!("Failed to update tags of '{}'. {}", dbtags.file, e); }
}
}
break;
} }
} }
} }

View File

@ -34,6 +34,7 @@ fn main() {
let mut task = "".to_string(); let mut task = "".to_string();
let mut lms_host = "127.0.0.1".to_string(); let mut lms_host = "127.0.0.1".to_string();
let mut max_num_tracks:usize = 0; let mut max_num_tracks:usize = 0;
let mut music_paths:Vec<PathBuf> = Vec::new();
match dirs::home_dir() { match dirs::home_dir() {
Some(path) => { music_path = String::from(path.join("Music").to_string_lossy()); } Some(path) => { music_path = String::from(path.join("Music").to_string_lossy()); }
@ -90,20 +91,23 @@ fn main() {
let mut config = Ini::new(); let mut config = Ini::new();
match config.load(&config_file) { match config.load(&config_file) {
Ok(_) => { Ok(_) => {
match config.get(TOP_LEVEL_INI_TAG, "music") { let path_keys: [&str; 5] = ["music", "music_1", "music_2", "music_3", "music_4"];
Some(val) => { music_path = val; }, for key in &path_keys {
None => { } match config.get(TOP_LEVEL_INI_TAG, key) {
Some(val) => { music_paths.push(PathBuf::from(&val)); },
None => { }
}
} }
match config.get(TOP_LEVEL_INI_TAG, "db") { match config.get(TOP_LEVEL_INI_TAG, "db") {
Some(val) => { db_path = val; }, Some(val) => { db_path = val; },
None => { } None => { }
} }
match config.get(TOP_LEVEL_INI_TAG, "lms") { match config.get(TOP_LEVEL_INI_TAG, "lms") {
Some(val) => { lms_host = val; }, Some(val) => { lms_host = val; },
None => { } None => { }
} }
match config.get(TOP_LEVEL_INI_TAG, "ignore") { match config.get(TOP_LEVEL_INI_TAG, "ignore") {
Some(val) => { ignore_file = val; }, Some(val) => { ignore_file = val; },
None => { } None => { }
} }
}, },
@ -115,6 +119,10 @@ fn main() {
} }
} }
if music_paths.is_empty() {
music_paths.push(PathBuf::from(&music_path));
}
if task.eq_ignore_ascii_case("stopmixer") { if task.eq_ignore_ascii_case("stopmixer") {
upload::stop_mixer(&lms_host); upload::stop_mixer(&lms_host);
} else { } else {
@ -137,18 +145,19 @@ fn main() {
process::exit(-1); process::exit(-1);
} }
} else { } else {
let mpath = PathBuf::from(&music_path); for mpath in &music_paths {
if !mpath.exists() { if !mpath.exists() {
log::error!("Music path ({}) does not exist", music_path); log::error!("Music path ({}) does not exist", mpath.to_string_lossy());
process::exit(-1); process::exit(-1);
} }
if !mpath.is_dir() { if !mpath.is_dir() {
log::error!("Music path ({}) is not a directory", music_path); log::error!("Music path ({}) is not a directory", mpath.to_string_lossy());
process::exit(-1); process::exit(-1);
}
} }
if task.eq_ignore_ascii_case("tags") { if task.eq_ignore_ascii_case("tags") {
analyse::read_tags(&db_path, &mpath); analyse::read_tags(&db_path, &music_paths);
} else if task.eq_ignore_ascii_case("ignore") { } else if task.eq_ignore_ascii_case("ignore") {
let ignore_path = PathBuf::from(&ignore_file); let ignore_path = PathBuf::from(&ignore_file);
if !ignore_path.exists() { if !ignore_path.exists() {
@ -161,7 +170,7 @@ fn main() {
} }
analyse::update_ignore(&db_path, &ignore_path); analyse::update_ignore(&db_path, &ignore_path);
} else { } else {
analyse::analyse_files(&db_path, &mpath, dry_run, keep_old, max_num_tracks); analyse::analyse_files(&db_path, &music_paths, dry_run, keep_old, max_num_tracks);
} }
} }
} }