Update version of bliss-rs, this now handles CUE processing internally.

This commit is contained in:
Craig Drummond committed 2022-04-18 20:20:32 +01:00
1 parent 2788759829
commit 467e341cfb
7 files changed
+96 -615

No files matched your search

Generated
+9 -292
View File
@@ -69,12 +69,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base-x"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.13.0" version = "0.13.0"
@@ -128,25 +122,21 @@ dependencies = [
"configparser", "configparser",
"dirs", "dirs",
"env_logger", "env_logger",
"hhmmss",
"if_chain", "if_chain",
"indicatif", "indicatif",
"lofty", "lofty",
"log", "log",
"num_cpus",
"rcue",
"regex", "regex",
"rusqlite", "rusqlite",
"subprocess",
"substring", "substring",
"tempdir",
"ureq", "ureq",
] ]
[[package]] [[package]]
name = "bliss-audio" name = "bliss-audio"
version = "0.5.0" 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 = [ dependencies = [
"bliss-audio-aubio-rs", "bliss-audio-aubio-rs",
"crossbeam", "crossbeam",
@@ -160,6 +150,7 @@ dependencies = [
"noisy_float", "noisy_float",
"num_cpus", "num_cpus",
"rayon", "rayon",
"rcue",
"ripemd160", "ripemd160",
"rustfft", "rustfft",
"strum", "strum",
@@ -266,7 +257,7 @@ dependencies = [
"libc", "libc",
"num-integer", "num-integer",
"num-traits", "num-traits",
"time 0.1.44", "time",
"winapi", "winapi",
] ]
@@ -306,12 +297,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "const_fn"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935"
[[package]] [[package]]
name = "constant_time_eq" name = "constant_time_eq"
version = "0.1.5" version = "0.1.5"
@@ -424,12 +409,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "discard"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]] [[package]]
name = "either" name = "either"
version = "1.6.1" version = "1.6.1"
@@ -520,12 +499,6 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.12.4" version = "0.12.4"
@@ -609,16 +582,6 @@ dependencies = [
"libc", "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]] [[package]]
name = "humantime" name = "humantime"
version = "2.1.0" version = "2.1.0"
@@ -673,12 +636,6 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itoa"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]] [[package]]
name = "jobserver" name = "jobserver"
version = "0.1.24" version = "0.1.24"
@@ -852,7 +809,7 @@ dependencies = [
"noisy_float", "noisy_float",
"num-integer", "num-integer",
"num-traits", "num-traits",
"rand 0.8.5", "rand",
] ]
[[package]] [[package]]
@@ -1048,12 +1005,6 @@ dependencies = [
"num-integer", "num-integer",
] ]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.36" version = "1.0.36"
@@ -1085,19 +1036,6 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
@@ -1106,7 +1044,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha",
"rand_core 0.6.3", "rand_core",
] ]
[[package]] [[package]]
@@ -1116,24 +1054,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [ dependencies = [
"ppv-lite86", "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]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.6.3" version = "0.6.3"
@@ -1176,17 +1099,9 @@ dependencies = [
[[package]] [[package]]
name = "rcue" name = "rcue"
version = "0.1.0" version = "0.1.3"
source = "git+https://github.com/gyng/rcue#9ecd1ccbb764acfb7d54c3395525a4c85b43daea"
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" checksum = "fca1481d62f18158646de2ec552dd63f8bdc5be6448389b192ba95c939df997e"
dependencies = [
"rand_core 0.3.1",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
@@ -1222,15 +1137,6 @@ version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 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]] [[package]]
name = "ring" name = "ring"
version = "0.16.20" version = "0.16.20"
@@ -1290,15 +1196,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 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]] [[package]]
name = "rustfft" name = "rustfft"
version = "5.1.1" version = "5.1.1"
@@ -1325,12 +1222,6 @@ dependencies = [
"webpki", "webpki",
] ]
[[package]]
name = "ryu"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.1.0" version = "1.1.0"
@@ -1347,49 +1238,6 @@ dependencies = [
"untrusted", "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]] [[package]]
name = "sha-1" name = "sha-1"
version = "0.8.2" version = "0.8.2"
@@ -1402,21 +1250,6 @@ dependencies = [
"opaque-debug 0.2.3", "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]] [[package]]
name = "shlex" name = "shlex"
version = "1.1.0" version = "1.1.0"
@@ -1435,64 +1268,6 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 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]] [[package]]
name = "strength_reduce" name = "strength_reduce"
version = "0.2.3" version = "0.2.3"
@@ -1517,16 +1292,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "subprocess"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "055cf3ebc2981ad8f0a5a17ef6652f652d87831f79fddcba2ac57bcb9a0aa407"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "substring" name = "substring"
version = "1.4.5" version = "1.4.5"
@@ -1547,16 +1312,6 @@ dependencies = [
"unicode-xid", "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]] [[package]]
name = "termcolor" name = "termcolor"
version = "1.1.2" version = "1.1.2"
@@ -1607,44 +1362,6 @@ dependencies = [
"winapi", "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]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.5.1" version = "1.5.1"
+1 -6
View File
@@ -10,7 +10,7 @@ keywords = ["audio", "song", "similarity"]
readme = "README.md" readme = "README.md"
[dependencies] [dependencies]
bliss-audio = { git = "https://github.com/Polochon-street/bliss-rs" } bliss-audio = "0.5.0"
argparse = "0.2.2" argparse = "0.2.2"
anyhow = "1.0.40" anyhow = "1.0.40"
rusqlite = { version = "0.25.0", features = ["bundled"] } rusqlite = { version = "0.25.0", features = ["bundled"] }
@@ -24,9 +24,4 @@ regex = "1"
substring = "1.4.5" substring = "1.4.5"
ureq = "2.4.0" ureq = "2.4.0"
configparser = "3.0.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" if_chain = "1.0.2"
+1 -1
View File
@@ -3,7 +3,7 @@
1. Tidy up code, thanks to Serial-ATA 1. Tidy up code, thanks to Serial-ATA
2. Update version of tag reader library, should now support ID3v2 in FLAC. 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. 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 0.1.0
----- -----
+77 -211
View File
@@ -6,33 +6,24 @@
* *
**/ **/
use crate::cue;
use crate::db; use crate::db;
use crate::tags; use crate::tags;
use anyhow::Result; use anyhow::Result;
use bliss_audio::{analyze_paths, BlissResult, Song}; use bliss_audio::{analyze_paths};
use hhmmss::Hhmmss;
use if_chain::if_chain; use if_chain::if_chain;
use indicatif::{ProgressBar, ProgressStyle}; use indicatif::{ProgressBar, ProgressStyle};
use num_cpus; use std::collections::HashSet;
use std::convert::TryInto; use std::convert::TryInto;
use std::fs;
use std::fs::{DirEntry, File}; use std::fs::{DirEntry, File};
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf}; 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 DONT_ANALYSE: &str = ".notmusic";
const MAX_ERRORS_TO_SHOW: usize = 100; const MAX_ERRORS_TO_SHOW: usize = 100;
const MAX_TAG_ERRORS_TO_SHOW: usize = 50; const MAX_TAG_ERRORS_TO_SHOW: usize = 50;
const VALID_EXTENSIONS: [&str; 5] = ["m4a", "mp3", "ogg", "flac", "opus"]; 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<String>, cue_tracks: &mut Vec<cue::CueTrack>) { fn get_file_list(db: &mut db::Db, mpath: &Path, path: &Path, track_paths: &mut Vec<String>) {
if !path.is_dir() { if !path.is_dir() {
return; 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() { if let Ok(items) = path.read_dir() {
for item in items { for item in items {
if let Ok(entry) = item { 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<String>, cue_tracks: &mut Vec<cue::CueTrack>) { fn check_dir_entry(db: &mut db::Db, mpath: &Path, entry: DirEntry, track_paths: &mut Vec<String>) {
let pb = entry.path(); let pb = entry.path();
if pb.is_dir() { if pb.is_dir() {
let check = pb.join(DONT_ANALYSE); let check = pb.join(DONT_ANALYSE);
if check.exists() { if check.exists() {
log::info!("Skipping '{}', found '{}'", pb.to_string_lossy(), DONT_ANALYSE); log::info!("Skipping '{}', found '{}'", pb.to_string_lossy(), DONT_ANALYSE);
} else { } else {
get_file_list(db, mpath, &pb, track_paths, cue_tracks); get_file_list(db, mpath, &pb, track_paths);
} }
} else if pb.is_file() { } else if pb.is_file() {
if_chain! { 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 VALID_EXTENSIONS.contains(&&*ext);
if let Ok(stripped) = pb.strip_prefix(mpath); if let Ok(stripped) = pb.strip_prefix(mpath);
then { then {
let sname = String::from(stripped.to_string_lossy());
let mut cue_file = pb.clone(); let mut cue_file = pb.clone();
cue_file.set_extension("cue"); cue_file.set_extension("cue");
if cue_file.exists() { if cue_file.exists() {
// Found a CUE file, try to parse and then check if tracks exists in DB // For cue files, check if first track is in DB
let this_cue_tracks = cue::parse(&pb, &cue_file); let mut track_path = pb.clone();
for track in this_cue_tracks { let ext = pb.extension().unwrap().to_string_lossy();
if let Ok(tstripped) = track.track_path.strip_prefix(mpath) { track_path.set_extension(format!("{}{}1", ext, db::CUE_MARKER));
let sname = String::from(tstripped.to_string_lossy()); let db_track = String::from(track_path.to_string_lossy());
if let Ok(id) = db.get_rowid(&db_track) {
if let Ok(id) = db.get_rowid(&sname) {
if id<=0 { if id<=0 {
cue_tracks.push(track); track_paths.push(String::from(cue_file.to_string_lossy()));
}
}
} }
} }
} else { } else {
let sname = String::from(stripped.to_string_lossy());
if let Ok(id) = db.get_rowid(&sname) { if let Ok(id) = db.get_rowid(&sname) {
if id<=0 { if id<=0 {
track_paths.push(String::from(pb.to_string_lossy())); 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<String>)
let mut analysed = 0; let mut analysed = 0;
let mut failed: Vec<String> = Vec::new(); let mut failed: Vec<String> = Vec::new();
let mut tag_error: Vec<String> = Vec::new(); let mut tag_error: Vec<String> = Vec::new();
let mut reported_cue:HashSet<String> = HashSet::new();
log::info!("Analysing new tracks"); log::info!("Analysing new files");
for (path, result) in analyze_paths(track_paths) { for (path, result) in analyze_paths(track_paths) {
let pbuff = PathBuf::from(&path); let pbuff = PathBuf::from(&path);
let stripped = pbuff.strip_prefix(mpath).unwrap(); let stripped = pbuff.strip_prefix(mpath).unwrap();
let spbuff = stripped.to_path_buf(); let spbuff = stripped.to_path_buf();
let sname = String::from(spbuff.to_string_lossy()); let sname = String::from(spbuff.to_string_lossy());
progress.set_message(format!("{}", sname)); progress.set_message(format!("{}", sname));
let mut inc_progress = true; // Only want to increment progress once for cue tracks
match result { match result {
Ok(track) => { Ok(track) => {
let cpath = String::from(path); let cpath = String::from(path);
let meta = tags::read(&cpath); match track.cue_info {
Some(cue) => {
match track.track_number {
Some(track_num) => {
let t_num = track_num.parse::<i32>().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
};
// 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() { if meta.is_empty() {
tag_error.push(sname.clone()); tag_error.push(sname.clone());
} }
db.add_track(&sname, &meta, &track.analysis); db.add_track(&sname, &meta, &track.analysis);
analysed += 1; analysed += 1;
} }
}
}
Err(e) => { failed.push(format!("{} - {}", sname, e)); } Err(e) => { failed.push(format!("{} - {}", sname, e)); }
}; };
if inc_progress {
progress.inc(1); progress.inc(1);
} }
}
progress.finish_with_message(format!( progress.finish_with_message(format!("{} Analysed. {} Failure(s).", analysed, failed.len()));
"{} Analysed. {} Failure(s).",
analysed,
failed.len()
));
if !failed.is_empty() { if !failed.is_empty() {
let total = failed.len(); let total = failed.len();
failed.truncate(MAX_ERRORS_TO_SHOW); 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 { for err in failed {
log::error!(" {}", err); log::error!(" {}", err);
} }
@@ -148,7 +181,7 @@ pub fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec<String>)
let total = tag_error.len(); let total = tag_error.len();
tag_error.truncate(MAX_TAG_ERRORS_TO_SHOW); 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 { for err in tag_error {
log::error!(" {}", err); log::error!(" {}", err);
} }
@@ -159,149 +192,6 @@ pub fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec<String>)
Ok(()) Ok(())
} }
pub fn analyze_cue_tracks(tracks: Vec<cue::CueTrack>) -> mpsc::IntoIter<(cue::CueTrack, BlissResult<Song>)> {
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<Song>)>,
Receiver<(cue::CueTrack, BlissResult<Song>)>,
) = 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<cue::CueTrack>) -> 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<String> = 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<PathBuf>, dry_run: bool, keep_old: bool, max_num_tracks: usize) { pub fn analyse_files(db_path: &str, mpaths: &Vec<PathBuf>, dry_run: bool, keep_old: bool, max_num_tracks: usize) {
let mut db = db::Db::new(&String::from(db_path)); let mut db = db::Db::new(&String::from(db_path));
let mut track_count_left = max_num_tracks; let mut track_count_left = max_num_tracks;
@@ -316,49 +206,31 @@ pub fn analyse_files(db_path: &str, mpaths: &Vec<PathBuf>, dry_run: bool, keep_o
let mpath = path.clone(); let mpath = path.clone();
let cur = path.clone(); let cur = path.clone();
let mut track_paths: Vec<String> = Vec::new(); let mut track_paths: Vec<String> = Vec::new();
let mut cue_tracks: Vec<cue::CueTrack> = Vec::new();
if mpaths.len() > 1 { 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 { } 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(); track_paths.sort();
log::info!("Num new tracks: {}", track_paths.len()); log::info!("Num new files: {}", track_paths.len());
if !cue_tracks.is_empty() {
log::info!("Num new cue tracks: {}", cue_tracks.len());
}
if dry_run { 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:"); log::info!("The following need to be analysed:");
for track in track_paths { for track in track_paths {
log::info!(" {}", track); log::info!(" {}", track);
} }
for track in cue_tracks {
log::info!(" {}", track.track_path.to_string_lossy());
}
} }
} else { } else {
if max_num_tracks > 0 { if max_num_tracks > 0 {
if track_paths.len() > track_count_left { 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_paths.truncate(track_count_left);
} }
track_count_left -= track_paths.len(); 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() { if !track_paths.is_empty() {
match analyse_new_files(&db, &mpath, track_paths) { match analyse_new_files(&db, &mpath, track_paths) {
@@ -366,17 +238,11 @@ pub fn analyse_files(db_path: &str, mpaths: &Vec<PathBuf>, dry_run: bool, keep_o
Err(e) => { log::error!("Analysis returned error: {}", e); } Err(e) => { log::error!("Analysis returned error: {}", e); }
} }
} else { } else {
log::info!("No new tracks to analyse"); log::info!("No new files 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);
}
} }
if max_num_tracks > 0 && track_count_left <= 0 { if max_num_tracks > 0 && track_count_left <= 0 {
log::info!("Track limit reached"); log::info!("File limit reached");
break; break;
} }
} }
-97
View File
@@ -1,97 +0,0 @@
/**
* Analyse music with Bliss
*
* Copyright (c) 2022 Craig Drummond <craig.p.drummond@gmail.com>
* 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<CueTrack> {
let mut resp: Vec<CueTrack> = 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
}
+4 -3
View File
@@ -6,7 +6,6 @@
* *
**/ **/
use crate::cue;
use crate::tags; use crate::tags;
use bliss_audio::{Analysis, AnalysisIndex}; use bliss_audio::{Analysis, AnalysisIndex};
use indicatif::{ProgressBar, ProgressStyle}; use indicatif::{ProgressBar, ProgressStyle};
@@ -15,6 +14,8 @@ use std::convert::TryInto;
use std::path::PathBuf; use std::path::PathBuf;
use std::process; use std::process;
pub const CUE_MARKER: &str = ".CUE_TRACK.";
pub struct FileMetadata { pub struct FileMetadata {
pub rowid: usize, pub rowid: usize,
pub file: String, pub file: String,
@@ -173,7 +174,7 @@ impl Db {
for tr in track_iter { 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(); let orig_path = db_path.clone();
match orig_path.find(cue::MARKER) { match orig_path.find(CUE_MARKER) {
Some(s) => { Some(s) => {
db_path.truncate(s); db_path.truncate(s);
} }
@@ -266,7 +267,7 @@ impl Db {
let mut updated = 0; let mut updated = 0;
for tr in track_iter { for tr in track_iter {
let dbtags = tr.unwrap(); let dbtags = tr.unwrap();
if !dbtags.file.contains(cue::MARKER) { if !dbtags.file.contains(CUE_MARKER) {
let dtags = Metadata { let dtags = Metadata {
title: dbtags.title.unwrap_or_default(), title: dbtags.title.unwrap_or_default(),
artist: dbtags.artist.unwrap_or_default(), artist: dbtags.artist.unwrap_or_default(),
+4 -5
View File
@@ -15,7 +15,6 @@ use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use std::process; use std::process;
mod analyse; mod analyse;
mod cue;
mod db; mod db;
mod tags; mod tags;
mod upload; mod upload;
@@ -33,7 +32,7 @@ fn main() {
let mut dry_run: bool = false; let mut dry_run: bool = false;
let mut task = "".to_string(); let mut task = "".to_string();
let mut lms_host = "127.0.0.1".to_string(); let mut lms_host = "127.0.0.1".to_string();
let mut max_num_tracks: usize = 0; let mut max_num_files: usize = 0;
let mut music_paths: Vec<PathBuf> = Vec::new(); let mut music_paths: Vec<PathBuf> = Vec::new();
match dirs::home_dir() { 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 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 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 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 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 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 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.refer(&mut task).add_argument("task", Store, "Task to perform; analyse, tags, ignore, upload, stopmixer.");
arg_parse.parse_args_or_exit(); arg_parse.parse_args_or_exit();
} }
@@ -176,7 +175,7 @@ fn main() {
} }
analyse::update_ignore(&db_path, &ignore_path); analyse::update_ignore(&db_path, &ignore_path);
} else { } 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);
} }
} }
} }