From 467e341cfbacfbad9f10013f7b950b5ede8b6192 Mon Sep 17 00:00:00 2001 From: Craig Drummond Date: Mon, 18 Apr 2022 20:20:32 +0100 Subject: [PATCH] Update version of bliss-rs, this now handles CUE processing internally. --- Cargo.lock | 301 ++----------------------------------------------- Cargo.toml | 7 +- ChangeLog | 2 +- src/analyse.rs | 300 ++++++++++++++---------------------------------- src/cue.rs | 97 ---------------- src/db.rs | 7 +- src/main.rs | 9 +- 7 files changed, 102 insertions(+), 621 deletions(-) delete mode 100644 src/cue.rs diff --git a/Cargo.lock b/Cargo.lock index f386100..5267252 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,12 +69,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "base-x" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" - [[package]] name = "base64" version = "0.13.0" @@ -128,25 +122,21 @@ dependencies = [ "configparser", "dirs", "env_logger", - "hhmmss", "if_chain", "indicatif", "lofty", "log", - "num_cpus", - "rcue", "regex", "rusqlite", - "subprocess", "substring", - "tempdir", "ureq", ] [[package]] name = "bliss-audio" version = "0.5.0" -source = "git+https://github.com/Polochon-street/bliss-rs#c08fd3d7034c737e7645f59498dfa78b24eea182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947fa29cb8c27448497bdbcbab05a9eb3c1f046a5efd7b684060a6dc587bb761" dependencies = [ "bliss-audio-aubio-rs", "crossbeam", @@ -160,6 +150,7 @@ dependencies = [ "noisy_float", "num_cpus", "rayon", + "rcue", "ripemd160", "rustfft", "strum", @@ -266,7 +257,7 @@ dependencies = [ "libc", "num-integer", "num-traits", - "time 0.1.44", + "time", "winapi", ] @@ -306,12 +297,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "const_fn" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" - [[package]] name = "constant_time_eq" version = "0.1.5" @@ -424,12 +409,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "discard" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" - [[package]] name = "either" version = "1.6.1" @@ -520,12 +499,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - [[package]] name = "generic-array" version = "0.12.4" @@ -609,16 +582,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hhmmss" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a3a7d0916cb01ef108a66108640419767991ea31d11a1c851bed37686a6062" -dependencies = [ - "chrono", - "time 0.2.27", -] - [[package]] name = "humantime" version = "2.1.0" @@ -673,12 +636,6 @@ dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" - [[package]] name = "jobserver" version = "0.1.24" @@ -852,7 +809,7 @@ dependencies = [ "noisy_float", "num-integer", "num-traits", - "rand 0.8.5", + "rand", ] [[package]] @@ -1048,12 +1005,6 @@ dependencies = [ "num-integer", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - [[package]] name = "proc-macro2" version = "1.0.36" @@ -1085,19 +1036,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi", -] - [[package]] name = "rand" version = "0.8.5" @@ -1106,7 +1044,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.3", + "rand_core", ] [[package]] @@ -1116,24 +1054,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.6.3" @@ -1176,17 +1099,9 @@ dependencies = [ [[package]] name = "rcue" -version = "0.1.0" -source = "git+https://github.com/gyng/rcue#9ecd1ccbb764acfb7d54c3395525a4c85b43daea" - -[[package]] -name = "rdrand" -version = "0.4.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] +checksum = "fca1481d62f18158646de2ec552dd63f8bdc5be6448389b192ba95c939df997e" [[package]] name = "redox_syscall" @@ -1222,15 +1137,6 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - [[package]] name = "ring" version = "0.16.20" @@ -1290,15 +1196,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - [[package]] name = "rustfft" version = "5.1.1" @@ -1325,12 +1222,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "ryu" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" - [[package]] name = "scopeguard" version = "1.1.0" @@ -1347,49 +1238,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "serde" -version = "1.0.136" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" - -[[package]] -name = "serde_derive" -version = "1.0.136" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" -dependencies = [ - "itoa", - "ryu", - "serde", -] - [[package]] name = "sha-1" version = "0.8.2" @@ -1402,21 +1250,6 @@ dependencies = [ "opaque-debug 0.2.3", ] -[[package]] -name = "sha1" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" -dependencies = [ - "sha1_smol", -] - -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - [[package]] name = "shlex" version = "1.1.0" @@ -1435,64 +1268,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "standback" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" -dependencies = [ - "version_check", -] - -[[package]] -name = "stdweb" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" -dependencies = [ - "base-x", - "proc-macro2", - "quote", - "serde", - "serde_derive", - "serde_json", - "sha1", - "syn", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" - [[package]] name = "strength_reduce" version = "0.2.3" @@ -1517,16 +1292,6 @@ dependencies = [ "syn", ] -[[package]] -name = "subprocess" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "055cf3ebc2981ad8f0a5a17ef6652f652d87831f79fddcba2ac57bcb9a0aa407" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "substring" version = "1.4.5" @@ -1547,16 +1312,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "tempdir" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" -dependencies = [ - "rand 0.4.6", - "remove_dir_all", -] - [[package]] name = "termcolor" version = "1.1.2" @@ -1607,44 +1362,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "time" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" -dependencies = [ - "const_fn", - "libc", - "standback", - "stdweb", - "time-macros", - "version_check", - "winapi", -] - -[[package]] -name = "time-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" -dependencies = [ - "proc-macro-hack", - "time-macros-impl", -] - -[[package]] -name = "time-macros-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "standback", - "syn", -] - [[package]] name = "tinyvec" version = "1.5.1" diff --git a/Cargo.toml b/Cargo.toml index 170daf6..6dfa277 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["audio", "song", "similarity"] readme = "README.md" [dependencies] -bliss-audio = { git = "https://github.com/Polochon-street/bliss-rs" } +bliss-audio = "0.5.0" argparse = "0.2.2" anyhow = "1.0.40" rusqlite = { version = "0.25.0", features = ["bundled"] } @@ -24,9 +24,4 @@ regex = "1" substring = "1.4.5" ureq = "2.4.0" configparser = "3.0.0" -rcue = { git = "https://github.com/gyng/rcue" } -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/ChangeLog b/ChangeLog index 06cec5b..eb783d1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,7 +3,7 @@ 1. Tidy up code, thanks to Serial-ATA 2. Update version of tag reader library, should now support ID3v2 in FLAC. 3. Show error message if can't open, or create, database file. -4. Update version of bliss-rs. +4. Update version of bliss-rs, this now handles CUE processing internally. 0.1.0 ----- diff --git a/src/analyse.rs b/src/analyse.rs index 033d94d..b64361d 100644 --- a/src/analyse.rs +++ b/src/analyse.rs @@ -6,33 +6,24 @@ * **/ -use crate::cue; use crate::db; use crate::tags; use anyhow::Result; -use bliss_audio::{analyze_paths, BlissResult, Song}; -use hhmmss::Hhmmss; +use bliss_audio::{analyze_paths}; use if_chain::if_chain; use indicatif::{ProgressBar, ProgressStyle}; -use num_cpus; +use std::collections::HashSet; use std::convert::TryInto; -use std::fs; use std::fs::{DirEntry, File}; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; -use std::sync::mpsc; -use std::sync::mpsc::{Receiver, Sender}; -use std::thread; -use std::time::Duration; -use subprocess::{Exec, NullFile}; -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: &Path, path: &Path, track_paths: &mut Vec, cue_tracks: &mut Vec) { +fn get_file_list(db: &mut db::Db, mpath: &Path, path: &Path, track_paths: &mut Vec) { if !path.is_dir() { return; } @@ -40,20 +31,20 @@ fn get_file_list(db: &mut db::Db, mpath: &Path, path: &Path, track_paths: &mut V 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); + check_dir_entry(db, mpath, entry, track_paths); } } } } -fn check_dir_entry(db: &mut db::Db, mpath: &Path, entry: DirEntry, track_paths: &mut Vec, cue_tracks: &mut Vec) { +fn check_dir_entry(db: &mut db::Db, mpath: &Path, entry: DirEntry, track_paths: &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); + get_file_list(db, mpath, &pb, track_paths); } } else if pb.is_file() { if_chain! { @@ -62,24 +53,21 @@ fn check_dir_entry(db: &mut db::Db, mpath: &Path, entry: DirEntry, track_paths: if VALID_EXTENSIONS.contains(&&*ext); if let Ok(stripped) = pb.strip_prefix(mpath); then { + let sname = String::from(stripped.to_string_lossy()); 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); - } - } + // For cue files, check if first track is in DB + let mut track_path = pb.clone(); + let ext = pb.extension().unwrap().to_string_lossy(); + track_path.set_extension(format!("{}{}1", ext, db::CUE_MARKER)); + let db_track = String::from(track_path.to_string_lossy()); + if let Ok(id) = db.get_rowid(&db_track) { + if id<=0 { + track_paths.push(String::from(cue_file.to_string_lossy())); } } } 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())); @@ -102,41 +90,86 @@ pub fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec) let mut analysed = 0; let mut failed: Vec = Vec::new(); let mut tag_error: Vec = Vec::new(); + let mut reported_cue:HashSet = HashSet::new(); - log::info!("Analysing new tracks"); + log::info!("Analysing new files"); for (path, result) in analyze_paths(track_paths) { let pbuff = PathBuf::from(&path); let stripped = pbuff.strip_prefix(mpath).unwrap(); let spbuff = stripped.to_path_buf(); let sname = String::from(spbuff.to_string_lossy()); progress.set_message(format!("{}", sname)); + let mut inc_progress = true; // Only want to increment progress once for cue tracks match result { Ok(track) => { let cpath = String::from(path); - let meta = tags::read(&cpath); - if meta.is_empty() { - tag_error.push(sname.clone()); - } + match track.cue_info { + Some(cue) => { + match track.track_number { + Some(track_num) => { + let t_num = track_num.parse::().unwrap(); + if reported_cue.contains(&cpath) { + inc_progress = false; + } else { + analysed += 1; + reported_cue.insert(cpath); + } + let meta = db::Metadata { + title: track.title.unwrap_or_default().to_string(), + artist: track.artist.unwrap_or_default().to_string(), + album: track.album.unwrap_or_default().to_string(), + album_artist: track.album_artist.unwrap_or_default().to_string(), + genre: track.genre.unwrap_or_default().to_string(), + duration: track.duration.as_secs() as u32 + }; - db.add_track(&sname, &meta, &track.analysis); - analysed += 1; + // Remove prefix from audio_file_path + let pbuff = PathBuf::from(&cue.audio_file_path); + let stripped = pbuff.strip_prefix(mpath).unwrap(); + let spbuff = stripped.to_path_buf(); + let sname = String::from(spbuff.to_string_lossy()); + + let db_path = format!("{}{}{}", sname, db::CUE_MARKER, t_num); + db.add_track(&db_path, &meta, &track.analysis); + } + None => { failed.push(format!("{} - No track number?", sname)); } + } + } + None => { + // Use lofty to read tags here, and not bliss's, so that if update + // tags is ever used they are from the same source. + let mut meta = tags::read(&cpath); + if meta.is_empty() { + // Lofty failed? Try from bliss... + meta.title = track.title.unwrap_or_default().to_string(); + meta.artist = track.artist.unwrap_or_default().to_string(); + meta.album = track.album.unwrap_or_default().to_string(); + meta.album_artist = track.album_artist.unwrap_or_default().to_string(); + meta.genre = track.genre.unwrap_or_default().to_string(); + meta.duration = track.duration.as_secs() as u32; + } + if meta.is_empty() { + tag_error.push(sname.clone()); + } + db.add_track(&sname, &meta, &track.analysis); + analysed += 1; + } + } } Err(e) => { failed.push(format!("{} - {}", sname, e)); } }; - progress.inc(1); + if inc_progress { + progress.inc(1); + } } - progress.finish_with_message(format!( - "{} Analysed. {} Failure(s).", - analysed, - failed.len() - )); + progress.finish_with_message(format!("{} Analysed. {} Failure(s).", analysed, failed.len())); if !failed.is_empty() { let total = failed.len(); failed.truncate(MAX_ERRORS_TO_SHOW); - log::error!("Failed to analyse the following track(s):"); + log::error!("Failed to analyse the following file(s):"); for err in failed { log::error!(" {}", err); } @@ -148,7 +181,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 following track(s):"); + log::error!("Failed to read tags of the following file(s):"); for err in tag_error { log::error!(" {}", err); } @@ -159,149 +192,6 @@ pub fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec) Ok(()) } -pub fn analyze_cue_tracks(tracks: Vec) -> mpsc::IntoIter<(cue::CueTrack, BlissResult)> { - let num_cpus = num_cpus::get(); - let last_track_duration = Duration::new(cue::LAST_TRACK_DURATION, 0); - - #[allow(clippy::type_complexity)] - let (tx, rx): ( - Sender<(cue::CueTrack, BlissResult)>, - Receiver<(cue::CueTrack, BlissResult)>, - ) = mpsc::channel(); - if tracks.is_empty() { - return rx.into_iter(); - } - - let mut handles = Vec::new(); - let mut chunk_length = tracks.len() / num_cpus; - if chunk_length == 0 { - chunk_length = tracks.len(); - } else if chunk_length == 1 && tracks.len() > num_cpus { - chunk_length = 2; - } - - for chunk in tracks.chunks(chunk_length) { - 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; - 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)); - } - 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::from_path(&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"); - } - } - }); - handles.push(child); - } - - rx.into_iter() -} - -pub fn analyse_new_cue_tracks(db: &db::Db, mpath: &PathBuf, 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 mut analysed = 0; - let mut failed: Vec = Vec::new(); - - log::info!("Analysing new cue tracks"); - for (track, result) in analyze_cue_tracks(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()); - progress.set_message(format!("{}", sname)); - match result { - Ok(song) => { - 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, - }; - - db.add_track(&sname, &meta, &song.analysis); - analysed += 1; - } - Err(e) => { - failed.push(format!("{} - {}", sname, e)); - } - }; - progress.inc(1); - } - progress.finish_with_message(format!("{} Analysed. {} Failure(s).", analysed, failed.len())); - if !failed.is_empty() { - let total = failed.len(); - failed.truncate(MAX_ERRORS_TO_SHOW); - - log::error!("Failed to analyse the following track(s):"); - for err in failed { - log::error!(" {}", err); - } - 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) { let mut db = db::Db::new(&String::from(db_path)); let mut track_count_left = max_num_tracks; @@ -316,49 +206,31 @@ pub fn analyse_files(db_path: &str, mpaths: &Vec, dry_run: bool, keep_o let mpath = path.clone(); let cur = path.clone(); let mut track_paths: Vec = Vec::new(); - let mut cue_tracks: Vec = Vec::new(); if mpaths.len() > 1 { - log::info!("Looking for new tracks in {}", mpath.to_string_lossy()); + log::info!("Looking for new files in {}", mpath.to_string_lossy()); } else { - log::info!("Looking for new tracks"); + log::info!("Looking for new files"); } - get_file_list(&mut db, &mpath, &cur, &mut track_paths, &mut cue_tracks); + get_file_list(&mut db, &mpath, &cur, &mut track_paths); track_paths.sort(); - log::info!("Num new tracks: {}", track_paths.len()); - if !cue_tracks.is_empty() { - log::info!("Num new cue tracks: {}", cue_tracks.len()); - } + log::info!("Num new files: {}", track_paths.len()); if dry_run { - if !track_paths.is_empty() || !cue_tracks.is_empty() { + if !track_paths.is_empty() { log::info!("The following need to be analysed:"); for track in track_paths { log::info!(" {}", track); } - for track in cue_tracks { - log::info!(" {}", track.track_path.to_string_lossy()); - } } } else { if max_num_tracks > 0 { if track_paths.len() > track_count_left { - log::info!("Only analysing {} tracks", track_count_left); + log::info!("Only analysing {} files", track_count_left); track_paths.truncate(track_count_left); } track_count_left -= track_paths.len(); } - if max_num_tracks > 0 { - if track_count_left == 0 { - cue_tracks.clear(); - } else { - if cue_tracks.len() > track_count_left { - log::info!("Only analysing {} cue tracks", track_count_left); - cue_tracks.truncate(track_count_left); - } - track_count_left -= track_paths.len(); - } - } if !track_paths.is_empty() { match analyse_new_files(&db, &mpath, track_paths) { @@ -366,17 +238,11 @@ pub fn analyse_files(db_path: &str, mpaths: &Vec, dry_run: bool, keep_o Err(e) => { log::error!("Analysis returned error: {}", e); } } } else { - log::info!("No new tracks to analyse"); - } - - if !cue_tracks.is_empty() { - if let Err(e) = analyse_new_cue_tracks(&db, &mpath, cue_tracks) { - log::error!("Cue analysis returned error: {}", e); - } + log::info!("No new files to analyse"); } if max_num_tracks > 0 && track_count_left <= 0 { - log::info!("Track limit reached"); + log::info!("File limit reached"); break; } } diff --git a/src/cue.rs b/src/cue.rs deleted file mode 100644 index 40bf797..0000000 --- a/src/cue.rs +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Analyse music with Bliss - * - * Copyright (c) 2022 Craig Drummond - * 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"; - -#[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 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(mut cue) => { - let album = cue.title.unwrap_or(String::new()); - let album_artist = cue.performer.unwrap_or(String::new()); - let mut genre = String::new(); - for comment in cue.comments { - if comment.0.eq(GENRE) { - genre = comment.1; - } - } - - 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(); - } - - 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 => { } - } - } - } - } - Err(e) => { log::error!("Failed to parse '{}'. {}", cue_path.to_string_lossy(), e); } - } - - for i in 0..(resp.len() - 1) { - let mut next_start = Duration::ZERO; - if let Some(next) = resp.get(i + 1) { - next_start = next.start; - } - if let Some(elem) = resp.get_mut(i) { - (*elem).duration = next_start - elem.start; - } - } - - resp -} diff --git a/src/db.rs b/src/db.rs index fd1abba..261621f 100644 --- a/src/db.rs +++ b/src/db.rs @@ -6,7 +6,6 @@ * **/ -use crate::cue; use crate::tags; use bliss_audio::{Analysis, AnalysisIndex}; use indicatif::{ProgressBar, ProgressStyle}; @@ -15,6 +14,8 @@ use std::convert::TryInto; use std::path::PathBuf; use std::process; +pub const CUE_MARKER: &str = ".CUE_TRACK."; + pub struct FileMetadata { pub rowid: usize, pub file: String, @@ -173,7 +174,7 @@ impl Db { for tr in track_iter { let mut db_path: String = tr.unwrap().0; let orig_path = db_path.clone(); - match orig_path.find(cue::MARKER) { + match orig_path.find(CUE_MARKER) { Some(s) => { db_path.truncate(s); } @@ -266,7 +267,7 @@ impl Db { let mut updated = 0; for tr in track_iter { let dbtags = tr.unwrap(); - if !dbtags.file.contains(cue::MARKER) { + if !dbtags.file.contains(CUE_MARKER) { let dtags = Metadata { title: dbtags.title.unwrap_or_default(), artist: dbtags.artist.unwrap_or_default(), diff --git a/src/main.rs b/src/main.rs index 6cb67d3..fec1410 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,6 @@ use std::io::Write; use std::path::PathBuf; use std::process; mod analyse; -mod cue; mod db; mod tags; mod upload; @@ -33,7 +32,7 @@ fn main() { 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 max_num_files: usize = 0; let mut music_paths: Vec = Vec::new(); match dirs::home_dir() { @@ -60,11 +59,11 @@ fn main() { 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 keep_old).add_option(&["-k", "--keep-old"], StoreTrue, "Don't remove files 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 max_num_files).add_option(&["-n", "--numfiles"], Store, "Maximum number of files 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(); } @@ -176,7 +175,7 @@ fn main() { } analyse::update_ignore(&db_path, &ignore_path); } else { - analyse::analyse_files(&db_path, &music_paths, dry_run, keep_old, max_num_tracks); + analyse::analyse_files(&db_path, &music_paths, dry_run, keep_old, max_num_files); } } }