From e0a6759af59528ff9a60d6d937fd2756f0327b50 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Tue, 22 Mar 2022 17:02:08 -0400 Subject: [PATCH 01/10] fmt Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com> --- src/analyse.rs | 229 +++++++++++++++++++++++++++++++------------------ src/cue.rs | 58 +++++++------ src/db.rs | 198 ++++++++++++++++++++++++------------------ src/main.rs | 138 +++++++++++++++++++++-------- src/tags.rs | 77 +++++++++-------- src/upload.rs | 108 +++++++++++------------ 6 files changed, 495 insertions(+), 313 deletions(-) diff --git a/src/analyse.rs b/src/analyse.rs index 28d2d9d..771b6ce 100644 --- a/src/analyse.rs +++ b/src/analyse.rs @@ -1,3 +1,6 @@ +use crate::cue; +use crate::db; +use crate::tags; /** * Analyse music with Bliss * @@ -5,32 +8,34 @@ * GPLv3 license. * **/ - -use anyhow::{Result}; +use anyhow::Result; use bliss_audio::{library::analyze_paths_streaming, BlissResult, Song}; use hhmmss::Hhmmss; use indicatif::{ProgressBar, ProgressStyle}; +use num_cpus; use std::convert::TryInto; use std::fs; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; -use std::time::Duration; use std::sync::mpsc; use std::sync::mpsc::{Receiver, Sender}; use std::thread; +use std::time::Duration; use subprocess::{Exec, NullFile}; use tempdir::TempDir; -use num_cpus; -use crate::cue; -use crate::db; -use crate::tags; -const DONT_ANALYSE:&str = ".notmusic"; -const MAX_ERRORS_TO_SHOW:usize = 100; -const MAX_TAG_ERRORS_TO_SHOW:usize = 50; +const DONT_ANALYSE: &str = ".notmusic"; +const MAX_ERRORS_TO_SHOW: usize = 100; +const MAX_TAG_ERRORS_TO_SHOW: usize = 50; -fn get_file_list(db:&mut db::Db, mpath:&PathBuf, path:&PathBuf, track_paths:&mut Vec, cue_tracks:&mut Vec) { +fn get_file_list( + db: &mut db::Db, + mpath: &PathBuf, + path: &PathBuf, + track_paths: &mut Vec, + cue_tracks: &mut Vec, +) { if path.is_dir() { match path.read_dir() { Ok(items) => { @@ -42,37 +47,57 @@ fn get_file_list(db:&mut db::Db, mpath:&PathBuf, path:&PathBuf, track_paths:&mut let mut check = pb.clone(); check.push(PathBuf::from(DONT_ANALYSE)); if check.exists() { - log::info!("Skipping '{}', found '{}'", pb.to_string_lossy(), DONT_ANALYSE); + log::info!( + "Skipping '{}', found '{}'", + pb.to_string_lossy(), + DONT_ANALYSE + ); } else { - get_file_list(db, mpath, &entry.path(), track_paths, cue_tracks); + get_file_list( + db, + mpath, + &entry.path(), + track_paths, + cue_tracks, + ); } } else if entry.path().is_file() { let e = pb.extension(); if e.is_some() { let ext = e.unwrap().to_string_lossy(); - if ext=="m4a" || ext=="mp3" || ext=="ogg" || ext=="flac" || ext=="opus" { + if ext == "m4a" + || ext == "mp3" + || ext == "ogg" + || ext == "flac" + || ext == "opus" + { match pb.strip_prefix(mpath) { Ok(stripped) => { let mut cue_file = pb.clone(); cue_file.set_extension("cue"); if cue_file.exists() { // Found a CUE file, try to parse and then check if tracks exists in DB - let this_cue_tracks = cue::parse(&pb, &cue_file); + let this_cue_tracks = + cue::parse(&pb, &cue_file); for track in this_cue_tracks { match track.track_path.strip_prefix(mpath) { Ok(tstripped) => { let spb = tstripped.to_path_buf(); - let sname = String::from(spb.to_string_lossy()); + let sname = String::from( + spb.to_string_lossy(), + ); match db.get_rowid(&sname) { Ok(id) => { - if id<=0 { - cue_tracks.push(track.clone()); + if id <= 0 { + cue_tracks.push( + track.clone(), + ); } - }, - Err(_) => { } + } + Err(_) => {} } - }, - Err(_) => { } + } + Err(_) => {} } } } else { @@ -80,30 +105,32 @@ fn get_file_list(db:&mut db::Db, mpath:&PathBuf, path:&PathBuf, track_paths:&mut let sname = String::from(spb.to_string_lossy()); match db.get_rowid(&sname) { Ok(id) => { - if id<=0 { - track_paths.push(String::from(pb.to_string_lossy())); + if id <= 0 { + track_paths.push(String::from( + pb.to_string_lossy(), + )); } - }, - Err(_) => { } + } + Err(_) => {} } } - }, - Err(_) => { } + } + Err(_) => {} } } } } - }, - Err(_) => { } + } + Err(_) => {} } } - }, - Err(_) => { } + } + Err(_) => {} } } } -pub fn analyse_new_files(db:&db::Db, mpath: &PathBuf, track_paths:Vec) -> Result<()> { +pub fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec) -> Result<()> { let total = track_paths.len(); let pb = ProgressBar::new(total.try_into().unwrap()); let style = ProgressStyle::default_bar() @@ -113,8 +140,8 @@ pub fn analyse_new_files(db:&db::Db, mpath: &PathBuf, track_paths:Vec) - let results = analyze_paths_streaming(track_paths)?; let mut analysed = 0; - let mut failed:Vec = Vec::new(); - let mut tag_error:Vec = Vec::new(); + let mut failed: Vec = Vec::new(); + let mut tag_error: Vec = Vec::new(); log::info!("Analysing new tracks"); for (path, result) in results { @@ -127,20 +154,28 @@ pub fn analyse_new_files(db:&db::Db, mpath: &PathBuf, track_paths:Vec) - Ok(track) => { let cpath = String::from(path); let meta = tags::read(&cpath); - if meta.title.is_empty() && meta.artist.is_empty() && meta.album.is_empty() && meta.genre.is_empty() { + if meta.title.is_empty() + && meta.artist.is_empty() + && meta.album.is_empty() + && meta.genre.is_empty() + { tag_error.push(sname.clone()); } db.add_track(&sname, &meta, &track.analysis); analysed += 1; - }, + } Err(e) => { failed.push(format!("{} - {}", sname, e)); } }; pb.inc(1); } - pb.finish_with_message(format!("{} Analysed. {} Failure(s).", analysed, failed.len())); + pb.finish_with_message(format!( + "{} Analysed. {} Failure(s).", + analysed, + failed.len() + )); if !failed.is_empty() { let total = failed.len(); failed.truncate(MAX_ERRORS_TO_SHOW); @@ -149,7 +184,7 @@ pub fn analyse_new_files(db:&db::Db, mpath: &PathBuf, track_paths:Vec) - for err in failed { log::error!(" {}", err); } - if total>MAX_ERRORS_TO_SHOW { + if total > MAX_ERRORS_TO_SHOW { log::error!(" + {} other(s)", total - MAX_ERRORS_TO_SHOW); } } @@ -161,14 +196,16 @@ pub fn analyse_new_files(db:&db::Db, mpath: &PathBuf, track_paths:Vec) - for err in tag_error { log::error!(" {}", err); } - if total>MAX_TAG_ERRORS_TO_SHOW { + if total > MAX_TAG_ERRORS_TO_SHOW { log::error!(" + {} other(s)", total - MAX_TAG_ERRORS_TO_SHOW); } } Ok(()) } -pub fn analyze_cue_streaming(tracks: Vec,) -> BlissResult)>> { +pub fn analyze_cue_streaming( + tracks: Vec, +) -> BlissResult)>> { let num_cpus = num_cpus::get(); let last_track_duration = Duration::new(cue::LAST_TRACK_DURATION, 0); @@ -209,29 +246,37 @@ pub fn analyze_cue_streaming(tracks: Vec,) -> BlissResult { }, - Err(e) => { log::error!("Failed to call ffmpeg. {}", e); } + match Exec::cmd("ffmpeg") + .arg("-i") + .arg(&audio_path) + .arg("-ss") + .arg(&cue_track.start.hhmmss()) + .arg("-t") + .arg(&cue_track.duration.hhmmss()) + .arg("-c") + .arg("copy") + .arg(String::from(tmp_file.to_string_lossy())) + .stderr(NullFile) + .join() + { + Ok(_) => {} + Err(e) => { + log::error!("Failed to call ffmpeg. {}", e); + } } - if ! cfg!(windows) { + if !cfg!(windows) { // ffmpeg seeks to break echo on terminal? 'stty echo' restores... match Exec::cmd("stty").arg("echo").join() { - Ok(_) => { }, - Err(_) => { } + Ok(_) => {} + Err(_) => {} } } if tmp_file.exists() { log::debug!("Analyzing '{}'", track_path); let song = Song::new(&tmp_file); - if cue_track.duration>=last_track_duration { + if cue_track.duration >= last_track_duration { // Last track, so read duration from temp file let mut cloned = cue_track.clone(); let meta = tags::read(&String::from(tmp_file.to_string_lossy())); @@ -241,15 +286,17 @@ pub fn analyze_cue_streaming(tracks: Vec,) -> BlissResult { }, - Err(_) => { } + Ok(_) => {} + Err(_) => {} } } else { log::error!("Failed to create temp file"); } } - }, - Err(e) => { log::error!("Failed to create temp folder. {}", e); } + } + Err(e) => { + log::error!("Failed to create temp folder. {}", e); + } } }); handles.push(child); @@ -258,7 +305,11 @@ pub fn analyze_cue_streaming(tracks: Vec,) -> BlissResult) -> Result<()> { +pub fn analyse_new_cue_tracks( + db: &db::Db, + mpath: &PathBuf, + cue_tracks: Vec, +) -> Result<()> { let total = cue_tracks.len(); let pb = ProgressBar::new(total.try_into().unwrap()); let style = ProgressStyle::default_bar() @@ -268,7 +319,7 @@ pub fn analyse_new_cue_tracks(db:&db::Db, mpath: &PathBuf, cue_tracks:Vec = Vec::new(); + let mut failed: Vec = Vec::new(); log::info!("Analysing new cue tracks"); for (track, result) in results { @@ -279,24 +330,28 @@ pub fn analyse_new_cue_tracks(db:&db::Db, mpath: &PathBuf, cue_tracks:Vec { let meta = db::Metadata { - title:track.title, - artist:track.artist, - album_artist:track.album_artist, - album:track.album, - genre:track.genre, - duration:track.duration.as_secs() as u32 + title: track.title, + artist: track.artist, + album_artist: track.album_artist, + album: track.album, + genre: track.genre, + duration: track.duration.as_secs() as u32, }; db.add_track(&sname, &meta, &song.analysis); analysed += 1; - }, + } Err(e) => { failed.push(format!("{} - {}", sname, e)); } }; pb.inc(1); } - pb.finish_with_message(format!("{} Analysed. {} Failure(s).", analysed, failed.len())); + pb.finish_with_message(format!( + "{} Analysed. {} Failure(s).", + analysed, + failed.len() + )); if !failed.is_empty() { let total = failed.len(); failed.truncate(MAX_ERRORS_TO_SHOW); @@ -305,14 +360,20 @@ pub fn analyse_new_cue_tracks(db:&db::Db, mpath: &PathBuf, cue_tracks:VecMAX_ERRORS_TO_SHOW { + if total > MAX_ERRORS_TO_SHOW { log::error!(" + {} other(s)", total - MAX_ERRORS_TO_SHOW); } } Ok(()) } -pub fn analyse_files(db_path: &str, mpaths: &Vec, dry_run:bool, keep_old:bool, max_num_tracks:usize) { +pub fn analyse_files( + db_path: &str, + mpaths: &Vec, + dry_run: bool, + keep_old: bool, + max_num_tracks: usize, +) { let mut db = db::Db::new(&String::from(db_path)); let mut track_count_left = max_num_tracks; @@ -325,10 +386,10 @@ pub fn analyse_files(db_path: &str, mpaths: &Vec, dry_run:bool, keep_ol for path in mpaths { let mpath = path.clone(); let cur = path.clone(); - let mut track_paths:Vec = Vec::new(); - let mut cue_tracks:Vec = Vec::new(); + let mut track_paths: Vec = Vec::new(); + let mut cue_tracks: Vec = Vec::new(); - if mpaths.len()>1 { + if mpaths.len() > 1 { log::info!("Looking for new tracks in {}", mpath.to_string_lossy()); } else { log::info!("Looking for new tracks"); @@ -350,18 +411,18 @@ pub fn analyse_files(db_path: &str, mpaths: &Vec, dry_run:bool, keep_ol } } } else { - if max_num_tracks>0 { - if track_paths.len()>track_count_left { + if max_num_tracks > 0 { + if track_paths.len() > track_count_left { log::info!("Only analysing {} tracks", track_count_left); track_paths.truncate(track_count_left); } track_count_left -= track_paths.len(); } - if max_num_tracks>0 { + if max_num_tracks > 0 { if track_count_left == 0 { cue_tracks.clear(); } else { - if cue_tracks.len()>track_count_left { + if cue_tracks.len() > track_count_left { log::info!("Only analysing {} cue tracks", track_count_left); cue_tracks.truncate(track_count_left); } @@ -371,8 +432,10 @@ pub fn analyse_files(db_path: &str, mpaths: &Vec, dry_run:bool, keep_ol if !track_paths.is_empty() { match analyse_new_files(&db, &mpath, track_paths) { - Ok(_) => { }, - Err(e) => { log::error!("Analysis returned error: {}", e); } + Ok(_) => {} + Err(e) => { + log::error!("Analysis returned error: {}", e); + } } } else { log::info!("No new tracks to analyse"); @@ -380,12 +443,14 @@ pub fn analyse_files(db_path: &str, mpaths: &Vec, dry_run:bool, keep_ol if !cue_tracks.is_empty() { match analyse_new_cue_tracks(&db, &mpath, cue_tracks) { - Ok(_) => { }, - Err(e) => { log::error!("Cue analysis returned error: {}", e); } + Ok(_) => {} + Err(e) => { + log::error!("Cue analysis returned error: {}", e); + } } } - if max_num_tracks>0 && track_count_left<=0 { + if max_num_tracks > 0 && track_count_left <= 0 { log::info!("Track limit reached"); break; } diff --git a/src/cue.rs b/src/cue.rs index 6353fec..09ad8e6 100644 --- a/src/cue.rs +++ b/src/cue.rs @@ -5,32 +5,31 @@ * GPLv3 license. * **/ - extern crate rcue; use rcue::parser::parse_from_file; use std::path::PathBuf; use std::time::Duration; -pub const MARKER:&str = ".CUE_TRACK."; -pub const LAST_TRACK_DURATION:u64 = 60*60*24; -const GENRE:&str = "GENRE"; +pub const MARKER: &str = ".CUE_TRACK."; +pub const LAST_TRACK_DURATION: u64 = 60 * 60 * 24; +const GENRE: &str = "GENRE"; #[derive(Clone)] pub struct CueTrack { - pub audio_path:PathBuf, - pub track_path:PathBuf, - pub title:String, - pub artist:String, - pub album:String, - pub album_artist:String, - pub genre:String, - pub start:Duration, - pub duration:Duration + pub audio_path: PathBuf, + pub track_path: PathBuf, + pub title: String, + pub artist: String, + pub album: String, + pub album_artist: String, + pub genre: String, + pub start: Duration, + pub duration: Duration, } -pub fn parse(audio_path:&PathBuf, cue_path:&PathBuf) -> Vec { - let mut resp:Vec = Vec::new(); +pub fn parse(audio_path: &PathBuf, cue_path: &PathBuf) -> Vec { + let mut resp: Vec = Vec::new(); match parse_from_file(&cue_path.to_string_lossy(), false) { Ok(cue) => { @@ -49,7 +48,12 @@ pub fn parse(audio_path:&PathBuf, cue_path:&PathBuf) -> Vec { Some((_, start)) => { let mut track_path = audio_path.clone(); let ext = audio_path.extension().unwrap().to_string_lossy(); - track_path.set_extension(format!("{}{}{}", ext, MARKER, resp.len()+1)); + track_path.set_extension(format!( + "{}{}{}", + ext, + MARKER, + resp.len() + 1 + )); let mut ctrack = CueTrack { audio_path: audio_path.clone(), track_path: track_path, @@ -68,24 +72,28 @@ pub fn parse(audio_path:&PathBuf, cue_path:&PathBuf) -> Vec { let mut path = audio_path.clone(); path.set_extension(""); match path.file_name() { - Some(n) => { ctrack.album = String::from(n.to_string_lossy()); } - None => { } + Some(n) => { + ctrack.album = String::from(n.to_string_lossy()); + } + None => {} } } resp.push(ctrack); - }, - None => { } + } + None => {} } } } } - }, - Err(e) => { log::error!("Failed to parse '{}'. {}", cue_path.to_string_lossy(), e);} + } + Err(e) => { + log::error!("Failed to parse '{}'. {}", cue_path.to_string_lossy(), e); + } } - for i in 0..(resp.len()-1) { + for i in 0..(resp.len() - 1) { let mut next_start = Duration::new(0, 0); - if let Some(next) = resp.get(i+1) { + if let Some(next) = resp.get(i + 1) { next_start = next.start; } if let Some(elem) = resp.get_mut(i) { @@ -93,4 +101,4 @@ pub fn parse(audio_path:&PathBuf, cue_path:&PathBuf) -> Vec { } } resp -} \ No newline at end of file +} diff --git a/src/db.rs b/src/db.rs index 90bd3a4..50d6c00 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,3 +1,5 @@ +use crate::cue; +use crate::tags; /** * Analyse music with Bliss * @@ -5,38 +7,35 @@ * GPLv3 license. * **/ - use bliss_audio::{Analysis, AnalysisIndex}; use indicatif::{ProgressBar, ProgressStyle}; -use rusqlite::{Connection, params}; +use rusqlite::{params, Connection}; use std::convert::TryInto; use std::path::PathBuf; use std::process; -use crate::cue; -use crate::tags; pub struct FileMetadata { - pub rowid:usize, - pub file:String, - pub title:Option, - pub artist:Option, - pub album_artist:Option, - pub album:Option, - pub genre:Option, - pub duration:u32 + pub rowid: usize, + pub file: String, + pub title: Option, + pub artist: Option, + pub album_artist: Option, + pub album: Option, + pub genre: Option, + pub duration: u32, } pub struct Metadata { - pub title:String, - pub artist:String, - pub album_artist:String, - pub album:String, - pub genre:String, - pub duration:u32 + pub title: String, + pub artist: String, + pub album_artist: String, + pub album: String, + pub genre: String, + pub duration: u32, } pub struct Db { - pub conn: Connection + pub conn: Connection, } impl Db { @@ -77,15 +76,20 @@ impl Db { Chroma8 real, Chroma9 real, Chroma10 real - );",[]) { - Ok(_) => { }, + );", + [], + ) { + Ok(_) => {} Err(_) => { log::error!("Failed to create DB table"); process::exit(-1); } } - match self.conn.execute("CREATE UNIQUE INDEX IF NOT EXISTS Tracks_idx ON Tracks(File)", []) { - Ok(_) => { }, + match self.conn.execute( + "CREATE UNIQUE INDEX IF NOT EXISTS Tracks_idx ON Tracks(File)", + [], + ) { + Ok(_) => {} Err(_) => { log::error!("Failed to create DB index"); process::exit(-1); @@ -95,8 +99,8 @@ impl Db { pub fn close(self) { match self.conn.close() { - Ok(_) => { }, - Err(_) => { } + Ok(_) => {} + Err(_) => {} } } @@ -105,11 +109,13 @@ impl Db { if cfg!(windows) { db_path = db_path.replace("\\", "/"); } - let mut stmt = self.conn.prepare("SELECT rowid FROM Tracks WHERE File=:path;")?; - let track_iter = stmt.query_map(&[(":path", &db_path)], |row| { - Ok(row.get(0)?) - }).unwrap(); - let mut rowid:usize = 0; + let mut stmt = self + .conn + .prepare("SELECT rowid FROM Tracks WHERE File=:path;")?; + let track_iter = stmt + .query_map(&[(":path", &db_path)], |row| Ok(row.get(0)?)) + .unwrap(); + let mut rowid: usize = 0; for tr in track_iter { rowid = tr.unwrap(); break; @@ -117,14 +123,14 @@ impl Db { Ok(rowid) } - pub fn add_track(&self, path: &String, meta: &Metadata, analysis:&Analysis) { + pub fn add_track(&self, path: &String, meta: &Metadata, analysis: &Analysis) { let mut db_path = path.clone(); if cfg!(windows) { db_path = db_path.replace("\\", "/"); } match self.get_rowid(&path) { Ok(id) => { - if id<=0 { + if id <= 0 { match self.conn.execute("INSERT INTO Tracks (File, Title, Artist, AlbumArtist, Album, Genre, Duration, Ignore, Tempo, Zcr, MeanSpectralCentroid, StdDevSpectralCentroid, MeanSpectralRolloff, StdDevSpectralRolloff, MeanSpectralFlatness, StdDevSpectralFlatness, MeanLoudness, StdDevLoudness, Chroma1, Chroma2, Chroma3, Chroma4, Chroma5, Chroma6, Chroma7, Chroma8, Chroma9, Chroma10) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);", params![db_path, meta.title, meta.artist, meta.album_artist, meta.album, meta.genre, meta.duration, 0, analysis[AnalysisIndex::Tempo], analysis[AnalysisIndex::Zcr], analysis[AnalysisIndex::MeanSpectralCentroid], analysis[AnalysisIndex::StdDeviationSpectralCentroid], analysis[AnalysisIndex::MeanSpectralRolloff], @@ -145,26 +151,24 @@ impl Db { Err(e) => { log::error!("Failed to update '{}' in database. {}", path, e); } } } - }, - Err(_) => { } + } + Err(_) => {} } } - pub fn remove_old(&self, mpaths: &Vec, dry_run:bool) { + pub fn remove_old(&self, mpaths: &Vec, 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| { - Ok((row.get(0)?,)) - }).unwrap(); - let mut to_remove:Vec = Vec::new(); + let track_iter = stmt.query_map([], |row| Ok((row.get(0)?,))).unwrap(); + let mut to_remove: Vec = Vec::new(); for tr in track_iter { - let mut db_path:String = tr.unwrap().0; + let mut db_path: String = tr.unwrap().0; let orig_path = db_path.clone(); match orig_path.find(cue::MARKER) { Some(s) => { db_path.truncate(s); - }, - None => { } + } + None => {} } if cfg!(windows) { db_path = db_path.replace("/", "\\"); @@ -188,7 +192,7 @@ impl Db { let num_to_remove = to_remove.len(); log::info!("Num non-existant tracks: {}", num_to_remove); - if num_to_remove>0 { + if num_to_remove > 0 { if dry_run { log::info!("The following need to be removed from database:"); for t in to_remove { @@ -198,9 +202,14 @@ impl Db { let count_before = self.get_track_count(); for t in to_remove { //log::debug!("Remove '{}'", t); - match self.conn.execute("DELETE FROM Tracks WHERE File = ?;", params![t]) { - Ok(_) => { }, - Err(e) => { log::error!("Failed to remove '{}' - {}", t, e) } + match self + .conn + .execute("DELETE FROM Tracks WHERE File = ?;", params![t]) + { + Ok(_) => {} + Err(e) => { + log::error!("Failed to remove '{}' - {}", t, e) + } } } let count_now = self.get_track_count(); @@ -213,10 +222,8 @@ impl Db { pub fn get_track_count(&self) -> usize { let mut stmt = self.conn.prepare("SELECT COUNT(*) FROM Tracks;").unwrap(); - let track_iter = stmt.query_map([], |row| { - Ok(row.get(0)?) - }).unwrap(); - let mut count:usize = 0; + let track_iter = stmt.query_map([], |row| Ok(row.get(0)?)).unwrap(); + let mut count: usize = 0; for tr in track_iter { count = tr.unwrap(); break; @@ -226,37 +233,41 @@ impl Db { pub fn update_tags(&self, mpaths: &Vec) { let total = self.get_track_count(); - if total>0 { + if total > 0 { let pb = ProgressBar::new(total.try_into().unwrap()); let style = ProgressStyle::default_bar() - .template("[{elapsed_precise}] [{bar:25}] {percent:>3}% {pos:>6}/{len:6} {wide_msg}") + .template( + "[{elapsed_precise}] [{bar:25}] {percent:>3}% {pos:>6}/{len:6} {wide_msg}", + ) .progress_chars("=> "); pb.set_style(style); let mut stmt = self.conn.prepare("SELECT rowid, File, Title, Artist, AlbumArtist, Album, Genre, Duration FROM Tracks ORDER BY File ASC;").unwrap(); - let track_iter = stmt.query_map([], |row| { - Ok(FileMetadata { - rowid: row.get(0)?, - file: row.get(1)?, - title: row.get(2)?, - artist: row.get(3)?, - album_artist: row.get(4)?, - album: row.get(5)?, - genre: row.get(6)?, - duration: row.get(7)?, + let track_iter = stmt + .query_map([], |row| { + Ok(FileMetadata { + rowid: row.get(0)?, + file: row.get(1)?, + title: row.get(2)?, + artist: row.get(3)?, + album_artist: row.get(4)?, + album: row.get(5)?, + genre: row.get(6)?, + duration: row.get(7)?, + }) }) - }).unwrap(); + .unwrap(); let mut updated = 0; for tr in track_iter { let dbtags = tr.unwrap(); if !dbtags.file.contains(cue::MARKER) { - let dtags = Metadata{ - title:dbtags.title.unwrap_or(String::new()), - artist:dbtags.artist.unwrap_or(String::new()), - album_artist:dbtags.album_artist.unwrap_or(String::new()), - album:dbtags.album.unwrap_or(String::new()), - genre:dbtags.genre.unwrap_or(String::new()), - duration:dbtags.duration + let dtags = Metadata { + title: dbtags.title.unwrap_or(String::new()), + artist: dbtags.artist.unwrap_or(String::new()), + album_artist: dbtags.album_artist.unwrap_or(String::new()), + album: dbtags.album.unwrap_or(String::new()), + genre: dbtags.genre.unwrap_or(String::new()), + duration: dbtags.duration, }; pb.set_message(format!("{}", dbtags.file)); @@ -265,9 +276,20 @@ impl Db { 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() { + 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 { + } 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; }, @@ -286,24 +308,36 @@ impl Db { pub fn clear_ignore(&self) { match self.conn.execute("UPDATE Tracks SET Ignore=0;", []) { - Ok(_) => { }, - Err(e) => { log::error!("Failed clear Ignore column. {}", e); } + Ok(_) => {} + Err(e) => { + log::error!("Failed clear Ignore column. {}", e); + } } } - pub fn set_ignore(&self, line:&str) { + pub fn set_ignore(&self, line: &str) { log::info!("Ignore: {}", line); if line.starts_with("SQL:") { let sql = &line[4..]; - match self.conn.execute(&format!("UPDATE Tracks Set Ignore=1 WHERE {}", sql), []) { - Ok(_) => { }, - Err(e) => { log::error!("Failed set Ignore column for '{}'. {}", line, e); } + match self + .conn + .execute(&format!("UPDATE Tracks Set Ignore=1 WHERE {}", sql), []) + { + Ok(_) => {} + Err(e) => { + log::error!("Failed set Ignore column for '{}'. {}", line, e); + } } } else { - match self.conn.execute(&format!("UPDATE Tracks SET Ignore=1 WHERE File LIKE \"{}%\"", line), []) { - Ok(_) => { }, - Err(e) => { log::error!("Failed set Ignore column for '{}'. {}", line, e); } + match self.conn.execute( + &format!("UPDATE Tracks SET Ignore=1 WHERE File LIKE \"{}%\"", line), + [], + ) { + Ok(_) => {} + Err(e) => { + log::error!("Failed set Ignore column for '{}'. {}", line, e); + } } } } - } \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index 4c15e61..3ea6b17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,9 +19,8 @@ mod db; mod tags; mod upload; - const VERSION: &'static str = env!("CARGO_PKG_VERSION"); -const TOP_LEVEL_INI_TAG:&str = "Bliss"; +const TOP_LEVEL_INI_TAG: &str = "Bliss"; fn main() { let mut config_file = "config.ini".to_string(); @@ -29,24 +28,32 @@ fn main() { let mut logging = "info".to_string(); let mut music_path = ".".to_string(); let mut ignore_file = "ignore.txt".to_string(); - let mut keep_old:bool = false; - let mut dry_run:bool = false; + let mut keep_old: bool = false; + let mut dry_run: bool = false; 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 = Vec::new(); + let mut max_num_tracks: usize = 0; + let mut music_paths: Vec = Vec::new(); match dirs::home_dir() { - Some(path) => { music_path = String::from(path.join("Music").to_string_lossy()); } - None => { } + Some(path) => { + music_path = String::from(path.join("Music").to_string_lossy()); + } + None => {} } { let config_file_help = format!("config file (default: {})", &config_file); let music_path_help = format!("Music folder (default: {})", &music_path); let db_path_help = format!("Database location (default: {})", &db_path); - let logging_help = format!("Log level; trace, debug, info, warn, error. (default: {})", logging); - let ignore_file_help = format!("File containg items to mark as ignored. (default: {})", ignore_file); + let logging_help = format!( + "Log level; trace, debug, info, warn, error. (default: {})", + logging + ); + let ignore_file_help = format!( + "File containg items to mark as ignored. (default: {})", + ignore_file + ); let lms_host_help = format!("LMS hostname or IP address (default: {})", &lms_host); let description = format!("Bliss Analyser v{}", VERSION); @@ -54,33 +61,80 @@ fn main() { // borrow per scope, hence this section is enclosed in { } let mut arg_parse = ArgumentParser::new(); arg_parse.set_description(&description); - arg_parse.refer(&mut config_file).add_option(&["-c", "--config"], Store, &config_file_help); - arg_parse.refer(&mut music_path).add_option(&["-m", "--music"], Store, &music_path_help); - arg_parse.refer(&mut db_path).add_option(&["-d", "--db"], Store, &db_path_help); - arg_parse.refer(&mut logging).add_option(&["-l", "--logging"], Store, &logging_help); - arg_parse.refer(&mut keep_old).add_option(&["-k", "--keep-old"], StoreTrue, "Don't remove tracks from DB if they don't exist (used with analyse task)"); - arg_parse.refer(&mut dry_run).add_option(&["-r", "--dry-run"], StoreTrue, "Dry run, only show what needs to be done (used with analyse task)"); - arg_parse.refer(&mut ignore_file).add_option(&["-i", "--ignore"], Store, &ignore_file_help); - arg_parse.refer(&mut lms_host).add_option(&["-L", "--lms"], Store, &lms_host_help); - arg_parse.refer(&mut max_num_tracks).add_option(&["-n", "--numtracks"], Store, "Maximum number of tracks to analyse"); - arg_parse.refer(&mut task).add_argument("task", Store, "Task to perform; analyse, tags, ignore, upload, stopmixer."); + arg_parse + .refer(&mut config_file) + .add_option(&["-c", "--config"], Store, &config_file_help); + arg_parse + .refer(&mut music_path) + .add_option(&["-m", "--music"], Store, &music_path_help); + arg_parse + .refer(&mut db_path) + .add_option(&["-d", "--db"], Store, &db_path_help); + arg_parse + .refer(&mut logging) + .add_option(&["-l", "--logging"], Store, &logging_help); + arg_parse.refer(&mut keep_old).add_option( + &["-k", "--keep-old"], + StoreTrue, + "Don't remove tracks from DB if they don't exist (used with analyse task)", + ); + arg_parse.refer(&mut dry_run).add_option( + &["-r", "--dry-run"], + StoreTrue, + "Dry run, only show what needs to be done (used with analyse task)", + ); + arg_parse + .refer(&mut ignore_file) + .add_option(&["-i", "--ignore"], Store, &ignore_file_help); + arg_parse + .refer(&mut lms_host) + .add_option(&["-L", "--lms"], Store, &lms_host_help); + arg_parse.refer(&mut max_num_tracks).add_option( + &["-n", "--numtracks"], + Store, + "Maximum number of tracks to analyse", + ); + arg_parse.refer(&mut task).add_argument( + "task", + Store, + "Task to perform; analyse, tags, ignore, upload, stopmixer.", + ); arg_parse.parse_args_or_exit(); } - if !(logging.eq_ignore_ascii_case("trace") || logging.eq_ignore_ascii_case("debug") || logging.eq_ignore_ascii_case("info") || logging.eq_ignore_ascii_case("warn") || logging.eq_ignore_ascii_case("error")) { + if !(logging.eq_ignore_ascii_case("trace") + || logging.eq_ignore_ascii_case("debug") + || logging.eq_ignore_ascii_case("info") + || logging.eq_ignore_ascii_case("warn") + || logging.eq_ignore_ascii_case("error")) + { logging = String::from("info"); } - let mut builder = env_logger::Builder::from_env(env_logger::Env::default().filter_or("XXXXXXXX", logging)); + let mut builder = + env_logger::Builder::from_env(env_logger::Env::default().filter_or("XXXXXXXX", logging)); builder.filter(Some("bliss_audio"), LevelFilter::Error); - builder.format(|buf, record| writeln!(buf, "[{} {:.1}] {}", Local::now().format("%Y-%m-%d %H:%M:%S"), record.level(), record.args())); + builder.format(|buf, record| { + writeln!( + buf, + "[{} {:.1}] {}", + Local::now().format("%Y-%m-%d %H:%M:%S"), + record.level(), + record.args() + ) + }); builder.init(); - if task.is_empty() { + if task.is_empty() { log::error!("No task specified, please choose from; analyse, tags, ignore, upload"); process::exit(-1); } - if !task.eq_ignore_ascii_case("analyse") && !task.eq_ignore_ascii_case("tags") && !task.eq_ignore_ascii_case("ignore") && !task.eq_ignore_ascii_case("upload") && !task.eq_ignore_ascii_case("stopmixer") { + if !task.eq_ignore_ascii_case("analyse") + && !task.eq_ignore_ascii_case("tags") + && !task.eq_ignore_ascii_case("ignore") + && !task.eq_ignore_ascii_case("upload") + && !task.eq_ignore_ascii_case("stopmixer") + { log::error!("Invalid task ({}) supplied", task); process::exit(-1); } @@ -91,26 +145,35 @@ fn main() { let mut config = Ini::new(); match config.load(&config_file) { Ok(_) => { - let path_keys: [&str; 5] = ["music", "music_1", "music_2", "music_3", "music_4"]; + 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 => { } + Some(val) => { + music_paths.push(PathBuf::from(&val)); + } + None => {} } } match config.get(TOP_LEVEL_INI_TAG, "db") { - Some(val) => { db_path = val; }, - None => { } + Some(val) => { + db_path = val; + } + None => {} } match config.get(TOP_LEVEL_INI_TAG, "lms") { - Some(val) => { lms_host = val; }, - None => { } + Some(val) => { + lms_host = val; + } + None => {} } match config.get(TOP_LEVEL_INI_TAG, "ignore") { - Some(val) => { ignore_file = val; }, - None => { } + Some(val) => { + ignore_file = val; + } + None => {} } - }, + } Err(e) => { log::error!("Failed to load config file. {}", e); process::exit(-1); @@ -151,7 +214,10 @@ fn main() { process::exit(-1); } if !mpath.is_dir() { - log::error!("Music path ({}) is not a directory", mpath.to_string_lossy()); + log::error!( + "Music path ({}) is not a directory", + mpath.to_string_lossy() + ); process::exit(-1); } } diff --git a/src/tags.rs b/src/tags.rs index 102ae1d..d6a8060 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -1,3 +1,4 @@ +use crate::db; /** * Analyse music with Bliss * @@ -5,23 +6,21 @@ * GPLv3 license. * **/ - use lofty::{Accessor, ItemKey, Probe}; use regex::Regex; use std::path::Path; use substring::Substring; -use crate::db; -const MAX_GENRE_VAL:usize = 192; +const MAX_GENRE_VAL: usize = 192; -pub fn read(track:&String) -> db::Metadata { - let mut meta = db::Metadata{ - title:String::new(), - artist:String::new(), - album:String::new(), - album_artist:String::new(), - genre:String::new(), - duration:180 +pub fn read(track: &String) -> db::Metadata { + let mut meta = db::Metadata { + title: String::new(), + artist: String::new(), + album: String::new(), + album_artist: String::new(), + genre: String::new(), + duration: 180, }; let path = Path::new(track); match Probe::open(path) { @@ -33,11 +32,14 @@ pub fn read(track:&String) -> db::Metadata { None => file.first_tag().expect("Error: No tags found!"), }; - meta.title=tag.title().unwrap_or("").to_string(); - meta.artist=tag.artist().unwrap_or("").to_string(); - meta.album=tag.album().unwrap_or("").to_string(); - meta.album_artist=tag.get_string(&ItemKey::AlbumArtist).unwrap_or("").to_string(); - meta.genre=tag.genre().unwrap_or("").to_string(); + meta.title = tag.title().unwrap_or("").to_string(); + meta.artist = tag.artist().unwrap_or("").to_string(); + meta.album = tag.album().unwrap_or("").to_string(); + meta.album_artist = tag + .get_string(&ItemKey::AlbumArtist) + .unwrap_or("") + .to_string(); + meta.genre = tag.genre().unwrap_or("").to_string(); // Check whether MP3 as numeric genre, and if so covert to text if file.file_type().eq(&lofty::FileType::MP3) { match tag.genre() { @@ -45,43 +47,48 @@ pub fn read(track:&String) -> db::Metadata { let test = &genre.parse::(); match test { Ok(val) => { - let idx:usize = *val as usize; - if idx { // Check for "(number)text" let re = Regex::new(r"^\([0-9]+\)").unwrap(); if re.is_match(&genre) { match genre.find(")") { Some(end) => { - let test = &genre.to_string().substring(1, end).parse::(); + let test = &genre + .to_string() + .substring(1, end) + .parse::(); match test { Ok(val) => { - let idx:usize = *val as usize; - if idx { } + } + Err(_) => {} } - }, - None => { } + } + None => {} } } } } - }, + } None => {} } } - meta.duration=file.properties().duration().as_secs() as u32; - }, - Err(_) => { } + meta.duration = file.properties().duration().as_secs() as u32; + } + Err(_) => {} } - }, - Err(_) => { } + } + Err(_) => {} } meta -} \ No newline at end of file +} diff --git a/src/upload.rs b/src/upload.rs index 98f6712..8b6c945 100644 --- a/src/upload.rs +++ b/src/upload.rs @@ -5,100 +5,102 @@ * GPLv3 license. * **/ - use std::fs::File; use std::io::BufReader; use std::process; use substring::Substring; use ureq; - -fn fail(msg:&str) { +fn fail(msg: &str) { log::error!("{}", msg); process::exit(-1); } -pub fn stop_mixer(lms:&String) { - let stop_req = "{\"id\":1, \"method\":\"slim.request\",\"params\":[\"\",[\"blissmixer\",\"stop\"]]}"; +pub fn stop_mixer(lms: &String) { + let stop_req = + "{\"id\":1, \"method\":\"slim.request\",\"params\":[\"\",[\"blissmixer\",\"stop\"]]}"; log::info!("Asking plugin to stop mixer"); match ureq::post(&format!("http://{}:9000/jsonrpc.js", lms)).send_string(&stop_req) { - Ok(_) => { }, - Err(e) => { log::error!("Failed to ask plugin to stop mixer. {}", e); } + Ok(_) => {} + Err(e) => { + log::error!("Failed to ask plugin to stop mixer. {}", e); + } } } -pub fn upload_db(db_path:&String, lms:&String) { +pub fn upload_db(db_path: &String, lms: &String) { // First tell LMS to restart the mixer in upload mode let start_req = "{\"id\":1, \"method\":\"slim.request\",\"params\":[\"\",[\"blissmixer\",\"start-upload\"]]}"; - let mut port:u16 = 0; + let mut port: u16 = 0; log::info!("Requesting LMS plugin to allow uploads"); match ureq::post(&format!("http://{}:9000/jsonrpc.js", lms)).send_string(&start_req) { - Ok(resp) => { - match resp.into_string() { - Ok(text) => { - match text.find("\"port\":") { - Some(s) => { - let txt = text.to_string().substring(s+7, text.len()).to_string(); - match txt.find("}") { - Some(e) => { - let p = txt.substring(0, e); - let test = p.parse::(); - match test { - Ok(val) => { - port = val; - }, - Err(_) => { fail("Could not parse resp (cast)"); } - } - }, - None => { fail("Could not parse resp (closing)"); } + Ok(resp) => match resp.into_string() { + Ok(text) => match text.find("\"port\":") { + Some(s) => { + let txt = text.to_string().substring(s + 7, text.len()).to_string(); + match txt.find("}") { + Some(e) => { + let p = txt.substring(0, e); + let test = p.parse::(); + match test { + Ok(val) => { + port = val; + } + Err(_) => { + fail("Could not parse resp (cast)"); + } } - }, - None => { fail("Could not parse resp (no port)"); } + } + None => { + fail("Could not parse resp (closing)"); + } } - }, - Err(_) => { fail("No text?")} - } + } + None => { + fail("Could not parse resp (no port)"); + } + }, + Err(_) => fail("No text?"), }, Err(e) => { fail(&format!("Failed to ask LMS plugin to allow upload. {}", e)); } } - if port<=0 { + if port <= 0 { fail("Invalid port"); } // Now we have port number, do the actual upload... log::info!("Uploading {}", db_path); match File::open(db_path) { - Ok(file) => { - match file.metadata() { - Ok(meta) => { - let buffered_reader = BufReader::new(file); - log::info!("Length: {}", meta.len()); - match ureq::put(&format!("http://{}:{}/upload", lms, port)) - .set("Content-Length", &meta.len().to_string()) - .set("Content-Type", "application/octet-stream") - .send(buffered_reader) { - Ok(_) => { - log::info!("Database uploaded"); - stop_mixer(lms); - }, - Err(e) => { - fail(&format!("Failed to upload database. {}", e)); - } + Ok(file) => match file.metadata() { + Ok(meta) => { + let buffered_reader = BufReader::new(file); + log::info!("Length: {}", meta.len()); + match ureq::put(&format!("http://{}:{}/upload", lms, port)) + .set("Content-Length", &meta.len().to_string()) + .set("Content-Type", "application/octet-stream") + .send(buffered_reader) + { + Ok(_) => { + log::info!("Database uploaded"); + stop_mixer(lms); + } + Err(e) => { + fail(&format!("Failed to upload database. {}", e)); } - }, - Err(e) => { - fail(&format!("Failed to open database. {}", e)); } } + Err(e) => { + fail(&format!("Failed to open database. {}", e)); + } }, Err(e) => { fail(&format!("Failed to open database. {}", e)); } } - } +} From b097f06b5fc529ed68d0a8d9e63da175b78302dc Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Tue, 22 Mar 2022 17:05:33 -0400 Subject: [PATCH 02/10] Add `Metadata::is_empty` Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com> --- src/analyse.rs | 10 +++------- src/db.rs | 10 ++++++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/analyse.rs b/src/analyse.rs index 771b6ce..b841364 100644 --- a/src/analyse.rs +++ b/src/analyse.rs @@ -154,11 +154,7 @@ pub fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec) Ok(track) => { let cpath = String::from(path); let meta = tags::read(&cpath); - if meta.title.is_empty() - && meta.artist.is_empty() - && meta.album.is_empty() - && meta.genre.is_empty() - { + if meta.is_empty() { tag_error.push(sname.clone()); } @@ -180,7 +176,7 @@ pub fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec) let total = failed.len(); failed.truncate(MAX_ERRORS_TO_SHOW); - log::error!("Failed to analyse the folling track(s):"); + log::error!("Failed to analyse the following track(s):"); for err in failed { log::error!(" {}", err); } @@ -192,7 +188,7 @@ pub fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec) let total = tag_error.len(); tag_error.truncate(MAX_TAG_ERRORS_TO_SHOW); - log::error!("Failed to read tags of the folling track(s):"); + log::error!("Failed to read tags of the following track(s):"); for err in tag_error { log::error!(" {}", err); } diff --git a/src/db.rs b/src/db.rs index 50d6c00..6bcdd98 100644 --- a/src/db.rs +++ b/src/db.rs @@ -34,6 +34,16 @@ pub struct Metadata { pub duration: u32, } +impl Metadata { + pub fn is_empty(&self) -> bool { + self.title.is_empty() + && self.artist.is_empty() + && self.album_artist.is_empty() + && self.album.is_empty() + && self.genre.is_empty() + } +} + pub struct Db { pub conn: Connection, } From 51ce6cadc7a1c9408e4f3d1c2884e2afa498fa4b Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Tue, 22 Mar 2022 17:25:03 -0400 Subject: [PATCH 03/10] Cleanup `analyse` module Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com> --- Cargo.lock | 7 ++ Cargo.toml | 1 + src/analyse.rs | 313 ++++++++++++++++++++++--------------------------- 3 files changed, 147 insertions(+), 174 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6bf9647..2d9ebd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,6 +129,7 @@ dependencies = [ "dirs", "env_logger", "hhmmss", + "if_chain", "indicatif", "lofty", "log", @@ -635,6 +636,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + [[package]] name = "indexmap" version = "1.8.0" diff --git a/Cargo.toml b/Cargo.toml index 2adac3d..58f0b72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,4 @@ hhmmss = "0.1.0" num_cpus = "1.13.0" tempdir = "0.3.7" subprocess = "0.2.8" +if_chain = "1.0.2" diff --git a/src/analyse.rs b/src/analyse.rs index b841364..4193b67 100644 --- a/src/analyse.rs +++ b/src/analyse.rs @@ -11,13 +11,14 @@ use crate::tags; use anyhow::Result; use bliss_audio::{library::analyze_paths_streaming, BlissResult, Song}; use hhmmss::Hhmmss; +use if_chain::if_chain; use indicatif::{ProgressBar, ProgressStyle}; use num_cpus; use std::convert::TryInto; use std::fs; -use std::fs::File; +use std::fs::{DirEntry, File}; use std::io::{BufRead, BufReader}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::mpsc; use std::sync::mpsc::{Receiver, Sender}; use std::thread; @@ -28,115 +29,90 @@ use tempdir::TempDir; const DONT_ANALYSE: &str = ".notmusic"; const MAX_ERRORS_TO_SHOW: usize = 100; const MAX_TAG_ERRORS_TO_SHOW: usize = 50; +const VALID_EXTENSIONS: [&str; 5] = ["m4a", "mp3", "ogg", "flac", "opus"]; fn get_file_list( db: &mut db::Db, - mpath: &PathBuf, - path: &PathBuf, + mpath: &Path, + path: &Path, track_paths: &mut Vec, cue_tracks: &mut Vec, ) { - if path.is_dir() { - match path.read_dir() { - Ok(items) => { - for item in items { - match item { - Ok(entry) => { - let pb = entry.path().to_path_buf(); - if entry.path().is_dir() { - let mut check = pb.clone(); - check.push(PathBuf::from(DONT_ANALYSE)); - if check.exists() { - log::info!( - "Skipping '{}', found '{}'", - pb.to_string_lossy(), - DONT_ANALYSE - ); - } else { - get_file_list( - db, - mpath, - &entry.path(), - track_paths, - cue_tracks, - ); - } - } else if entry.path().is_file() { - let e = pb.extension(); - if e.is_some() { - let ext = e.unwrap().to_string_lossy(); - if ext == "m4a" - || ext == "mp3" - || ext == "ogg" - || ext == "flac" - || ext == "opus" - { - match pb.strip_prefix(mpath) { - Ok(stripped) => { - let mut cue_file = pb.clone(); - cue_file.set_extension("cue"); - if cue_file.exists() { - // Found a CUE file, try to parse and then check if tracks exists in DB - let this_cue_tracks = - cue::parse(&pb, &cue_file); - for track in this_cue_tracks { - match track.track_path.strip_prefix(mpath) { - Ok(tstripped) => { - let spb = tstripped.to_path_buf(); - let sname = String::from( - spb.to_string_lossy(), - ); - match db.get_rowid(&sname) { - Ok(id) => { - if id <= 0 { - cue_tracks.push( - track.clone(), - ); - } - } - Err(_) => {} - } - } - Err(_) => {} - } - } - } else { - let spb = stripped.to_path_buf(); - let sname = String::from(spb.to_string_lossy()); - match db.get_rowid(&sname) { - Ok(id) => { - if id <= 0 { - track_paths.push(String::from( - pb.to_string_lossy(), - )); - } - } - Err(_) => {} - } - } - } - Err(_) => {} - } + if !path.is_dir() { + return; + } + + if let Ok(items) = path.read_dir() { + for item in items { + if let Ok(entry) = item { + check_dir_entry(db, mpath, entry, track_paths, cue_tracks); + } + } + } +} + +fn check_dir_entry( + db: &mut db::Db, + mpath: &Path, + entry: DirEntry, + track_paths: &mut Vec, + cue_tracks: &mut Vec, +) { + let pb = entry.path(); + if pb.is_dir() { + let check = pb.join(DONT_ANALYSE); + if check.exists() { + log::info!( + "Skipping '{}', found '{}'", + pb.to_string_lossy(), + DONT_ANALYSE + ); + } else { + get_file_list(db, mpath, &pb, track_paths, cue_tracks); + } + } else if pb.is_file() { + if_chain! { + if let Some(ext) = pb.extension(); + let ext = ext.to_string_lossy(); + if VALID_EXTENSIONS.contains(&&*ext); + if let Ok(stripped) = pb.strip_prefix(mpath); + then { + let mut cue_file = pb.clone(); + cue_file.set_extension("cue"); + if cue_file.exists() { + // Found a CUE file, try to parse and then check if tracks exists in DB + let this_cue_tracks = cue::parse(&pb, &cue_file); + for track in this_cue_tracks { + if let Ok(tstripped) = track.track_path.strip_prefix(mpath) { + let sname = String::from(tstripped.to_string_lossy()); + + if let Ok(id) = db.get_rowid(&sname) { + if id<=0 { + cue_tracks.push(track); } } } } - Err(_) => {} + } else { + let sname = String::from(stripped.to_string_lossy()); + if let Ok(id) = db.get_rowid(&sname) { + if id<=0 { + track_paths.push(String::from(pb.to_string_lossy())); + } + } } } - } - Err(_) => {} } } } pub fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec) -> Result<()> { let total = track_paths.len(); - let pb = ProgressBar::new(total.try_into().unwrap()); - let style = ProgressStyle::default_bar() - .template("[{elapsed_precise}] [{bar:25}] {percent:>3}% {pos:>6}/{len:6} {wide_msg}") - .progress_chars("=> "); - pb.set_style(style); + let progress = ProgressBar::new(total.try_into().unwrap()).with_style( + ProgressStyle::default_bar() + .template("[{elapsed_precise}] [{bar:25}] {percent:>3}% {pos:>6}/{len:6} {wide_msg}") + .progress_chars("=> "), + ); let results = analyze_paths_streaming(track_paths)?; let mut analysed = 0; @@ -149,7 +125,7 @@ pub fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec) let stripped = pbuff.strip_prefix(mpath).unwrap(); let spbuff = stripped.to_path_buf(); let sname = String::from(spbuff.to_string_lossy()); - pb.set_message(format!("{}", sname)); + progress.set_message(format!("{}", sname)); match result { Ok(track) => { let cpath = String::from(path); @@ -165,9 +141,11 @@ pub fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec) failed.push(format!("{} - {}", sname, e)); } }; - pb.inc(1); + + progress.inc(1); } - pb.finish_with_message(format!( + + progress.finish_with_message(format!( "{} Analysed. {} Failure(s).", analysed, failed.len() @@ -226,72 +204,62 @@ pub fn analyze_cue_streaming( let tx_thread = tx.clone(); let owned_chunk = chunk.to_owned(); let child = thread::spawn(move || { + let dir = TempDir::new("bliss"); + if let Err(e) = dir { + log::error!("Failed to create temp folder. {}", e); + return; + } + let mut idx = 0; - match &TempDir::new("bliss") { - Ok(dir) => { - for cue_track in owned_chunk { - let audio_path = String::from(cue_track.audio_path.to_string_lossy()); - let ext = cue_track.audio_path.extension(); - let track_path = String::from(cue_track.track_path.to_string_lossy()); - let mut tmp_file = PathBuf::from(dir.path()); - if ext.is_some() { - tmp_file.push(format!("{}.{}", idx, ext.unwrap().to_string_lossy())); - } else { - tmp_file.push(format!("{}.flac", idx)); - } - idx += 1; - - log::debug!("Extracting '{}'", track_path); - match Exec::cmd("ffmpeg") - .arg("-i") - .arg(&audio_path) - .arg("-ss") - .arg(&cue_track.start.hhmmss()) - .arg("-t") - .arg(&cue_track.duration.hhmmss()) - .arg("-c") - .arg("copy") - .arg(String::from(tmp_file.to_string_lossy())) - .stderr(NullFile) - .join() - { - Ok(_) => {} - Err(e) => { - log::error!("Failed to call ffmpeg. {}", e); - } - } - - if !cfg!(windows) { - // ffmpeg seeks to break echo on terminal? 'stty echo' restores... - match Exec::cmd("stty").arg("echo").join() { - Ok(_) => {} - Err(_) => {} - } - } - - if tmp_file.exists() { - log::debug!("Analyzing '{}'", track_path); - let song = Song::new(&tmp_file); - if cue_track.duration >= last_track_duration { - // Last track, so read duration from temp file - let mut cloned = cue_track.clone(); - let meta = tags::read(&String::from(tmp_file.to_string_lossy())); - cloned.duration = Duration::new(meta.duration as u64, 0); - tx_thread.send((cloned, song)).unwrap(); - } else { - tx_thread.send((cue_track, song)).unwrap(); - } - match fs::remove_file(tmp_file) { - Ok(_) => {} - Err(_) => {} - } - } else { - log::error!("Failed to create temp file"); - } - } + let dir = dir.unwrap(); + for mut cue_track in owned_chunk { + let audio_path = String::from(cue_track.audio_path.to_string_lossy()); + let ext = cue_track.audio_path.extension(); + let track_path = String::from(cue_track.track_path.to_string_lossy()); + let mut tmp_file = PathBuf::from(dir.path()); + if ext.is_some() { + tmp_file.push(format!("{}.{}", idx, ext.unwrap().to_string_lossy())); + } else { + tmp_file.push(format!("{}.flac", idx)); } - Err(e) => { - log::error!("Failed to create temp folder. {}", e); + idx += 1; + + log::debug!("Extracting '{}'", track_path); + let cmd = Exec::cmd("ffmpeg") + .arg("-i") + .arg(&audio_path) + .arg("-ss") + .arg(&cue_track.start.hhmmss()) + .arg("-t") + .arg(&cue_track.duration.hhmmss()) + .arg("-c") + .arg("copy") + .arg(String::from(tmp_file.to_string_lossy())) + .stderr(NullFile) + .join(); + + if let Err(e) = cmd { + log::error!("Failed to call ffmpeg. {}", e); + } + + if !cfg!(windows) { + // ffmpeg seeks to break echo on terminal? 'stty echo' restores... + let _ = Exec::cmd("stty").arg("echo").join(); + } + + if tmp_file.exists() { + log::debug!("Analyzing '{}'", track_path); + let song = Song::new(&tmp_file); + if cue_track.duration >= last_track_duration { + // Last track, so read duration from temp file + let meta = tags::read(&String::from(tmp_file.to_string_lossy())); + cue_track.duration = Duration::new(meta.duration as u64, 0); + } + + tx_thread.send((cue_track, song)).unwrap(); + let _ = fs::remove_file(tmp_file); + } else { + log::error!("Failed to create temp file"); } } }); @@ -307,11 +275,9 @@ pub fn analyse_new_cue_tracks( cue_tracks: Vec, ) -> Result<()> { let total = cue_tracks.len(); - let pb = ProgressBar::new(total.try_into().unwrap()); - let style = ProgressStyle::default_bar() + let progress = ProgressBar::new(total.try_into().unwrap()).with_style(ProgressStyle::default_bar() .template("[{elapsed_precise}] [{bar:25}] {percent:>3}% {pos:>6}/{len:6} {wide_msg}") - .progress_chars("=> "); - pb.set_style(style); + .progress_chars("=> ")); let results = analyze_cue_streaming(cue_tracks)?; let mut analysed = 0; @@ -322,7 +288,7 @@ pub fn analyse_new_cue_tracks( let stripped = track.track_path.strip_prefix(mpath).unwrap(); let spbuff = stripped.to_path_buf(); let sname = String::from(spbuff.to_string_lossy()); - pb.set_message(format!("{}", sname)); + progress.set_message(format!("{}", sname)); match result { Ok(song) => { let meta = db::Metadata { @@ -341,9 +307,9 @@ pub fn analyse_new_cue_tracks( failed.push(format!("{} - {}", sname, e)); } }; - pb.inc(1); + progress.inc(1); } - pb.finish_with_message(format!( + progress.finish_with_message(format!( "{} Analysed. {} Failure(s).", analysed, failed.len() @@ -396,6 +362,7 @@ pub fn analyse_files( if !cue_tracks.is_empty() { log::info!("Num new cue tracks: {}", cue_tracks.len()); } + if dry_run { if !track_paths.is_empty() || !cue_tracks.is_empty() { log::info!("The following need to be analysed:"); @@ -438,11 +405,8 @@ pub fn analyse_files( } 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 let Err(e) = analyse_new_cue_tracks(&db, &mpath, cue_tracks) { + log::error!("Cue analysis returned error: {}", e); } } @@ -470,11 +434,12 @@ pub fn update_ignore(db_path: &str, ignore_path: &PathBuf) { db.init(); db.clear_ignore(); - for (_index, line) in reader.lines().enumerate() { - let line = line.unwrap(); + let mut lines = reader.lines(); + while let Some(Ok(line)) = lines.next() { if !line.is_empty() && !line.starts_with("#") { db.set_ignore(&line); } } + db.close(); } From b61aee0a6a338569e9894ca0e1b8720b5ba55a8e Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Tue, 22 Mar 2022 17:32:31 -0400 Subject: [PATCH 04/10] Cleanup `cue` module Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com> --- src/cue.rs | 81 +++++++++++++++++++++++++++--------------------------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/src/cue.rs b/src/cue.rs index 09ad8e6..25431c9 100644 --- a/src/cue.rs +++ b/src/cue.rs @@ -32,7 +32,7 @@ pub fn parse(audio_path: &PathBuf, cue_path: &PathBuf) -> Vec { let mut resp: Vec = Vec::new(); match parse_from_file(&cue_path.to_string_lossy(), false) { - Ok(cue) => { + Ok(mut cue) => { let album = cue.title.unwrap_or(String::new()); let album_artist = cue.performer.unwrap_or(String::new()); let mut genre = String::new(); @@ -41,47 +41,45 @@ pub fn parse(audio_path: &PathBuf, cue_path: &PathBuf) -> Vec { genre = comment.1; } } - if 1 == cue.files.len() { - for file in cue.files { - for track in file.tracks { - match track.indices.get(0) { - Some((_, start)) => { - let mut track_path = audio_path.clone(); - let ext = audio_path.extension().unwrap().to_string_lossy(); - track_path.set_extension(format!( - "{}{}{}", - ext, - MARKER, - resp.len() + 1 - )); - let mut ctrack = CueTrack { - audio_path: audio_path.clone(), - track_path: track_path, - title: track.title.unwrap_or(String::new()), - artist: track.performer.unwrap_or(String::new()), - album_artist: album_artist.clone(), - album: album.clone(), - genre: genre.clone(), - start: start.clone(), - duration: Duration::new(LAST_TRACK_DURATION, 0), - }; - if ctrack.artist.is_empty() && !ctrack.album_artist.is_empty() { - ctrack.artist = ctrack.album_artist.clone(); - } - if ctrack.album.is_empty() { - let mut path = audio_path.clone(); - path.set_extension(""); - match path.file_name() { - Some(n) => { - ctrack.album = String::from(n.to_string_lossy()); - } - None => {} - } - } - resp.push(ctrack); + + if let Some(file) = cue.files.pop() { + for track in file.tracks { + match track.indices.get(0) { + Some((_, start)) => { + let mut track_path = audio_path.clone(); + let ext = audio_path.extension().unwrap().to_string_lossy(); + track_path.set_extension(format!( + "{}{}{}", + ext, + MARKER, + resp.len() + 1 + )); + + let mut ctrack = CueTrack { + audio_path: audio_path.clone(), + track_path, + title: track.title.unwrap_or_default(), + artist: track.performer.unwrap_or_default(), + album_artist: album_artist.clone(), + album: album.clone(), + genre: genre.clone(), + start: start.clone(), + duration: Duration::new(LAST_TRACK_DURATION, 0), + }; + + if ctrack.artist.is_empty() && !ctrack.album_artist.is_empty() { + ctrack.artist = ctrack.album_artist.clone(); } - None => {} + + if ctrack.album.is_empty() { + if let Some(name) = audio_path.file_name() { + ctrack.album = String::from(name.to_string_lossy()); + } + } + + resp.push(ctrack); } + None => {} } } } @@ -92,7 +90,7 @@ pub fn parse(audio_path: &PathBuf, cue_path: &PathBuf) -> Vec { } for i in 0..(resp.len() - 1) { - let mut next_start = Duration::new(0, 0); + let mut next_start = Duration::ZERO; if let Some(next) = resp.get(i + 1) { next_start = next.start; } @@ -100,5 +98,6 @@ pub fn parse(audio_path: &PathBuf, cue_path: &PathBuf) -> Vec { (*elem).duration = next_start - elem.start; } } + resp } From 37d9f627ad5e00c501fdcaa33b45ae6db5881eb9 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Tue, 22 Mar 2022 17:41:02 -0400 Subject: [PATCH 05/10] Cleanup `db` module Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com> --- src/analyse.rs | 8 +-- src/db.rs | 130 +++++++++++++++++++++---------------------------- 2 files changed, 60 insertions(+), 78 deletions(-) diff --git a/src/analyse.rs b/src/analyse.rs index 4193b67..a0e8543 100644 --- a/src/analyse.rs +++ b/src/analyse.rs @@ -275,9 +275,11 @@ pub fn analyse_new_cue_tracks( cue_tracks: Vec, ) -> Result<()> { let total = cue_tracks.len(); - let progress = ProgressBar::new(total.try_into().unwrap()).with_style(ProgressStyle::default_bar() - .template("[{elapsed_precise}] [{bar:25}] {percent:>3}% {pos:>6}/{len:6} {wide_msg}") - .progress_chars("=> ")); + let progress = ProgressBar::new(total.try_into().unwrap()).with_style( + ProgressStyle::default_bar() + .template("[{elapsed_precise}] [{bar:25}] {percent:>3}% {pos:>6}/{len:6} {wide_msg}") + .progress_chars("=> "), + ); let results = analyze_cue_streaming(cue_tracks)?; let mut analysed = 0; diff --git a/src/db.rs b/src/db.rs index 6bcdd98..eeb5458 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,5 +1,3 @@ -use crate::cue; -use crate::tags; /** * Analyse music with Bliss * @@ -7,6 +5,8 @@ use crate::tags; * GPLv3 license. * **/ +use crate::cue; +use crate::tags; use bliss_audio::{Analysis, AnalysisIndex}; use indicatif::{ProgressBar, ProgressStyle}; use rusqlite::{params, Connection}; @@ -56,7 +56,7 @@ impl Db { } pub fn init(&self) { - match self.conn.execute( + let cmd = self.conn.execute( "CREATE TABLE IF NOT EXISTS Tracks ( File text primary key, Title text, @@ -88,34 +88,30 @@ impl Db { Chroma10 real );", [], - ) { - Ok(_) => {} - Err(_) => { - log::error!("Failed to create DB table"); - process::exit(-1); - } + ); + + if let Err(e) = cmd { + log::error!("Failed to create DB table"); + process::exit(-1); } - match self.conn.execute( + + let cmd = self.conn.execute( "CREATE UNIQUE INDEX IF NOT EXISTS Tracks_idx ON Tracks(File)", [], - ) { - Ok(_) => {} - Err(_) => { - log::error!("Failed to create DB index"); - process::exit(-1); - } + ); + + if let Err(e) = cmd { + log::error!("Failed to create DB index"); + process::exit(-1); } } pub fn close(self) { - match self.conn.close() { - Ok(_) => {} - Err(_) => {} - } + let _ = self.conn.close(); } - pub fn get_rowid(&self, path: &String) -> Result { - let mut db_path = path.clone(); + pub fn get_rowid(&self, path: &str) -> Result { + let mut db_path = path.to_string(); if cfg!(windows) { db_path = db_path.replace("\\", "/"); } @@ -212,14 +208,12 @@ impl Db { let count_before = self.get_track_count(); for t in to_remove { //log::debug!("Remove '{}'", t); - match self + let cmd = self .conn - .execute("DELETE FROM Tracks WHERE File = ?;", params![t]) - { - Ok(_) => {} - Err(e) => { - log::error!("Failed to remove '{}' - {}", t, e) - } + .execute("DELETE FROM Tracks WHERE File = ?;", params![t]); + + if let Err(e) = cmd { + log::error!("Failed to remove '{}' - {}", t, e) } } let count_now = self.get_track_count(); @@ -244,13 +238,14 @@ impl Db { pub fn update_tags(&self, mpaths: &Vec) { let total = self.get_track_count(); if total > 0 { - let pb = ProgressBar::new(total.try_into().unwrap()); - let style = ProgressStyle::default_bar() - .template( - "[{elapsed_precise}] [{bar:25}] {percent:>3}% {pos:>6}/{len:6} {wide_msg}", - ) - .progress_chars("=> "); - pb.set_style(style); + let progress = ProgressBar::new(total.try_into().unwrap()).with_style( + ProgressStyle::default_bar() + .template( + "[{elapsed_precise}] [{bar:25}] {percent:>3}% {pos:>6}/{len:6} {wide_msg}", + ) + .progress_chars("=> "), + ); + let mut stmt = self.conn.prepare("SELECT rowid, File, Title, Artist, AlbumArtist, Album, Genre, Duration FROM Tracks ORDER BY File ASC;").unwrap(); let track_iter = stmt .query_map([], |row| { @@ -272,34 +267,23 @@ impl Db { let dbtags = tr.unwrap(); if !dbtags.file.contains(cue::MARKER) { let dtags = Metadata { - title: dbtags.title.unwrap_or(String::new()), - artist: dbtags.artist.unwrap_or(String::new()), - album_artist: dbtags.album_artist.unwrap_or(String::new()), - album: dbtags.album.unwrap_or(String::new()), - genre: dbtags.genre.unwrap_or(String::new()), + title: dbtags.title.unwrap_or_default(), + artist: dbtags.artist.unwrap_or_default(), + album_artist: dbtags.album_artist.unwrap_or_default(), + album: dbtags.album.unwrap_or_default(), + genre: dbtags.genre.unwrap_or_default(), duration: dbtags.duration, }; - pb.set_message(format!("{}", dbtags.file)); + progress.set_message(format!("{}", dbtags.file)); 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() - { + if ftags.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 - { + } else if ftags != dtags { 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; }, @@ -310,18 +294,17 @@ impl Db { } } } - pb.inc(1); + progress.inc(1); } - pb.finish_with_message(format!("{} Updated.", updated)) + progress.finish_with_message(format!("{} Updated.", updated)) } } pub fn clear_ignore(&self) { - match self.conn.execute("UPDATE Tracks SET Ignore=0;", []) { - Ok(_) => {} - Err(e) => { - log::error!("Failed clear Ignore column. {}", e); - } + let cmd = self.conn.execute("UPDATE Tracks SET Ignore=0;", []); + + if let Err(e) = cmd { + log::error!("Failed clear Ignore column. {}", e); } } @@ -329,24 +312,21 @@ impl Db { log::info!("Ignore: {}", line); if line.starts_with("SQL:") { let sql = &line[4..]; - match self + let cmd = self .conn - .execute(&format!("UPDATE Tracks Set Ignore=1 WHERE {}", sql), []) - { - Ok(_) => {} - Err(e) => { - log::error!("Failed set Ignore column for '{}'. {}", line, e); - } + .execute(&format!("UPDATE Tracks Set Ignore=1 WHERE {}", sql), []); + + if let Err(e) = cmd { + log::error!("Failed set Ignore column for '{}'. {}", line, e); } } else { - match self.conn.execute( + let cmd = self.conn.execute( &format!("UPDATE Tracks SET Ignore=1 WHERE File LIKE \"{}%\"", line), [], - ) { - Ok(_) => {} - Err(e) => { - log::error!("Failed set Ignore column for '{}'. {}", line, e); - } + ); + + if let Err(e) = cmd { + log::error!("Failed set Ignore column for '{}'. {}", line, e); } } } From 625248fbc607e7d12009c1481df14d9dd169b63a Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Tue, 22 Mar 2022 17:44:40 -0400 Subject: [PATCH 06/10] Cleanup `tags` module Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com> --- src/db.rs | 1 + src/tags.rs | 108 +++++++++++++++++++++++----------------------------- 2 files changed, 48 insertions(+), 61 deletions(-) diff --git a/src/db.rs b/src/db.rs index eeb5458..e1454c6 100644 --- a/src/db.rs +++ b/src/db.rs @@ -25,6 +25,7 @@ pub struct FileMetadata { pub duration: u32, } +#[derive(Default)] pub struct Metadata { pub title: String, pub artist: String, diff --git a/src/tags.rs b/src/tags.rs index d6a8060..fb050a6 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -15,80 +15,66 @@ const MAX_GENRE_VAL: usize = 192; pub fn read(track: &String) -> db::Metadata { let mut meta = db::Metadata { - title: String::new(), - artist: String::new(), - album: String::new(), - album_artist: String::new(), - genre: String::new(), duration: 180, + ..db::Metadata::default() }; - let path = Path::new(track); - match Probe::open(path) { - Ok(probe) => { - match probe.read(true) { - Ok(file) => { - let tag = match file.primary_tag() { - Some(primary_tag) => primary_tag, - None => file.first_tag().expect("Error: No tags found!"), - }; - meta.title = tag.title().unwrap_or("").to_string(); - meta.artist = tag.artist().unwrap_or("").to_string(); - meta.album = tag.album().unwrap_or("").to_string(); - meta.album_artist = tag - .get_string(&ItemKey::AlbumArtist) - .unwrap_or("") - .to_string(); - meta.genre = tag.genre().unwrap_or("").to_string(); - // Check whether MP3 as numeric genre, and if so covert to text - if file.file_type().eq(&lofty::FileType::MP3) { - match tag.genre() { - Some(genre) => { - let test = &genre.parse::(); - match test { - Ok(val) => { - let idx: usize = *val as usize; - if idx < MAX_GENRE_VAL { - meta.genre = lofty::id3::v1::GENRES[idx].to_string(); - } - } - Err(_) => { - // Check for "(number)text" - let re = Regex::new(r"^\([0-9]+\)").unwrap(); - if re.is_match(&genre) { - match genre.find(")") { - Some(end) => { - let test = &genre - .to_string() - .substring(1, end) - .parse::(); - match test { - Ok(val) => { - let idx: usize = *val as usize; - if idx < MAX_GENRE_VAL { - meta.genre = lofty::id3::v1::GENRES - [idx] - .to_string(); - } - } - Err(_) => {} - } - } - None => {} + if let Ok(file) = lofty::read_from_path(Path::new(track), true) { + let tag = match file.primary_tag() { + Some(primary_tag) => primary_tag, + None => file.first_tag().expect("Error: No tags found!"), + }; + + meta.title = tag.title().unwrap_or_default().to_string(); + meta.artist = tag.artist().unwrap_or_default().to_string(); + meta.album = tag.album().unwrap_or_default().to_string(); + meta.album_artist = tag + .get_string(&ItemKey::AlbumArtist) + .unwrap_or_default() + .to_string(); + meta.genre = tag.genre().unwrap_or_default().to_string(); + + // Check whether MP3 as numeric genre, and if so covert to text + if file.file_type().eq(&lofty::FileType::MP3) { + match tag.genre() { + Some(genre) => { + let test = genre.parse::(); + match test { + Ok(val) => { + let idx: usize = val as usize; + if idx < MAX_GENRE_VAL { + meta.genre = lofty::id3::v1::GENRES[idx].to_string(); + } + } + Err(_) => { + // Check for "(number)text" + let re = Regex::new(r"^\([0-9]+\)").unwrap(); + if re.is_match(&genre) { + match genre.find(")") { + Some(end) => { + let test = + genre.to_string().substring(1, end).parse::(); + + if let Ok(val) = test { + let idx: usize = val as usize; + if idx < MAX_GENRE_VAL { + meta.genre = + lofty::id3::v1::GENRES[idx].to_string(); } } } + None => {} } } - None => {} } } - meta.duration = file.properties().duration().as_secs() as u32; } - Err(_) => {} + None => {} } } - Err(_) => {} + + meta.duration = file.properties().duration().as_secs() as u32; } + meta } From f6edb983d32deee38f612dc560486b9446b7a206 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Tue, 22 Mar 2022 17:49:27 -0400 Subject: [PATCH 07/10] Cleanup `upload` module Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com> --- src/upload.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/upload.rs b/src/upload.rs index 8b6c945..89f362a 100644 --- a/src/upload.rs +++ b/src/upload.rs @@ -21,11 +21,9 @@ pub fn stop_mixer(lms: &String) { "{\"id\":1, \"method\":\"slim.request\",\"params\":[\"\",[\"blissmixer\",\"stop\"]]}"; log::info!("Asking plugin to stop mixer"); - match ureq::post(&format!("http://{}:9000/jsonrpc.js", lms)).send_string(&stop_req) { - Ok(_) => {} - Err(e) => { - log::error!("Failed to ask plugin to stop mixer. {}", e); - } + let req = ureq::post(&format!("http://{}:9000/jsonrpc.js", lms)).send_string(&stop_req); + if let Err(e) = req { + log::error!("Failed to ask plugin to stop mixer. {}", e); } } @@ -70,7 +68,7 @@ pub fn upload_db(db_path: &String, lms: &String) { } } - if port <= 0 { + if port == 0 { fail("Invalid port"); } From c51de4769679e5429a9d7c6dfcca9a1ce7de0fb0 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Tue, 22 Mar 2022 18:05:39 -0400 Subject: [PATCH 08/10] Missing derive Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com> --- src/db.rs | 2 +- src/tags.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/db.rs b/src/db.rs index e1454c6..676f336 100644 --- a/src/db.rs +++ b/src/db.rs @@ -25,7 +25,7 @@ pub struct FileMetadata { pub duration: u32, } -#[derive(Default)] +#[derive(Default, PartialEq)] pub struct Metadata { pub title: String, pub artist: String, diff --git a/src/tags.rs b/src/tags.rs index fb050a6..92540bb 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -6,7 +6,7 @@ use crate::db; * GPLv3 license. * **/ -use lofty::{Accessor, ItemKey, Probe}; +use lofty::{Accessor, ItemKey}; use regex::Regex; use std::path::Path; use substring::Substring; From 182d9803954fc701138ca95a624268c8264d9bfb Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Tue, 22 Mar 2022 19:07:59 -0400 Subject: [PATCH 09/10] Typos Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com> --- src/db.rs | 4 ++-- src/main.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/db.rs b/src/db.rs index 676f336..d069c8d 100644 --- a/src/db.rs +++ b/src/db.rs @@ -91,7 +91,7 @@ impl Db { [], ); - if let Err(e) = cmd { + if cmd.is_err() { log::error!("Failed to create DB table"); process::exit(-1); } @@ -101,7 +101,7 @@ impl Db { [], ); - if let Err(e) = cmd { + if cmd.is_err() { log::error!("Failed to create DB index"); process::exit(-1); } diff --git a/src/main.rs b/src/main.rs index 3ea6b17..e050946 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,7 +51,7 @@ fn main() { logging ); let ignore_file_help = format!( - "File containg items to mark as ignored. (default: {})", + "File contains items to mark as ignored. (default: {})", ignore_file ); let lms_host_help = format!("LMS hostname or IP address (default: {})", &lms_host); From dabbfdb206a80dc395979d8624d87b65cc708af0 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Tue, 22 Mar 2022 19:08:50 -0400 Subject: [PATCH 10/10] More typos Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com> --- src/analyse.rs | 2 +- src/db.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/analyse.rs b/src/analyse.rs index a0e8543..9ea3ea8 100644 --- a/src/analyse.rs +++ b/src/analyse.rs @@ -320,7 +320,7 @@ pub fn analyse_new_cue_tracks( let total = failed.len(); failed.truncate(MAX_ERRORS_TO_SHOW); - log::error!("Failed to analyse the folling track(s):"); + log::error!("Failed to analyse the following track(s):"); for err in failed { log::error!(" {}", err); } diff --git a/src/db.rs b/src/db.rs index d069c8d..f114a90 100644 --- a/src/db.rs +++ b/src/db.rs @@ -164,7 +164,7 @@ impl Db { } pub fn remove_old(&self, mpaths: &Vec, dry_run: bool) { - log::info!("Looking for non-existant tracks"); + log::info!("Looking for non-existent tracks"); let mut stmt = self.conn.prepare("SELECT File FROM Tracks;").unwrap(); let track_iter = stmt.query_map([], |row| Ok((row.get(0)?,))).unwrap(); let mut to_remove: Vec = Vec::new(); @@ -198,7 +198,7 @@ impl Db { } let num_to_remove = to_remove.len(); - log::info!("Num non-existant tracks: {}", num_to_remove); + log::info!("Num non-existent tracks: {}", num_to_remove); if num_to_remove > 0 { if dry_run { log::info!("The following need to be removed from database:");