diff --git a/Cargo.lock b/Cargo.lock index 83be087..46ed1da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,7 +180,7 @@ dependencies = [ [[package]] name = "bliss-analyser" -version = "0.3.0" +version = "0.4.0" dependencies = [ "anyhow", "argparse", diff --git a/Cargo.toml b/Cargo.toml index 40f912a..a01131b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bliss-analyser" -version = "0.3.0" +version = "0.4.0" authors = ["Craig Drummond "] edition = "2021" license = "GPL-3.0-only" diff --git a/ChangeLog b/ChangeLog index 9aa0626..7c4e584 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +0.4.0 +----- +1. Add action to export results from database to files. + 0.3.0 ----- 1. Add support for (DSD) WavPack - thanks to Bart Lauret diff --git a/UserGuide.md b/UserGuide.md index 12ce412..af1b90a 100644 --- a/UserGuide.md +++ b/UserGuide.md @@ -143,7 +143,7 @@ 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 when uploading the database file to LMS. This defaults to `127.0.0.1` If your LMS is password protected then use `user:pass@server` - e.g. `lms=pi:abc123@127.0.0.1` -* `json` specifies the JSONRPC port number of your LMS server. This will defaul to +* `json` specifies the JSONRPC port number of your LMS server. This will default to 9000. * `ignore` specifies the name and location of a file containing items to ignore in mixes. See the `Ignore` section later on for more details. @@ -172,7 +172,7 @@ tracks are to be analysed and how many old tracks are left in the database. * `-L` / `--lms` Hostname, or IP address, of your LMS server. * `-J` / `--json` JSONRPC port number of your LMS server. * `-n` / `--numtracks` Specify maximum number of tracks to analyse. -* `-T` / `--tags` Write anlysis results to file tags, and read from file tags. +* `-T` / `--tags` Write analysis results to file tags, and read from file tags. Equivalent items specified in the INI config file (detailed above) will override any specified on the commandline. @@ -187,6 +187,7 @@ required task. This takes the following values: any changes. * `ignore` Reads the `ignore` file and updates the database to flag tracks as to be ignored for mixes. +* `export` Exports tags from DB and stores within the audio files. @@ -357,6 +358,27 @@ is accomplished as follows: +Exporting Analysis +================== + +If you have analysis results stored within the SQLite DB, and not within the files +themselves, then you can use the `export` action to copy these analysis results from +the DB and into the files. + +(Linux / macOS) +``` +./bliss-analyser export +``` + +(Windows) +``` +.\bliss-analyser.exe export +``` + +*NOTE* Exporting of analysis results is not implemented for CUE tracks. + + + Credits ======= diff --git a/src/analyse.rs b/src/analyse.rs index da616e3..70b1f73 100644 --- a/src/analyse.rs +++ b/src/analyse.rs @@ -482,6 +482,13 @@ pub fn read_tags(db_path: &str, mpaths: &Vec) { db.close(); } +pub fn export(db_path: &str, mpaths: &Vec) { + let db = db::Db::new(&String::from(db_path)); + db.init(); + db.export(&mpaths); + db.close(); +} + pub fn update_ignore(db_path: &str, ignore_path: &PathBuf) { let file = File::open(ignore_path).unwrap(); let reader = BufReader::new(file); diff --git a/src/db.rs b/src/db.rs index d7ee80c..be0e879 100644 --- a/src/db.rs +++ b/src/db.rs @@ -29,6 +29,11 @@ pub struct FileMetadata { pub duration: u32, } +struct AnalysisResults { + pub file: String, + pub analysis: Analysis, +} + #[derive(Default, PartialEq)] pub struct Metadata { pub title: String, @@ -338,4 +343,50 @@ impl Db { } } } + + pub fn export(&self, mpaths: &Vec) { + let total = self.get_track_count(); + if total > 0 { + 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 File, Tempo, Zcr, MeanSpectralCentroid, StdDevSpectralCentroid, MeanSpectralRolloff, StdDevSpectralRolloff, MeanSpectralFlatness, StdDevSpectralFlatness, MeanLoudness, StdDevLoudness, Chroma1, Chroma2, Chroma3, Chroma4, Chroma5, Chroma6, Chroma7, Chroma8, Chroma9, Chroma10 FROM Tracks ORDER BY File ASC;").unwrap(); + let track_iter = stmt + .query_map([], |row| { + Ok(AnalysisResults { + file: row.get(0)?, + analysis: Analysis::new([row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?, row.get(5)?, row.get(6)?, row.get(7)?, row.get(8)?, row.get(9)?, row.get(10)?, row.get(11)?, row.get(12)?, row.get(13)?, row.get(14)?, row.get(15)?, row.get(16)?, row.get(17)?, row.get(18)?, row.get(19)?, row.get(20)?]), + }) + }) + .unwrap(); + + let mut updated = 0; + for tr in track_iter { + let dbtags = tr.unwrap(); + if !dbtags.file.contains(CUE_MARKER) { + progress.set_message(format!("{}", dbtags.file)); + + for mpath in mpaths { + let track_path = mpath.join(&dbtags.file); + if track_path.exists() { + let spath = String::from(track_path.to_string_lossy()); + let meta = tags::read(&spath, true); + if meta.is_empty() || meta.analysis.is_none() || meta.analysis.unwrap()!=dbtags.analysis { + tags::write_analysis(&spath, &dbtags.analysis); + updated+=1; + } + break; + } + } + } + progress.inc(1); + } + progress.finish_with_message(format!("{} Updated.", updated)) + } + } } diff --git a/src/main.rs b/src/main.rs index 2555af8..01c122f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,7 +76,7 @@ fn main() { arg_parse.refer(&mut max_num_files).add_option(&["-n", "--numfiles"], Store, "Maximum number of files to analyse"); arg_parse.refer(&mut max_threads).add_option(&["-t", "--threads"], Store, "Maximum number of threads to use for analysis"); arg_parse.refer(&mut use_tags).add_option(&["-T", "--tags"], StoreTrue, "Read/write analysis results from/to source files"); - arg_parse.refer(&mut task).add_argument("task", Store, "Task to perform; analyse, tags, ignore, upload, stopmixer."); + arg_parse.refer(&mut task).add_argument("task", Store, "Task to perform; analyse, tags, ignore, upload, export, stopmixer."); arg_parse.parse_args_or_exit(); } @@ -94,7 +94,7 @@ fn main() { builder.init(); if task.is_empty() { - log::error!("No task specified, please choose from; analyse, tags, ignore, upload"); + log::error!("No task specified, please choose from; analyse, tags, ignore, upload, export, stopmixer"); process::exit(-1); } @@ -206,6 +206,8 @@ fn main() { process::exit(-1); } analyse::update_ignore(&db_path, &ignore_path); + } else if task.eq_ignore_ascii_case("export") { + analyse::export(&db_path, &music_paths); } else { let ignore_path = PathBuf::from(&ignore_file); analyse::analyse_files(&db_path, &music_paths, dry_run, keep_old, max_num_files, max_threads, &ignore_path, use_tags);