forked from third-party-mirrors/bliss-analyser
fmt
Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com>
This commit is contained in:
parent
0153b85b65
commit
e0a6759af5
155
src/analyse.rs
155
src/analyse.rs
@ -1,3 +1,6 @@
|
|||||||
|
use crate::cue;
|
||||||
|
use crate::db;
|
||||||
|
use crate::tags;
|
||||||
/**
|
/**
|
||||||
* Analyse music with Bliss
|
* Analyse music with Bliss
|
||||||
*
|
*
|
||||||
@ -5,32 +8,34 @@
|
|||||||
* GPLv3 license.
|
* GPLv3 license.
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
|
use anyhow::Result;
|
||||||
use anyhow::{Result};
|
|
||||||
use bliss_audio::{library::analyze_paths_streaming, BlissResult, Song};
|
use bliss_audio::{library::analyze_paths_streaming, BlissResult, Song};
|
||||||
use hhmmss::Hhmmss;
|
use hhmmss::Hhmmss;
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
use num_cpus;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufRead, BufReader};
|
use std::io::{BufRead, BufReader};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::Duration;
|
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::sync::mpsc::{Receiver, Sender};
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
use subprocess::{Exec, NullFile};
|
use subprocess::{Exec, NullFile};
|
||||||
use tempdir::TempDir;
|
use tempdir::TempDir;
|
||||||
use num_cpus;
|
|
||||||
use crate::cue;
|
|
||||||
use crate::db;
|
|
||||||
use crate::tags;
|
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
fn get_file_list(db:&mut db::Db, mpath:&PathBuf, path:&PathBuf, track_paths:&mut Vec<String>, cue_tracks:&mut Vec<cue::CueTrack>) {
|
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() {
|
if path.is_dir() {
|
||||||
match path.read_dir() {
|
match path.read_dir() {
|
||||||
Ok(items) => {
|
Ok(items) => {
|
||||||
@ -42,36 +47,56 @@ fn get_file_list(db:&mut db::Db, mpath:&PathBuf, path:&PathBuf, track_paths:&mut
|
|||||||
let mut check = pb.clone();
|
let mut check = pb.clone();
|
||||||
check.push(PathBuf::from(DONT_ANALYSE));
|
check.push(PathBuf::from(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, &entry.path(), track_paths, cue_tracks);
|
get_file_list(
|
||||||
|
db,
|
||||||
|
mpath,
|
||||||
|
&entry.path(),
|
||||||
|
track_paths,
|
||||||
|
cue_tracks,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if entry.path().is_file() {
|
} else if entry.path().is_file() {
|
||||||
let e = pb.extension();
|
let e = pb.extension();
|
||||||
if e.is_some() {
|
if e.is_some() {
|
||||||
let ext = e.unwrap().to_string_lossy();
|
let ext = e.unwrap().to_string_lossy();
|
||||||
if ext=="m4a" || ext=="mp3" || ext=="ogg" || ext=="flac" || ext=="opus" {
|
if ext == "m4a"
|
||||||
|
|| ext == "mp3"
|
||||||
|
|| ext == "ogg"
|
||||||
|
|| ext == "flac"
|
||||||
|
|| ext == "opus"
|
||||||
|
{
|
||||||
match pb.strip_prefix(mpath) {
|
match pb.strip_prefix(mpath) {
|
||||||
Ok(stripped) => {
|
Ok(stripped) => {
|
||||||
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
|
// Found a CUE file, try to parse and then check if tracks exists in DB
|
||||||
let this_cue_tracks = cue::parse(&pb, &cue_file);
|
let this_cue_tracks =
|
||||||
|
cue::parse(&pb, &cue_file);
|
||||||
for track in this_cue_tracks {
|
for track in this_cue_tracks {
|
||||||
match track.track_path.strip_prefix(mpath) {
|
match track.track_path.strip_prefix(mpath) {
|
||||||
Ok(tstripped) => {
|
Ok(tstripped) => {
|
||||||
let spb = tstripped.to_path_buf();
|
let spb = tstripped.to_path_buf();
|
||||||
let sname = String::from(spb.to_string_lossy());
|
let sname = String::from(
|
||||||
|
spb.to_string_lossy(),
|
||||||
|
);
|
||||||
match db.get_rowid(&sname) {
|
match db.get_rowid(&sname) {
|
||||||
Ok(id) => {
|
Ok(id) => {
|
||||||
if id <= 0 {
|
if id <= 0 {
|
||||||
cue_tracks.push(track.clone());
|
cue_tracks.push(
|
||||||
|
track.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,23 +106,25 @@ fn get_file_list(db:&mut db::Db, mpath:&PathBuf, path:&PathBuf, track_paths:&mut
|
|||||||
match db.get_rowid(&sname) {
|
match db.get_rowid(&sname) {
|
||||||
Ok(id) => {
|
Ok(id) => {
|
||||||
if id <= 0 {
|
if id <= 0 {
|
||||||
track_paths.push(String::from(pb.to_string_lossy()));
|
track_paths.push(String::from(
|
||||||
|
pb.to_string_lossy(),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,20 +154,28 @@ pub fn analyse_new_files(db:&db::Db, mpath: &PathBuf, track_paths:Vec<String>) -
|
|||||||
Ok(track) => {
|
Ok(track) => {
|
||||||
let cpath = String::from(path);
|
let cpath = String::from(path);
|
||||||
let meta = tags::read(&cpath);
|
let meta = tags::read(&cpath);
|
||||||
if meta.title.is_empty() && meta.artist.is_empty() && meta.album.is_empty() && meta.genre.is_empty() {
|
if meta.title.is_empty()
|
||||||
|
&& meta.artist.is_empty()
|
||||||
|
&& meta.album.is_empty()
|
||||||
|
&& meta.genre.is_empty()
|
||||||
|
{
|
||||||
tag_error.push(sname.clone());
|
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) => {
|
Err(e) => {
|
||||||
failed.push(format!("{} - {}", sname, e));
|
failed.push(format!("{} - {}", sname, e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
pb.inc(1);
|
pb.inc(1);
|
||||||
}
|
}
|
||||||
pb.finish_with_message(format!("{} Analysed. {} Failure(s).", analysed, failed.len()));
|
pb.finish_with_message(format!(
|
||||||
|
"{} Analysed. {} Failure(s).",
|
||||||
|
analysed,
|
||||||
|
failed.len()
|
||||||
|
));
|
||||||
if !failed.is_empty() {
|
if !failed.is_empty() {
|
||||||
let total = failed.len();
|
let total = failed.len();
|
||||||
failed.truncate(MAX_ERRORS_TO_SHOW);
|
failed.truncate(MAX_ERRORS_TO_SHOW);
|
||||||
@ -168,7 +203,9 @@ pub fn analyse_new_files(db:&db::Db, mpath: &PathBuf, track_paths:Vec<String>) -
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn analyze_cue_streaming(tracks: Vec<cue::CueTrack>,) -> BlissResult<Receiver<(cue::CueTrack, BlissResult<Song>)>> {
|
pub fn analyze_cue_streaming(
|
||||||
|
tracks: Vec<cue::CueTrack>,
|
||||||
|
) -> BlissResult<Receiver<(cue::CueTrack, BlissResult<Song>)>> {
|
||||||
let num_cpus = num_cpus::get();
|
let num_cpus = num_cpus::get();
|
||||||
let last_track_duration = Duration::new(cue::LAST_TRACK_DURATION, 0);
|
let last_track_duration = Duration::new(cue::LAST_TRACK_DURATION, 0);
|
||||||
|
|
||||||
@ -209,21 +246,29 @@ pub fn analyze_cue_streaming(tracks: Vec<cue::CueTrack>,) -> BlissResult<Receive
|
|||||||
idx += 1;
|
idx += 1;
|
||||||
|
|
||||||
log::debug!("Extracting '{}'", track_path);
|
log::debug!("Extracting '{}'", track_path);
|
||||||
match Exec::cmd("ffmpeg").arg("-i").arg(&audio_path)
|
match Exec::cmd("ffmpeg")
|
||||||
.arg("-ss").arg(&cue_track.start.hhmmss())
|
.arg("-i")
|
||||||
.arg("-t").arg(&cue_track.duration.hhmmss())
|
.arg(&audio_path)
|
||||||
.arg("-c").arg("copy")
|
.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()))
|
.arg(String::from(tmp_file.to_string_lossy()))
|
||||||
.stderr(NullFile)
|
.stderr(NullFile)
|
||||||
.join() {
|
.join()
|
||||||
Ok(_) => { },
|
{
|
||||||
Err(e) => { log::error!("Failed to call ffmpeg. {}", e); }
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to call ffmpeg. {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cfg!(windows) {
|
if !cfg!(windows) {
|
||||||
// ffmpeg seeks to break echo on terminal? 'stty echo' restores...
|
// ffmpeg seeks to break echo on terminal? 'stty echo' restores...
|
||||||
match Exec::cmd("stty").arg("echo").join() {
|
match Exec::cmd("stty").arg("echo").join() {
|
||||||
Ok(_) => { },
|
Ok(_) => {}
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,15 +286,17 @@ pub fn analyze_cue_streaming(tracks: Vec<cue::CueTrack>,) -> BlissResult<Receive
|
|||||||
tx_thread.send((cue_track, song)).unwrap();
|
tx_thread.send((cue_track, song)).unwrap();
|
||||||
}
|
}
|
||||||
match fs::remove_file(tmp_file) {
|
match fs::remove_file(tmp_file) {
|
||||||
Ok(_) => { },
|
Ok(_) => {}
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::error!("Failed to create temp file");
|
log::error!("Failed to create temp file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(e) => { log::error!("Failed to create temp folder. {}", e); }
|
Err(e) => {
|
||||||
|
log::error!("Failed to create temp folder. {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
handles.push(child);
|
handles.push(child);
|
||||||
@ -258,7 +305,11 @@ pub fn analyze_cue_streaming(tracks: Vec<cue::CueTrack>,) -> BlissResult<Receive
|
|||||||
Ok(rx)
|
Ok(rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn analyse_new_cue_tracks(db:&db::Db, mpath: &PathBuf, cue_tracks:Vec<cue::CueTrack>) -> Result<()> {
|
pub fn analyse_new_cue_tracks(
|
||||||
|
db: &db::Db,
|
||||||
|
mpath: &PathBuf,
|
||||||
|
cue_tracks: Vec<cue::CueTrack>,
|
||||||
|
) -> Result<()> {
|
||||||
let total = cue_tracks.len();
|
let total = cue_tracks.len();
|
||||||
let pb = ProgressBar::new(total.try_into().unwrap());
|
let pb = ProgressBar::new(total.try_into().unwrap());
|
||||||
let style = ProgressStyle::default_bar()
|
let style = ProgressStyle::default_bar()
|
||||||
@ -284,19 +335,23 @@ pub fn analyse_new_cue_tracks(db:&db::Db, mpath: &PathBuf, cue_tracks:Vec<cue::C
|
|||||||
album_artist: track.album_artist,
|
album_artist: track.album_artist,
|
||||||
album: track.album,
|
album: track.album,
|
||||||
genre: track.genre,
|
genre: track.genre,
|
||||||
duration:track.duration.as_secs() as u32
|
duration: track.duration.as_secs() as u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
db.add_track(&sname, &meta, &song.analysis);
|
db.add_track(&sname, &meta, &song.analysis);
|
||||||
analysed += 1;
|
analysed += 1;
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
failed.push(format!("{} - {}", sname, e));
|
failed.push(format!("{} - {}", sname, e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
pb.inc(1);
|
pb.inc(1);
|
||||||
}
|
}
|
||||||
pb.finish_with_message(format!("{} Analysed. {} Failure(s).", analysed, failed.len()));
|
pb.finish_with_message(format!(
|
||||||
|
"{} Analysed. {} Failure(s).",
|
||||||
|
analysed,
|
||||||
|
failed.len()
|
||||||
|
));
|
||||||
if !failed.is_empty() {
|
if !failed.is_empty() {
|
||||||
let total = failed.len();
|
let total = failed.len();
|
||||||
failed.truncate(MAX_ERRORS_TO_SHOW);
|
failed.truncate(MAX_ERRORS_TO_SHOW);
|
||||||
@ -312,7 +367,13 @@ pub fn analyse_new_cue_tracks(db:&db::Db, mpath: &PathBuf, cue_tracks:Vec<cue::C
|
|||||||
Ok(())
|
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;
|
||||||
|
|
||||||
@ -371,8 +432,10 @@ pub fn analyse_files(db_path: &str, mpaths: &Vec<PathBuf>, dry_run:bool, keep_ol
|
|||||||
|
|
||||||
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) {
|
||||||
Ok(_) => { },
|
Ok(_) => {}
|
||||||
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 tracks to analyse");
|
||||||
@ -380,8 +443,10 @@ pub fn analyse_files(db_path: &str, mpaths: &Vec<PathBuf>, dry_run:bool, keep_ol
|
|||||||
|
|
||||||
if !cue_tracks.is_empty() {
|
if !cue_tracks.is_empty() {
|
||||||
match analyse_new_cue_tracks(&db, &mpath, cue_tracks) {
|
match analyse_new_cue_tracks(&db, &mpath, cue_tracks) {
|
||||||
Ok(_) => { },
|
Ok(_) => {}
|
||||||
Err(e) => { log::error!("Cue analysis returned error: {}", e); }
|
Err(e) => {
|
||||||
|
log::error!("Cue analysis returned error: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
22
src/cue.rs
22
src/cue.rs
@ -5,7 +5,6 @@
|
|||||||
* GPLv3 license.
|
* GPLv3 license.
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
|
|
||||||
extern crate rcue;
|
extern crate rcue;
|
||||||
|
|
||||||
use rcue::parser::parse_from_file;
|
use rcue::parser::parse_from_file;
|
||||||
@ -26,7 +25,7 @@ pub struct CueTrack {
|
|||||||
pub album_artist: String,
|
pub album_artist: String,
|
||||||
pub genre: String,
|
pub genre: String,
|
||||||
pub start: Duration,
|
pub start: Duration,
|
||||||
pub duration:Duration
|
pub duration: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(audio_path: &PathBuf, cue_path: &PathBuf) -> Vec<CueTrack> {
|
pub fn parse(audio_path: &PathBuf, cue_path: &PathBuf) -> Vec<CueTrack> {
|
||||||
@ -49,7 +48,12 @@ pub fn parse(audio_path:&PathBuf, cue_path:&PathBuf) -> Vec<CueTrack> {
|
|||||||
Some((_, start)) => {
|
Some((_, start)) => {
|
||||||
let mut track_path = audio_path.clone();
|
let mut track_path = audio_path.clone();
|
||||||
let ext = audio_path.extension().unwrap().to_string_lossy();
|
let ext = audio_path.extension().unwrap().to_string_lossy();
|
||||||
track_path.set_extension(format!("{}{}{}", ext, MARKER, resp.len()+1));
|
track_path.set_extension(format!(
|
||||||
|
"{}{}{}",
|
||||||
|
ext,
|
||||||
|
MARKER,
|
||||||
|
resp.len() + 1
|
||||||
|
));
|
||||||
let mut ctrack = CueTrack {
|
let mut ctrack = CueTrack {
|
||||||
audio_path: audio_path.clone(),
|
audio_path: audio_path.clone(),
|
||||||
track_path: track_path,
|
track_path: track_path,
|
||||||
@ -68,19 +72,23 @@ pub fn parse(audio_path:&PathBuf, cue_path:&PathBuf) -> Vec<CueTrack> {
|
|||||||
let mut path = audio_path.clone();
|
let mut path = audio_path.clone();
|
||||||
path.set_extension("");
|
path.set_extension("");
|
||||||
match path.file_name() {
|
match path.file_name() {
|
||||||
Some(n) => { ctrack.album = String::from(n.to_string_lossy()); }
|
Some(n) => {
|
||||||
|
ctrack.album = String::from(n.to_string_lossy());
|
||||||
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp.push(ctrack);
|
resp.push(ctrack);
|
||||||
},
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(e) => { log::error!("Failed to parse '{}'. {}", cue_path.to_string_lossy(), e);}
|
Err(e) => {
|
||||||
|
log::error!("Failed to parse '{}'. {}", cue_path.to_string_lossy(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..(resp.len() - 1) {
|
for i in 0..(resp.len() - 1) {
|
||||||
|
116
src/db.rs
116
src/db.rs
@ -1,3 +1,5 @@
|
|||||||
|
use crate::cue;
|
||||||
|
use crate::tags;
|
||||||
/**
|
/**
|
||||||
* Analyse music with Bliss
|
* Analyse music with Bliss
|
||||||
*
|
*
|
||||||
@ -5,15 +7,12 @@
|
|||||||
* GPLv3 license.
|
* GPLv3 license.
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
|
|
||||||
use bliss_audio::{Analysis, AnalysisIndex};
|
use bliss_audio::{Analysis, AnalysisIndex};
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use rusqlite::{Connection, params};
|
use rusqlite::{params, Connection};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process;
|
use std::process;
|
||||||
use crate::cue;
|
|
||||||
use crate::tags;
|
|
||||||
|
|
||||||
pub struct FileMetadata {
|
pub struct FileMetadata {
|
||||||
pub rowid: usize,
|
pub rowid: usize,
|
||||||
@ -23,7 +22,7 @@ pub struct FileMetadata {
|
|||||||
pub album_artist: Option<String>,
|
pub album_artist: Option<String>,
|
||||||
pub album: Option<String>,
|
pub album: Option<String>,
|
||||||
pub genre: Option<String>,
|
pub genre: Option<String>,
|
||||||
pub duration:u32
|
pub duration: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
@ -32,11 +31,11 @@ pub struct Metadata {
|
|||||||
pub album_artist: String,
|
pub album_artist: String,
|
||||||
pub album: String,
|
pub album: String,
|
||||||
pub genre: String,
|
pub genre: String,
|
||||||
pub duration:u32
|
pub duration: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Db {
|
pub struct Db {
|
||||||
pub conn: Connection
|
pub conn: Connection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Db {
|
impl Db {
|
||||||
@ -77,15 +76,20 @@ impl Db {
|
|||||||
Chroma8 real,
|
Chroma8 real,
|
||||||
Chroma9 real,
|
Chroma9 real,
|
||||||
Chroma10 real
|
Chroma10 real
|
||||||
);",[]) {
|
);",
|
||||||
Ok(_) => { },
|
[],
|
||||||
|
) {
|
||||||
|
Ok(_) => {}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::error!("Failed to create DB table");
|
log::error!("Failed to create DB table");
|
||||||
process::exit(-1);
|
process::exit(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match self.conn.execute("CREATE UNIQUE INDEX IF NOT EXISTS Tracks_idx ON Tracks(File)", []) {
|
match self.conn.execute(
|
||||||
Ok(_) => { },
|
"CREATE UNIQUE INDEX IF NOT EXISTS Tracks_idx ON Tracks(File)",
|
||||||
|
[],
|
||||||
|
) {
|
||||||
|
Ok(_) => {}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::error!("Failed to create DB index");
|
log::error!("Failed to create DB index");
|
||||||
process::exit(-1);
|
process::exit(-1);
|
||||||
@ -95,7 +99,7 @@ impl Db {
|
|||||||
|
|
||||||
pub fn close(self) {
|
pub fn close(self) {
|
||||||
match self.conn.close() {
|
match self.conn.close() {
|
||||||
Ok(_) => { },
|
Ok(_) => {}
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,10 +109,12 @@ impl Db {
|
|||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
db_path = db_path.replace("\\", "/");
|
db_path = db_path.replace("\\", "/");
|
||||||
}
|
}
|
||||||
let mut stmt = self.conn.prepare("SELECT rowid FROM Tracks WHERE File=:path;")?;
|
let mut stmt = self
|
||||||
let track_iter = stmt.query_map(&[(":path", &db_path)], |row| {
|
.conn
|
||||||
Ok(row.get(0)?)
|
.prepare("SELECT rowid FROM Tracks WHERE File=:path;")?;
|
||||||
}).unwrap();
|
let track_iter = stmt
|
||||||
|
.query_map(&[(":path", &db_path)], |row| Ok(row.get(0)?))
|
||||||
|
.unwrap();
|
||||||
let mut rowid: usize = 0;
|
let mut rowid: usize = 0;
|
||||||
for tr in track_iter {
|
for tr in track_iter {
|
||||||
rowid = tr.unwrap();
|
rowid = tr.unwrap();
|
||||||
@ -145,7 +151,7 @@ impl Db {
|
|||||||
Err(e) => { log::error!("Failed to update '{}' in database. {}", path, e); }
|
Err(e) => { log::error!("Failed to update '{}' in database. {}", path, e); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,9 +159,7 @@ impl Db {
|
|||||||
pub fn remove_old(&self, mpaths: &Vec<PathBuf>, dry_run: bool) {
|
pub fn remove_old(&self, mpaths: &Vec<PathBuf>, dry_run: bool) {
|
||||||
log::info!("Looking for non-existant tracks");
|
log::info!("Looking for non-existant tracks");
|
||||||
let mut stmt = self.conn.prepare("SELECT File FROM Tracks;").unwrap();
|
let mut stmt = self.conn.prepare("SELECT File FROM Tracks;").unwrap();
|
||||||
let track_iter = stmt.query_map([], |row| {
|
let track_iter = stmt.query_map([], |row| Ok((row.get(0)?,))).unwrap();
|
||||||
Ok((row.get(0)?,))
|
|
||||||
}).unwrap();
|
|
||||||
let mut to_remove: Vec<String> = Vec::new();
|
let mut to_remove: Vec<String> = Vec::new();
|
||||||
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;
|
||||||
@ -163,7 +167,7 @@ impl Db {
|
|||||||
match orig_path.find(cue::MARKER) {
|
match orig_path.find(cue::MARKER) {
|
||||||
Some(s) => {
|
Some(s) => {
|
||||||
db_path.truncate(s);
|
db_path.truncate(s);
|
||||||
},
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
@ -198,9 +202,14 @@ impl Db {
|
|||||||
let count_before = self.get_track_count();
|
let count_before = self.get_track_count();
|
||||||
for t in to_remove {
|
for t in to_remove {
|
||||||
//log::debug!("Remove '{}'", t);
|
//log::debug!("Remove '{}'", t);
|
||||||
match self.conn.execute("DELETE FROM Tracks WHERE File = ?;", params![t]) {
|
match self
|
||||||
Ok(_) => { },
|
.conn
|
||||||
Err(e) => { log::error!("Failed to remove '{}' - {}", t, e) }
|
.execute("DELETE FROM Tracks WHERE File = ?;", params![t])
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to remove '{}' - {}", t, e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let count_now = self.get_track_count();
|
let count_now = self.get_track_count();
|
||||||
@ -213,9 +222,7 @@ impl Db {
|
|||||||
|
|
||||||
pub fn get_track_count(&self) -> usize {
|
pub fn get_track_count(&self) -> usize {
|
||||||
let mut stmt = self.conn.prepare("SELECT COUNT(*) FROM Tracks;").unwrap();
|
let mut stmt = self.conn.prepare("SELECT COUNT(*) FROM Tracks;").unwrap();
|
||||||
let track_iter = stmt.query_map([], |row| {
|
let track_iter = stmt.query_map([], |row| Ok(row.get(0)?)).unwrap();
|
||||||
Ok(row.get(0)?)
|
|
||||||
}).unwrap();
|
|
||||||
let mut count: usize = 0;
|
let mut count: usize = 0;
|
||||||
for tr in track_iter {
|
for tr in track_iter {
|
||||||
count = tr.unwrap();
|
count = tr.unwrap();
|
||||||
@ -229,11 +236,14 @@ impl Db {
|
|||||||
if total > 0 {
|
if total > 0 {
|
||||||
let pb = ProgressBar::new(total.try_into().unwrap());
|
let pb = ProgressBar::new(total.try_into().unwrap());
|
||||||
let style = ProgressStyle::default_bar()
|
let style = ProgressStyle::default_bar()
|
||||||
.template("[{elapsed_precise}] [{bar:25}] {percent:>3}% {pos:>6}/{len:6} {wide_msg}")
|
.template(
|
||||||
|
"[{elapsed_precise}] [{bar:25}] {percent:>3}% {pos:>6}/{len:6} {wide_msg}",
|
||||||
|
)
|
||||||
.progress_chars("=> ");
|
.progress_chars("=> ");
|
||||||
pb.set_style(style);
|
pb.set_style(style);
|
||||||
let mut stmt = self.conn.prepare("SELECT rowid, File, Title, Artist, AlbumArtist, Album, Genre, Duration FROM Tracks ORDER BY File ASC;").unwrap();
|
let mut stmt = self.conn.prepare("SELECT rowid, File, Title, Artist, AlbumArtist, Album, Genre, Duration FROM Tracks ORDER BY File ASC;").unwrap();
|
||||||
let track_iter = stmt.query_map([], |row| {
|
let track_iter = stmt
|
||||||
|
.query_map([], |row| {
|
||||||
Ok(FileMetadata {
|
Ok(FileMetadata {
|
||||||
rowid: row.get(0)?,
|
rowid: row.get(0)?,
|
||||||
file: row.get(1)?,
|
file: row.get(1)?,
|
||||||
@ -244,7 +254,8 @@ impl Db {
|
|||||||
genre: row.get(6)?,
|
genre: row.get(6)?,
|
||||||
duration: row.get(7)?,
|
duration: row.get(7)?,
|
||||||
})
|
})
|
||||||
}).unwrap();
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut updated = 0;
|
let mut updated = 0;
|
||||||
for tr in track_iter {
|
for tr in track_iter {
|
||||||
@ -256,7 +267,7 @@ impl Db {
|
|||||||
album_artist: dbtags.album_artist.unwrap_or(String::new()),
|
album_artist: dbtags.album_artist.unwrap_or(String::new()),
|
||||||
album: dbtags.album.unwrap_or(String::new()),
|
album: dbtags.album.unwrap_or(String::new()),
|
||||||
genre: dbtags.genre.unwrap_or(String::new()),
|
genre: dbtags.genre.unwrap_or(String::new()),
|
||||||
duration:dbtags.duration
|
duration: dbtags.duration,
|
||||||
};
|
};
|
||||||
pb.set_message(format!("{}", dbtags.file));
|
pb.set_message(format!("{}", dbtags.file));
|
||||||
|
|
||||||
@ -265,9 +276,20 @@ impl Db {
|
|||||||
if track_path.exists() {
|
if track_path.exists() {
|
||||||
let path = String::from(track_path.to_string_lossy());
|
let path = String::from(track_path.to_string_lossy());
|
||||||
let ftags = tags::read(&path);
|
let ftags = tags::read(&path);
|
||||||
if ftags.title.is_empty() && ftags.artist.is_empty() && ftags.album_artist.is_empty() && ftags.album.is_empty() && ftags.genre.is_empty() {
|
if ftags.title.is_empty()
|
||||||
|
&& ftags.artist.is_empty()
|
||||||
|
&& ftags.album_artist.is_empty()
|
||||||
|
&& ftags.album.is_empty()
|
||||||
|
&& ftags.genre.is_empty()
|
||||||
|
{
|
||||||
log::error!("Failed to read tags of '{}'", dbtags.file);
|
log::error!("Failed to read tags of '{}'", dbtags.file);
|
||||||
} else if ftags.duration!=dtags.duration || ftags.title!=dtags.title || ftags.artist!=dtags.artist || ftags.album_artist!=dtags.album_artist || ftags.album!=dtags.album || ftags.genre!=dtags.genre {
|
} else if ftags.duration != dtags.duration
|
||||||
|
|| ftags.title != dtags.title
|
||||||
|
|| ftags.artist != dtags.artist
|
||||||
|
|| ftags.album_artist != dtags.album_artist
|
||||||
|
|| ftags.album != dtags.album
|
||||||
|
|| ftags.genre != dtags.genre
|
||||||
|
{
|
||||||
match self.conn.execute("UPDATE Tracks SET Title=?, Artist=?, AlbumArtist=?, Album=?, Genre=?, Duration=? WHERE rowid=?;",
|
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]) {
|
params![ftags.title, ftags.artist, ftags.album_artist, ftags.album, ftags.genre, ftags.duration, dbtags.rowid]) {
|
||||||
Ok(_) => { updated += 1; },
|
Ok(_) => { updated += 1; },
|
||||||
@ -286,8 +308,10 @@ impl Db {
|
|||||||
|
|
||||||
pub fn clear_ignore(&self) {
|
pub fn clear_ignore(&self) {
|
||||||
match self.conn.execute("UPDATE Tracks SET Ignore=0;", []) {
|
match self.conn.execute("UPDATE Tracks SET Ignore=0;", []) {
|
||||||
Ok(_) => { },
|
Ok(_) => {}
|
||||||
Err(e) => { log::error!("Failed clear Ignore column. {}", e); }
|
Err(e) => {
|
||||||
|
log::error!("Failed clear Ignore column. {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,14 +319,24 @@ impl Db {
|
|||||||
log::info!("Ignore: {}", line);
|
log::info!("Ignore: {}", line);
|
||||||
if line.starts_with("SQL:") {
|
if line.starts_with("SQL:") {
|
||||||
let sql = &line[4..];
|
let sql = &line[4..];
|
||||||
match self.conn.execute(&format!("UPDATE Tracks Set Ignore=1 WHERE {}", sql), []) {
|
match self
|
||||||
Ok(_) => { },
|
.conn
|
||||||
Err(e) => { log::error!("Failed set Ignore column for '{}'. {}", line, e); }
|
.execute(&format!("UPDATE Tracks Set Ignore=1 WHERE {}", sql), [])
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed set Ignore column for '{}'. {}", line, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match self.conn.execute(&format!("UPDATE Tracks SET Ignore=1 WHERE File LIKE \"{}%\"", line), []) {
|
match self.conn.execute(
|
||||||
Ok(_) => { },
|
&format!("UPDATE Tracks SET Ignore=1 WHERE File LIKE \"{}%\"", line),
|
||||||
Err(e) => { log::error!("Failed set Ignore column for '{}'. {}", line, e); }
|
[],
|
||||||
|
) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed set Ignore column for '{}'. {}", line, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
116
src/main.rs
116
src/main.rs
@ -19,7 +19,6 @@ mod db;
|
|||||||
mod tags;
|
mod tags;
|
||||||
mod upload;
|
mod upload;
|
||||||
|
|
||||||
|
|
||||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||||
const TOP_LEVEL_INI_TAG: &str = "Bliss";
|
const TOP_LEVEL_INI_TAG: &str = "Bliss";
|
||||||
|
|
||||||
@ -37,7 +36,9 @@ fn main() {
|
|||||||
let mut music_paths: Vec<PathBuf> = Vec::new();
|
let mut music_paths: Vec<PathBuf> = Vec::new();
|
||||||
|
|
||||||
match dirs::home_dir() {
|
match dirs::home_dir() {
|
||||||
Some(path) => { music_path = String::from(path.join("Music").to_string_lossy()); }
|
Some(path) => {
|
||||||
|
music_path = String::from(path.join("Music").to_string_lossy());
|
||||||
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,8 +46,14 @@ fn main() {
|
|||||||
let config_file_help = format!("config file (default: {})", &config_file);
|
let config_file_help = format!("config file (default: {})", &config_file);
|
||||||
let music_path_help = format!("Music folder (default: {})", &music_path);
|
let music_path_help = format!("Music folder (default: {})", &music_path);
|
||||||
let db_path_help = format!("Database location (default: {})", &db_path);
|
let db_path_help = format!("Database location (default: {})", &db_path);
|
||||||
let logging_help = format!("Log level; trace, debug, info, warn, error. (default: {})", logging);
|
let logging_help = format!(
|
||||||
let ignore_file_help = format!("File containg items to mark as ignored. (default: {})", ignore_file);
|
"Log level; trace, debug, info, warn, error. (default: {})",
|
||||||
|
logging
|
||||||
|
);
|
||||||
|
let ignore_file_help = format!(
|
||||||
|
"File containg items to mark as ignored. (default: {})",
|
||||||
|
ignore_file
|
||||||
|
);
|
||||||
let lms_host_help = format!("LMS hostname or IP address (default: {})", &lms_host);
|
let lms_host_help = format!("LMS hostname or IP address (default: {})", &lms_host);
|
||||||
let description = format!("Bliss Analyser v{}", VERSION);
|
let description = format!("Bliss Analyser v{}", VERSION);
|
||||||
|
|
||||||
@ -54,25 +61,67 @@ fn main() {
|
|||||||
// borrow per scope, hence this section is enclosed in { }
|
// borrow per scope, hence this section is enclosed in { }
|
||||||
let mut arg_parse = ArgumentParser::new();
|
let mut arg_parse = ArgumentParser::new();
|
||||||
arg_parse.set_description(&description);
|
arg_parse.set_description(&description);
|
||||||
arg_parse.refer(&mut config_file).add_option(&["-c", "--config"], Store, &config_file_help);
|
arg_parse
|
||||||
arg_parse.refer(&mut music_path).add_option(&["-m", "--music"], Store, &music_path_help);
|
.refer(&mut config_file)
|
||||||
arg_parse.refer(&mut db_path).add_option(&["-d", "--db"], Store, &db_path_help);
|
.add_option(&["-c", "--config"], Store, &config_file_help);
|
||||||
arg_parse.refer(&mut logging).add_option(&["-l", "--logging"], Store, &logging_help);
|
arg_parse
|
||||||
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)");
|
.refer(&mut music_path)
|
||||||
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)");
|
.add_option(&["-m", "--music"], Store, &music_path_help);
|
||||||
arg_parse.refer(&mut ignore_file).add_option(&["-i", "--ignore"], Store, &ignore_file_help);
|
arg_parse
|
||||||
arg_parse.refer(&mut lms_host).add_option(&["-L", "--lms"], Store, &lms_host_help);
|
.refer(&mut db_path)
|
||||||
arg_parse.refer(&mut max_num_tracks).add_option(&["-n", "--numtracks"], Store, "Maximum number of tracks to analyse");
|
.add_option(&["-d", "--db"], Store, &db_path_help);
|
||||||
arg_parse.refer(&mut task).add_argument("task", Store, "Task to perform; analyse, tags, ignore, upload, stopmixer.");
|
arg_parse
|
||||||
|
.refer(&mut logging)
|
||||||
|
.add_option(&["-l", "--logging"], Store, &logging_help);
|
||||||
|
arg_parse.refer(&mut keep_old).add_option(
|
||||||
|
&["-k", "--keep-old"],
|
||||||
|
StoreTrue,
|
||||||
|
"Don't remove tracks from DB if they don't exist (used with analyse task)",
|
||||||
|
);
|
||||||
|
arg_parse.refer(&mut dry_run).add_option(
|
||||||
|
&["-r", "--dry-run"],
|
||||||
|
StoreTrue,
|
||||||
|
"Dry run, only show what needs to be done (used with analyse task)",
|
||||||
|
);
|
||||||
|
arg_parse
|
||||||
|
.refer(&mut ignore_file)
|
||||||
|
.add_option(&["-i", "--ignore"], Store, &ignore_file_help);
|
||||||
|
arg_parse
|
||||||
|
.refer(&mut lms_host)
|
||||||
|
.add_option(&["-L", "--lms"], Store, &lms_host_help);
|
||||||
|
arg_parse.refer(&mut max_num_tracks).add_option(
|
||||||
|
&["-n", "--numtracks"],
|
||||||
|
Store,
|
||||||
|
"Maximum number of tracks to analyse",
|
||||||
|
);
|
||||||
|
arg_parse.refer(&mut task).add_argument(
|
||||||
|
"task",
|
||||||
|
Store,
|
||||||
|
"Task to perform; analyse, tags, ignore, upload, stopmixer.",
|
||||||
|
);
|
||||||
arg_parse.parse_args_or_exit();
|
arg_parse.parse_args_or_exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
if !(logging.eq_ignore_ascii_case("trace") || logging.eq_ignore_ascii_case("debug") || logging.eq_ignore_ascii_case("info") || logging.eq_ignore_ascii_case("warn") || logging.eq_ignore_ascii_case("error")) {
|
if !(logging.eq_ignore_ascii_case("trace")
|
||||||
|
|| logging.eq_ignore_ascii_case("debug")
|
||||||
|
|| logging.eq_ignore_ascii_case("info")
|
||||||
|
|| logging.eq_ignore_ascii_case("warn")
|
||||||
|
|| logging.eq_ignore_ascii_case("error"))
|
||||||
|
{
|
||||||
logging = String::from("info");
|
logging = String::from("info");
|
||||||
}
|
}
|
||||||
let mut builder = env_logger::Builder::from_env(env_logger::Env::default().filter_or("XXXXXXXX", logging));
|
let mut builder =
|
||||||
|
env_logger::Builder::from_env(env_logger::Env::default().filter_or("XXXXXXXX", logging));
|
||||||
builder.filter(Some("bliss_audio"), LevelFilter::Error);
|
builder.filter(Some("bliss_audio"), LevelFilter::Error);
|
||||||
builder.format(|buf, record| writeln!(buf, "[{} {:.1}] {}", Local::now().format("%Y-%m-%d %H:%M:%S"), record.level(), record.args()));
|
builder.format(|buf, record| {
|
||||||
|
writeln!(
|
||||||
|
buf,
|
||||||
|
"[{} {:.1}] {}",
|
||||||
|
Local::now().format("%Y-%m-%d %H:%M:%S"),
|
||||||
|
record.level(),
|
||||||
|
record.args()
|
||||||
|
)
|
||||||
|
});
|
||||||
builder.init();
|
builder.init();
|
||||||
|
|
||||||
if task.is_empty() {
|
if task.is_empty() {
|
||||||
@ -80,7 +129,12 @@ fn main() {
|
|||||||
process::exit(-1);
|
process::exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !task.eq_ignore_ascii_case("analyse") && !task.eq_ignore_ascii_case("tags") && !task.eq_ignore_ascii_case("ignore") && !task.eq_ignore_ascii_case("upload") && !task.eq_ignore_ascii_case("stopmixer") {
|
if !task.eq_ignore_ascii_case("analyse")
|
||||||
|
&& !task.eq_ignore_ascii_case("tags")
|
||||||
|
&& !task.eq_ignore_ascii_case("ignore")
|
||||||
|
&& !task.eq_ignore_ascii_case("upload")
|
||||||
|
&& !task.eq_ignore_ascii_case("stopmixer")
|
||||||
|
{
|
||||||
log::error!("Invalid task ({}) supplied", task);
|
log::error!("Invalid task ({}) supplied", task);
|
||||||
process::exit(-1);
|
process::exit(-1);
|
||||||
}
|
}
|
||||||
@ -91,26 +145,35 @@ fn main() {
|
|||||||
let mut config = Ini::new();
|
let mut config = Ini::new();
|
||||||
match config.load(&config_file) {
|
match config.load(&config_file) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let path_keys: [&str; 5] = ["music", "music_1", "music_2", "music_3", "music_4"];
|
let path_keys: [&str; 5] =
|
||||||
|
["music", "music_1", "music_2", "music_3", "music_4"];
|
||||||
for key in &path_keys {
|
for key in &path_keys {
|
||||||
match config.get(TOP_LEVEL_INI_TAG, key) {
|
match config.get(TOP_LEVEL_INI_TAG, key) {
|
||||||
Some(val) => { music_paths.push(PathBuf::from(&val)); },
|
Some(val) => {
|
||||||
|
music_paths.push(PathBuf::from(&val));
|
||||||
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match config.get(TOP_LEVEL_INI_TAG, "db") {
|
match config.get(TOP_LEVEL_INI_TAG, "db") {
|
||||||
Some(val) => { db_path = val; },
|
Some(val) => {
|
||||||
|
db_path = val;
|
||||||
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
match config.get(TOP_LEVEL_INI_TAG, "lms") {
|
match config.get(TOP_LEVEL_INI_TAG, "lms") {
|
||||||
Some(val) => { lms_host = val; },
|
Some(val) => {
|
||||||
|
lms_host = val;
|
||||||
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
match config.get(TOP_LEVEL_INI_TAG, "ignore") {
|
match config.get(TOP_LEVEL_INI_TAG, "ignore") {
|
||||||
Some(val) => { ignore_file = val; },
|
Some(val) => {
|
||||||
|
ignore_file = val;
|
||||||
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to load config file. {}", e);
|
log::error!("Failed to load config file. {}", e);
|
||||||
process::exit(-1);
|
process::exit(-1);
|
||||||
@ -151,7 +214,10 @@ fn main() {
|
|||||||
process::exit(-1);
|
process::exit(-1);
|
||||||
}
|
}
|
||||||
if !mpath.is_dir() {
|
if !mpath.is_dir() {
|
||||||
log::error!("Music path ({}) is not a directory", mpath.to_string_lossy());
|
log::error!(
|
||||||
|
"Music path ({}) is not a directory",
|
||||||
|
mpath.to_string_lossy()
|
||||||
|
);
|
||||||
process::exit(-1);
|
process::exit(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
src/tags.rs
31
src/tags.rs
@ -1,3 +1,4 @@
|
|||||||
|
use crate::db;
|
||||||
/**
|
/**
|
||||||
* Analyse music with Bliss
|
* Analyse music with Bliss
|
||||||
*
|
*
|
||||||
@ -5,12 +6,10 @@
|
|||||||
* GPLv3 license.
|
* GPLv3 license.
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
|
|
||||||
use lofty::{Accessor, ItemKey, Probe};
|
use lofty::{Accessor, ItemKey, Probe};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use substring::Substring;
|
use substring::Substring;
|
||||||
use crate::db;
|
|
||||||
|
|
||||||
const MAX_GENRE_VAL: usize = 192;
|
const MAX_GENRE_VAL: usize = 192;
|
||||||
|
|
||||||
@ -21,7 +20,7 @@ pub fn read(track:&String) -> db::Metadata {
|
|||||||
album: String::new(),
|
album: String::new(),
|
||||||
album_artist: String::new(),
|
album_artist: String::new(),
|
||||||
genre: String::new(),
|
genre: String::new(),
|
||||||
duration:180
|
duration: 180,
|
||||||
};
|
};
|
||||||
let path = Path::new(track);
|
let path = Path::new(track);
|
||||||
match Probe::open(path) {
|
match Probe::open(path) {
|
||||||
@ -36,7 +35,10 @@ pub fn read(track:&String) -> db::Metadata {
|
|||||||
meta.title = tag.title().unwrap_or("").to_string();
|
meta.title = tag.title().unwrap_or("").to_string();
|
||||||
meta.artist = tag.artist().unwrap_or("").to_string();
|
meta.artist = tag.artist().unwrap_or("").to_string();
|
||||||
meta.album = tag.album().unwrap_or("").to_string();
|
meta.album = tag.album().unwrap_or("").to_string();
|
||||||
meta.album_artist=tag.get_string(&ItemKey::AlbumArtist).unwrap_or("").to_string();
|
meta.album_artist = tag
|
||||||
|
.get_string(&ItemKey::AlbumArtist)
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string();
|
||||||
meta.genre = tag.genre().unwrap_or("").to_string();
|
meta.genre = tag.genre().unwrap_or("").to_string();
|
||||||
// Check whether MP3 as numeric genre, and if so covert to text
|
// Check whether MP3 as numeric genre, and if so covert to text
|
||||||
if file.file_type().eq(&lofty::FileType::MP3) {
|
if file.file_type().eq(&lofty::FileType::MP3) {
|
||||||
@ -49,38 +51,43 @@ pub fn read(track:&String) -> db::Metadata {
|
|||||||
if idx < MAX_GENRE_VAL {
|
if idx < MAX_GENRE_VAL {
|
||||||
meta.genre = lofty::id3::v1::GENRES[idx].to_string();
|
meta.genre = lofty::id3::v1::GENRES[idx].to_string();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// Check for "(number)text"
|
// Check for "(number)text"
|
||||||
let re = Regex::new(r"^\([0-9]+\)").unwrap();
|
let re = Regex::new(r"^\([0-9]+\)").unwrap();
|
||||||
if re.is_match(&genre) {
|
if re.is_match(&genre) {
|
||||||
match genre.find(")") {
|
match genre.find(")") {
|
||||||
Some(end) => {
|
Some(end) => {
|
||||||
let test = &genre.to_string().substring(1, end).parse::<u8>();
|
let test = &genre
|
||||||
|
.to_string()
|
||||||
|
.substring(1, end)
|
||||||
|
.parse::<u8>();
|
||||||
match test {
|
match test {
|
||||||
Ok(val) => {
|
Ok(val) => {
|
||||||
let idx: usize = *val as usize;
|
let idx: usize = *val as usize;
|
||||||
if idx < MAX_GENRE_VAL {
|
if idx < MAX_GENRE_VAL {
|
||||||
meta.genre=lofty::id3::v1::GENRES[idx].to_string();
|
meta.genre = lofty::id3::v1::GENRES
|
||||||
|
[idx]
|
||||||
|
.to_string();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
meta.duration = file.properties().duration().as_secs() as u32;
|
meta.duration = file.properties().duration().as_secs() as u32;
|
||||||
},
|
}
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
}
|
}
|
||||||
meta
|
meta
|
||||||
|
@ -5,26 +5,27 @@
|
|||||||
* GPLv3 license.
|
* GPLv3 license.
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::process;
|
use std::process;
|
||||||
use substring::Substring;
|
use substring::Substring;
|
||||||
use ureq;
|
use ureq;
|
||||||
|
|
||||||
|
|
||||||
fn fail(msg: &str) {
|
fn fail(msg: &str) {
|
||||||
log::error!("{}", msg);
|
log::error!("{}", msg);
|
||||||
process::exit(-1);
|
process::exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop_mixer(lms: &String) {
|
pub fn stop_mixer(lms: &String) {
|
||||||
let stop_req = "{\"id\":1, \"method\":\"slim.request\",\"params\":[\"\",[\"blissmixer\",\"stop\"]]}";
|
let stop_req =
|
||||||
|
"{\"id\":1, \"method\":\"slim.request\",\"params\":[\"\",[\"blissmixer\",\"stop\"]]}";
|
||||||
|
|
||||||
log::info!("Asking plugin to stop mixer");
|
log::info!("Asking plugin to stop mixer");
|
||||||
match ureq::post(&format!("http://{}:9000/jsonrpc.js", lms)).send_string(&stop_req) {
|
match ureq::post(&format!("http://{}:9000/jsonrpc.js", lms)).send_string(&stop_req) {
|
||||||
Ok(_) => { },
|
Ok(_) => {}
|
||||||
Err(e) => { log::error!("Failed to ask plugin to stop mixer. {}", e); }
|
Err(e) => {
|
||||||
|
log::error!("Failed to ask plugin to stop mixer. {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,10 +37,8 @@ pub fn upload_db(db_path:&String, lms:&String) {
|
|||||||
log::info!("Requesting LMS plugin to allow uploads");
|
log::info!("Requesting LMS plugin to allow uploads");
|
||||||
|
|
||||||
match ureq::post(&format!("http://{}:9000/jsonrpc.js", lms)).send_string(&start_req) {
|
match ureq::post(&format!("http://{}:9000/jsonrpc.js", lms)).send_string(&start_req) {
|
||||||
Ok(resp) => {
|
Ok(resp) => match resp.into_string() {
|
||||||
match resp.into_string() {
|
Ok(text) => match text.find("\"port\":") {
|
||||||
Ok(text) => {
|
|
||||||
match text.find("\"port\":") {
|
|
||||||
Some(s) => {
|
Some(s) => {
|
||||||
let txt = text.to_string().substring(s + 7, text.len()).to_string();
|
let txt = text.to_string().substring(s + 7, text.len()).to_string();
|
||||||
match txt.find("}") {
|
match txt.find("}") {
|
||||||
@ -49,18 +48,22 @@ pub fn upload_db(db_path:&String, lms:&String) {
|
|||||||
match test {
|
match test {
|
||||||
Ok(val) => {
|
Ok(val) => {
|
||||||
port = val;
|
port = val;
|
||||||
},
|
}
|
||||||
Err(_) => { fail("Could not parse resp (cast)"); }
|
Err(_) => {
|
||||||
|
fail("Could not parse resp (cast)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
fail("Could not parse resp (closing)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
fail("Could not parse resp (no port)");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => { fail("Could not parse resp (closing)"); }
|
Err(_) => fail("No text?"),
|
||||||
}
|
|
||||||
},
|
|
||||||
None => { fail("Could not parse resp (no port)"); }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(_) => { fail("No text?")}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
fail(&format!("Failed to ask LMS plugin to allow upload. {}", e));
|
fail(&format!("Failed to ask LMS plugin to allow upload. {}", e));
|
||||||
@ -74,28 +77,27 @@ pub fn upload_db(db_path:&String, lms:&String) {
|
|||||||
// Now we have port number, do the actual upload...
|
// Now we have port number, do the actual upload...
|
||||||
log::info!("Uploading {}", db_path);
|
log::info!("Uploading {}", db_path);
|
||||||
match File::open(db_path) {
|
match File::open(db_path) {
|
||||||
Ok(file) => {
|
Ok(file) => match file.metadata() {
|
||||||
match file.metadata() {
|
|
||||||
Ok(meta) => {
|
Ok(meta) => {
|
||||||
let buffered_reader = BufReader::new(file);
|
let buffered_reader = BufReader::new(file);
|
||||||
log::info!("Length: {}", meta.len());
|
log::info!("Length: {}", meta.len());
|
||||||
match ureq::put(&format!("http://{}:{}/upload", lms, port))
|
match ureq::put(&format!("http://{}:{}/upload", lms, port))
|
||||||
.set("Content-Length", &meta.len().to_string())
|
.set("Content-Length", &meta.len().to_string())
|
||||||
.set("Content-Type", "application/octet-stream")
|
.set("Content-Type", "application/octet-stream")
|
||||||
.send(buffered_reader) {
|
.send(buffered_reader)
|
||||||
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
log::info!("Database uploaded");
|
log::info!("Database uploaded");
|
||||||
stop_mixer(lms);
|
stop_mixer(lms);
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
fail(&format!("Failed to upload database. {}", e));
|
fail(&format!("Failed to upload database. {}", e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
fail(&format!("Failed to open database. {}", e));
|
fail(&format!("Failed to open database. {}", e));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
fail(&format!("Failed to open database. {}", e));
|
fail(&format!("Failed to open database. {}", e));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user