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

@ -1,6 +1,8 @@
0.1.0
-----
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
-----

@ -102,7 +102,8 @@ ignore=ignore.txt
The following items are supported:
* `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
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

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

@ -10,7 +10,7 @@ use bliss_audio::{Analysis, AnalysisIndex};
use indicatif::{ProgressBar, ProgressStyle};
use rusqlite::{Connection, params};
use std::convert::TryInto;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::process;
use crate::cue;
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");
let mut stmt = self.conn.prepare("SELECT File FROM Tracks;").unwrap();
let track_iter = stmt.query_map([], |row| {
@ -169,13 +169,23 @@ impl Db {
if cfg!(windows) {
db_path = db_path.replace("/", "\\");
}
let path = mpath.join(PathBuf::from(db_path.clone()));
//log::debug!("Check if '{}' exists.", path.to_string_lossy());
let mut exists = false;
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);
}
}
let num_to_remove = to_remove.len();
log::info!("Num non-existant tracks: {}", num_to_remove);
if !dry_run && num_to_remove>0 {
@ -207,7 +217,7 @@ impl Db {
count
}
pub fn update_tags(&self, mpath:&PathBuf) {
pub fn update_tags(&self, mpaths: &Vec<PathBuf>) {
let total = self.get_track_count();
if total>0 {
let pb = ProgressBar::new(total.try_into().unwrap());
@ -242,15 +252,22 @@ impl Db {
duration:dbtags.duration
};
pb.set_message(format!("{}", dbtags.file));
let path = String::from(mpath.join(&dbtags.file).to_string_lossy());
let ftags = tags::read(&path);
if ftags.title.is_empty() && ftags.artist.is_empty() && ftags.album_artist.is_empty() && ftags.album.is_empty() && ftags.genre.is_empty() {
log::error!("Failed to read tags of '{}'", dbtags.file);
} 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); }
for mpath in mpaths {
let track_path = mpath.join(&dbtags.file);
if track_path.exists() {
let path = String::from(track_path.to_string_lossy());
let ftags = tags::read(&path);
if ftags.title.is_empty() && ftags.artist.is_empty() && ftags.album_artist.is_empty() && ftags.album.is_empty() && ftags.genre.is_empty() {
log::error!("Failed to read tags of '{}'", dbtags.file);
} 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;
}
}
}

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