mirror of
https://github.com/CDrummond/bliss-analyser.git
synced 2025-04-08 05:00:02 +03:00
Add support for analysing cue tracks
This commit is contained in:
parent
05532ec6cd
commit
caa77bd847
301
Cargo.lock
generated
301
Cargo.lock
generated
@ -69,6 +69,12 @@ 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"
|
||||
@ -113,7 +119,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bliss-analyser"
|
||||
version = "0.0.2"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argparse",
|
||||
@ -122,12 +128,17 @@ dependencies = [
|
||||
"configparser",
|
||||
"dirs",
|
||||
"env_logger",
|
||||
"hhmmss",
|
||||
"indicatif",
|
||||
"lofty",
|
||||
"log",
|
||||
"num_cpus",
|
||||
"rcue",
|
||||
"regex",
|
||||
"rusqlite",
|
||||
"subprocess",
|
||||
"substring",
|
||||
"tempdir",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
@ -254,7 +265,7 @@ dependencies = [
|
||||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"time",
|
||||
"time 0.1.44",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@ -294,6 +305,12 @@ 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"
|
||||
@ -406,6 +423,12 @@ 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"
|
||||
@ -496,6 +519,12 @@ 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"
|
||||
@ -579,6 +608,16 @@ 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"
|
||||
@ -627,6 +666,12 @@ 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"
|
||||
@ -800,7 +845,7 @@ dependencies = [
|
||||
"noisy_float",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -996,6 +1041,12 @@ 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"
|
||||
@ -1027,6 +1078,19 @@ 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"
|
||||
@ -1035,7 +1099,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_core 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1045,9 +1109,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.6.3",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@ -1088,6 +1167,20 @@ dependencies = [
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rcue"
|
||||
version = "0.1.0"
|
||||
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"
|
||||
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.57"
|
||||
@ -1122,6 +1215,15 @@ 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"
|
||||
@ -1181,6 +1283,15 @@ 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"
|
||||
@ -1207,6 +1318,12 @@ 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"
|
||||
@ -1223,6 +1340,49 @@ 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"
|
||||
@ -1235,6 +1395,21 @@ 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"
|
||||
@ -1253,6 +1428,64 @@ 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"
|
||||
@ -1277,6 +1510,16 @@ 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"
|
||||
@ -1297,6 +1540,16 @@ 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"
|
||||
@ -1347,6 +1600,44 @@ 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"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bliss-analyser"
|
||||
version = "0.0.2"
|
||||
version = "0.1.0"
|
||||
authors = ["Craig Drummond <craig.p.drummond@gmail.com>"]
|
||||
edition = "2018"
|
||||
license = "GPL-3.0-only"
|
||||
@ -24,3 +24,8 @@ 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"
|
||||
|
@ -1,3 +1,7 @@
|
||||
0.1.0
|
||||
-----
|
||||
1. Add support for analysing CUE files.
|
||||
|
||||
0.0.2
|
||||
-----
|
||||
1. Package vcruntime140.dll with Windows ZIP.
|
||||
|
212
src/analyse.rs
212
src/analyse.rs
@ -7,19 +7,29 @@
|
||||
**/
|
||||
|
||||
use anyhow::{Result};
|
||||
use bliss_audio::{library::analyze_paths_streaming};
|
||||
use bliss_audio::{library::analyze_paths_streaming, BlissResult, Song};
|
||||
use hhmmss::Hhmmss;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
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 subprocess::{Popen, PopenConfig};
|
||||
use tempdir::TempDir;
|
||||
use num_cpus;
|
||||
use crate::cue;
|
||||
use crate::db;
|
||||
use crate::tags;
|
||||
|
||||
const DONT_ANALYSE:&str = ".notmusic";
|
||||
const MAX_TAG_ERRORS_TO_SHOW:usize = 25;
|
||||
|
||||
fn get_file_list(db:&mut db::Db, mpath:&PathBuf, path:&PathBuf, track_paths:&mut Vec<String>) {
|
||||
fn get_file_list(db:&mut db::Db, mpath:&PathBuf, path:&PathBuf, track_paths:&mut Vec<String>, cue_tracks:&mut Vec<cue::CueTrack>) {
|
||||
if path.is_dir() {
|
||||
match path.read_dir() {
|
||||
Ok(items) => {
|
||||
@ -33,7 +43,7 @@ fn get_file_list(db:&mut db::Db, mpath:&PathBuf, path:&PathBuf, track_paths:&mut
|
||||
if check.exists() {
|
||||
log::info!("Skipping '{}', found '{}'", pb.to_string_lossy(), DONT_ANALYSE);
|
||||
} else {
|
||||
get_file_list(db, mpath, &entry.path(), track_paths);
|
||||
get_file_list(db, mpath, &entry.path(), track_paths, cue_tracks);
|
||||
}
|
||||
} else if entry.path().is_file() {
|
||||
let e = pb.extension();
|
||||
@ -42,10 +52,35 @@ fn get_file_list(db:&mut db::Db, mpath:&PathBuf, path:&PathBuf, track_paths:&mut
|
||||
if ext=="m4a" || ext=="mp3" || ext=="ogg" || ext=="flac" || ext=="opus" {
|
||||
match pb.strip_prefix(mpath) {
|
||||
Ok(stripped) => {
|
||||
let mut cue = pb.clone();
|
||||
cue.set_extension("cue");
|
||||
if cue.exists() {
|
||||
log::warn!("Found CUE album '{}' - not currently handled!", pb.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);
|
||||
let mut analyse = false;
|
||||
for track in this_cue_tracks.iter() {
|
||||
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 {
|
||||
analyse = true;
|
||||
}
|
||||
},
|
||||
Err(_) => { }
|
||||
}
|
||||
},
|
||||
Err(_) => { }
|
||||
}
|
||||
}
|
||||
|
||||
if analyse {
|
||||
for track in this_cue_tracks {
|
||||
cue_tracks.push(track);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let spb = stripped.to_path_buf();
|
||||
let sname = String::from(spb.to_string_lossy());
|
||||
@ -127,19 +162,172 @@ pub fn analyse_new_files(db:&db::Db, mpath: &PathBuf, track_paths:Vec<String>) -
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn analyze_cue_streaming(tracks: Vec<cue::CueTrack>,) -> BlissResult<Receiver<(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 Ok(rx);
|
||||
}
|
||||
|
||||
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 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 track_path = String::from(cue_track.track_path.to_string_lossy());
|
||||
let mut tmp_file = PathBuf::from(dir.path());
|
||||
tmp_file.push(format!("{}.mp3", idx));
|
||||
idx += 1;
|
||||
|
||||
log::debug!("Extracting '{}'", track_path);
|
||||
if cue_track.duration<last_track_duration {
|
||||
match Popen::create(&["ffmpeg", "-hide_banner", "-loglevel", "panic", "-i", &audio_path, "-b:a", "128k",
|
||||
"-ss", &cue_track.start.hhmmss(), "-t", &cue_track.duration.hhmmss(), &tmp_file.to_string_lossy()], PopenConfig::default()) {
|
||||
Ok(mut proc) => {
|
||||
match proc.wait() {
|
||||
Ok(_) => { },
|
||||
Err(_) => { }
|
||||
}
|
||||
},
|
||||
Err(e) => { log::error!("Wait failed for ffmpeg. {}", e); }
|
||||
}
|
||||
} else {
|
||||
match Popen::create(&["ffmpeg", "-hide_banner", "-loglevel", "panic", "-i", &audio_path, "-b:a", "128k",
|
||||
"-ss", &cue_track.start.hhmmss(), &tmp_file.to_string_lossy()], PopenConfig::default()) {
|
||||
Ok(mut proc) => {
|
||||
match proc.wait() {
|
||||
Ok(_) => { },
|
||||
Err(e) => { log::error!("Wait failed for ffmpeg. {}", e); }
|
||||
}
|
||||
},
|
||||
Err(e) => { log::error!("Failed to call ffmpeg. {}", e); }
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => { log::error!("Failed to create temp folder. {}", e); }
|
||||
}
|
||||
});
|
||||
handles.push(child);
|
||||
}
|
||||
|
||||
Ok(rx)
|
||||
}
|
||||
|
||||
pub fn analyse_new_cue_tracks(db:&db::Db, mpath: &PathBuf, cue_tracks:Vec<cue::CueTrack>) -> Result<()> {
|
||||
let total = cue_tracks.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 results = analyze_cue_streaming(cue_tracks)?;
|
||||
let mut analysed = 0;
|
||||
let mut failed = 0;
|
||||
let mut tag_error:Vec<String> = Vec::new();
|
||||
|
||||
log::info!("Analysing new cue tracks");
|
||||
for (track, result) in results {
|
||||
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));
|
||||
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(_) => {
|
||||
failed += 1;
|
||||
}
|
||||
};
|
||||
pb.inc(1);
|
||||
}
|
||||
pb.finish_with_message(format!("{} Analysed. {} Failure(s).", analysed, failed));
|
||||
if !tag_error.is_empty() {
|
||||
let total = tag_error.len();
|
||||
tag_error.truncate(MAX_TAG_ERRORS_TO_SHOW);
|
||||
|
||||
log::error!("Failed to read tags of the folling track(s):");
|
||||
for err in tag_error {
|
||||
log::error!(" {}", err);
|
||||
}
|
||||
if total>MAX_TAG_ERRORS_TO_SHOW {
|
||||
log::error!(" + {} other(s)", total - MAX_TAG_ERRORS_TO_SHOW);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn analyse_files(db_path: &str, mpath: &PathBuf, dry_run:bool, keep_old:bool, max_num_tracks:usize) {
|
||||
let mut track_paths:Vec<String> = Vec::new();
|
||||
let mut cue_tracks:Vec<cue::CueTrack> = Vec::new();
|
||||
let mut db = db::Db::new(&String::from(db_path));
|
||||
let cur = PathBuf::from(mpath);
|
||||
|
||||
db.init();
|
||||
log::info!("Looking for new tracks");
|
||||
get_file_list(&mut db, mpath, &cur, &mut track_paths);
|
||||
get_file_list(&mut db, mpath, &cur, &mut track_paths, &mut cue_tracks);
|
||||
log::info!("Num new tracks: {}", track_paths.len());
|
||||
if !cue_tracks.is_empty() {
|
||||
log::info!("Num new cue tracks: {}", cue_tracks.len());
|
||||
}
|
||||
if !dry_run && max_num_tracks>0 && track_paths.len()>max_num_tracks {
|
||||
log::info!("Only analysing {} tracks", max_num_tracks);
|
||||
track_paths.truncate(max_num_tracks);
|
||||
}
|
||||
if !dry_run && max_num_tracks>0 && cue_tracks.len()>max_num_tracks {
|
||||
log::info!("Only analysing {} cue tracks", max_num_tracks);
|
||||
cue_tracks.truncate(max_num_tracks);
|
||||
}
|
||||
if !keep_old {
|
||||
db.remove_old(mpath, dry_run);
|
||||
}
|
||||
@ -147,11 +335,17 @@ pub fn analyse_files(db_path: &str, mpath: &PathBuf, dry_run:bool, keep_old:bool
|
||||
if track_paths.len()>0 {
|
||||
match analyse_new_files(&db, mpath, track_paths) {
|
||||
Ok(_) => { },
|
||||
Err(_) => { }
|
||||
Err(e) => { log::error!("Analysis returned error: {}", e); }
|
||||
}
|
||||
} else {
|
||||
log::info!("No new tracks to analyse");
|
||||
}
|
||||
if !cue_tracks.is_empty() {
|
||||
match analyse_new_cue_tracks(&db, mpath, cue_tracks) {
|
||||
Ok(_) => { },
|
||||
Err(e) => { log::error!("Cue analysis returned error: {}", e); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.close();
|
||||
|
87
src/cue.rs
Normal file
87
src/cue.rs
Normal file
@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 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*7;
|
||||
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 analyse:bool
|
||||
}
|
||||
|
||||
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(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 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!("{}{}{}.mp3", ext, MARKER, resp.len()));
|
||||
let 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),
|
||||
analyse: false
|
||||
};
|
||||
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::new(0, 0);
|
||||
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
|
||||
}
|
50
src/db.rs
50
src/db.rs
@ -12,6 +12,7 @@ use rusqlite::{Connection, params};
|
||||
use std::convert::TryInto;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use crate::cue;
|
||||
use crate::tags;
|
||||
|
||||
pub struct FileMetadata {
|
||||
@ -134,7 +135,7 @@ impl Db {
|
||||
Err(e) => { log::error!("Failed to insert '{}' into database. {}", path, e); }
|
||||
}
|
||||
} else {
|
||||
match self.conn.execute("UPDATE Tracks SET Title=?, Artist=?, AlbumArtist=?, Album=?, Genre=?, Duration=?, Tempo=?, Zcr=?, MeanSpectralCentroid=?, StdDevSpectralCentroid=?, MeanSpectralRolloff=?, StdDevSpectralRolloff=?, MeanSpectralFlatness=?, StdDevSpectralFlatness=?, MeanLoudness=?, StdDevLoudness=?, Chroma1=?, Chroma2=?, Chroma3=?, Chroma4=?, Chroma5=?, Chroma6=?, Chroma7=?, Chroma8=?, Chroma9=?, Chroma10=? WHERE rowid=?);",
|
||||
match self.conn.execute("UPDATE Tracks SET Title=?, Artist=?, AlbumArtist=?, Album=?, Genre=?, Duration=?, Tempo=?, Zcr=?, MeanSpectralCentroid=?, StdDevSpectralCentroid=?, MeanSpectralRolloff=?, StdDevSpectralRolloff=?, MeanSpectralFlatness=?, StdDevSpectralFlatness=?, MeanLoudness=?, StdDevLoudness=?, Chroma1=?, Chroma2=?, Chroma3=?, Chroma4=?, Chroma5=?, Chroma6=?, Chroma7=?, Chroma8=?, Chroma9=?, Chroma10=? WHERE rowid=?;",
|
||||
params![meta.title, meta.artist, meta.album_artist, meta.album, meta.genre, meta.duration,
|
||||
analysis[AnalysisIndex::Tempo], analysis[AnalysisIndex::Zcr], analysis[AnalysisIndex::MeanSpectralCentroid], analysis[AnalysisIndex::StdDeviationSpectralCentroid], analysis[AnalysisIndex::MeanSpectralRolloff],
|
||||
analysis[AnalysisIndex::StdDeviationSpectralRolloff], analysis[AnalysisIndex::MeanSpectralFlatness], analysis[AnalysisIndex::StdDeviationSpectralFlatness], analysis[AnalysisIndex::MeanLoudness], analysis[AnalysisIndex::StdDeviationLoudness],
|
||||
@ -159,10 +160,17 @@ 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) {
|
||||
Some(s) => {
|
||||
db_path.truncate(s);
|
||||
},
|
||||
None => { }
|
||||
}
|
||||
if cfg!(windows) {
|
||||
db_path = db_path.replace("/", "\\");
|
||||
}
|
||||
let path = mpath.join(PathBuf::from(db_path.clone()));
|
||||
//log::debug!("Check if '{}' exists.", path.to_string_lossy());
|
||||
|
||||
if !path.exists() {
|
||||
to_remove.push(orig_path);
|
||||
@ -173,7 +181,7 @@ impl Db {
|
||||
if !dry_run && num_to_remove>0 {
|
||||
let count_before = self.get_track_count();
|
||||
for t in to_remove {
|
||||
log::debug!("Remove '{}'", t);
|
||||
//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) }
|
||||
@ -224,24 +232,26 @@ impl Db {
|
||||
let mut updated = 0;
|
||||
for tr in track_iter {
|
||||
let dbtags = tr.unwrap();
|
||||
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));
|
||||
let path = String::from(mpath.join(&dbtags.file).to_string_lossy());
|
||||
let ftags = tags::read(&path);
|
||||
if ftags.title.is_empty() && ftags.artist.is_empty() && ftags.album_artist.is_empty() && ftags.album.is_empty() && ftags.genre.is_empty() {
|
||||
log::error!("Failed to read tags of '{}'", dbtags.file);
|
||||
} else if ftags.duration!=dtags.duration || ftags.title!=dtags.title || ftags.artist!=dtags.artist || ftags.album_artist!=dtags.album_artist || ftags.album!=dtags.album || ftags.genre!=dtags.genre {
|
||||
match self.conn.execute("UPDATE Tracks SET Title=?, Artist=?, AlbumArtist=?, Album=?, Genre=?, Duration=? WHERE rowid=?;",
|
||||
params![ftags.title, ftags.artist, ftags.album_artist, ftags.album, ftags.genre, ftags.duration, dbtags.rowid]) {
|
||||
Ok(_) => { updated += 1; },
|
||||
Err(e) => { log::error!("Failed to update tags of '{}'. {}", dbtags.file, e); }
|
||||
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
|
||||
};
|
||||
pb.set_message(format!("{}", dbtags.file));
|
||||
let path = String::from(mpath.join(&dbtags.file).to_string_lossy());
|
||||
let ftags = tags::read(&path);
|
||||
if ftags.title.is_empty() && ftags.artist.is_empty() && ftags.album_artist.is_empty() && ftags.album.is_empty() && ftags.genre.is_empty() {
|
||||
log::error!("Failed to read tags of '{}'", dbtags.file);
|
||||
} else if ftags.duration!=dtags.duration || ftags.title!=dtags.title || ftags.artist!=dtags.artist || ftags.album_artist!=dtags.album_artist || ftags.album!=dtags.album || ftags.genre!=dtags.genre {
|
||||
match self.conn.execute("UPDATE Tracks SET Title=?, Artist=?, AlbumArtist=?, Album=?, Genre=?, Duration=? WHERE rowid=?;",
|
||||
params![ftags.title, ftags.artist, ftags.album_artist, ftags.album, ftags.genre, ftags.duration, dbtags.rowid]) {
|
||||
Ok(_) => { updated += 1; },
|
||||
Err(e) => { log::error!("Failed to update tags of '{}'. {}", dbtags.file, e); }
|
||||
}
|
||||
}
|
||||
}
|
||||
pb.inc(1);
|
||||
|
@ -14,6 +14,7 @@ use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
mod analyse;
|
||||
mod cue;
|
||||
mod db;
|
||||
mod tags;
|
||||
mod upload;
|
||||
|
Loading…
x
Reference in New Issue
Block a user