mirror of
https://github.com/CDrummond/bliss-analyser.git
synced 2025-04-12 15:07:14 +03:00
Add option to preserve file modification time when writing tags.
Issue #21
This commit is contained in:
parent
342440f04b
commit
417ac5f652
@ -1,6 +1,7 @@
|
|||||||
0.4.0
|
0.4.0
|
||||||
-----
|
-----
|
||||||
1. Add action to export results from database to files.
|
1. Add action to export results from database to files.
|
||||||
|
2. Add option to preserve file modification time when writing tags.
|
||||||
|
|
||||||
0.3.0
|
0.3.0
|
||||||
-----
|
-----
|
||||||
|
@ -150,6 +150,8 @@ in mixes. See the `Ignore` section later on for more details.
|
|||||||
* `tags` specifies whether analysis results should be written to, and re-read from,
|
* `tags` specifies whether analysis results should be written to, and re-read from,
|
||||||
files. Set to `true` or `false`. If enabled, then results are stored in a `COMMENT`
|
files. Set to `true` or `false`. If enabled, then results are stored in a `COMMENT`
|
||||||
tag that starts with `BLISS_ANALYSIS`
|
tag that starts with `BLISS_ANALYSIS`
|
||||||
|
* `preserve` specifies whether file modification time should be preserved when
|
||||||
|
writing tags. Set to `true` or `false`.
|
||||||
|
|
||||||
|
|
||||||
Command-line parameters
|
Command-line parameters
|
||||||
@ -173,6 +175,7 @@ tracks are to be analysed and how many old tracks are left in the database.
|
|||||||
* `-J` / `--json` JSONRPC port number of your LMS server.
|
* `-J` / `--json` JSONRPC port number of your LMS server.
|
||||||
* `-n` / `--numtracks` Specify maximum number of tracks to analyse.
|
* `-n` / `--numtracks` Specify maximum number of tracks to analyse.
|
||||||
* `-T` / `--tags` Write analysis results to file tags, and read from file tags.
|
* `-T` / `--tags` Write analysis results to file tags, and read from file tags.
|
||||||
|
* `-p' / '--preserve` Attempt to preserve file modification time when writing tags.
|
||||||
|
|
||||||
Equivalent items specified in the INI config file (detailed above) will override
|
Equivalent items specified in the INI config file (detailed above) will override
|
||||||
any specified on the commandline.
|
any specified on the commandline.
|
||||||
|
@ -161,7 +161,7 @@ fn show_errors(failed: &mut Vec<String>, tag_error: &mut Vec<String>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "ffmpeg"))]
|
#[cfg(not(feature = "ffmpeg"))]
|
||||||
fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec<String>, max_threads: usize, use_tags: bool) -> Result<()> {
|
fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec<String>, max_threads: usize, use_tags: bool, preserve_mod_times: bool) -> Result<()> {
|
||||||
let total = track_paths.len();
|
let total = track_paths.len();
|
||||||
let progress = ProgressBar::new(total.try_into().unwrap()).with_style(
|
let progress = ProgressBar::new(total.try_into().unwrap()).with_style(
|
||||||
ProgressStyle::default_bar()
|
ProgressStyle::default_bar()
|
||||||
@ -237,7 +237,7 @@ fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec<String>, max
|
|||||||
tag_error.push(sname.clone());
|
tag_error.push(sname.clone());
|
||||||
}
|
}
|
||||||
if use_tags {
|
if use_tags {
|
||||||
tags::write_analysis(&cpath, &track.analysis);
|
tags::write_analysis(&cpath, &track.analysis, preserve_mod_times);
|
||||||
}
|
}
|
||||||
db.add_track(&sname, &meta, &track.analysis);
|
db.add_track(&sname, &meta, &track.analysis);
|
||||||
}
|
}
|
||||||
@ -259,7 +259,7 @@ fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec<String>, max
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ffmpeg")]
|
#[cfg(feature = "ffmpeg")]
|
||||||
fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec<String>, max_threads: usize, use_tags: bool) -> Result<()> {
|
fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec<String>, max_threads: usize, use_tags: bool, preserve_mod_times: bool) -> Result<()> {
|
||||||
let total = track_paths.len();
|
let total = track_paths.len();
|
||||||
let progress = ProgressBar::new(total.try_into().unwrap()).with_style(
|
let progress = ProgressBar::new(total.try_into().unwrap()).with_style(
|
||||||
ProgressStyle::default_bar()
|
ProgressStyle::default_bar()
|
||||||
@ -292,7 +292,7 @@ fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec<String>, max
|
|||||||
tag_error.push(sname.clone());
|
tag_error.push(sname.clone());
|
||||||
}
|
}
|
||||||
if use_tags {
|
if use_tags {
|
||||||
tags::write_analysis(&cpath, &track.analysis);
|
tags::write_analysis(&cpath, &track.analysis, preserve_mod_times);
|
||||||
}
|
}
|
||||||
db.add_track(&sname, &meta, &track.analysis);
|
db.add_track(&sname, &meta, &track.analysis);
|
||||||
analysed += 1;
|
analysed += 1;
|
||||||
@ -405,7 +405,7 @@ fn analyse_new_cue_tracks(db:&db::Db, mpath: &PathBuf, cue_tracks:Vec<cue::CueTr
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn analyse_files(db_path: &str, mpaths: &Vec<PathBuf>, dry_run: bool, keep_old: bool, max_num_files: usize, max_threads: usize, ignore_path: &PathBuf, use_tags: bool) {
|
pub fn analyse_files(db_path: &str, mpaths: &Vec<PathBuf>, dry_run: bool, keep_old: bool, max_num_files: usize, max_threads: usize, ignore_path: &PathBuf, use_tags: bool, preserve_mod_times: bool) {
|
||||||
let mut db = db::Db::new(&String::from(db_path));
|
let mut db = db::Db::new(&String::from(db_path));
|
||||||
|
|
||||||
db.init();
|
db.init();
|
||||||
@ -450,7 +450,7 @@ pub fn analyse_files(db_path: &str, mpaths: &Vec<PathBuf>, dry_run: bool, keep_o
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !track_paths.is_empty() {
|
if !track_paths.is_empty() {
|
||||||
match analyse_new_files(&db, &mpath, track_paths, max_threads, use_tags) {
|
match analyse_new_files(&db, &mpath, track_paths, max_threads, use_tags, preserve_mod_times) {
|
||||||
Ok(_) => { changes_made = true; }
|
Ok(_) => { changes_made = true; }
|
||||||
Err(e) => { log::error!("Analysis returned error: {}", e); }
|
Err(e) => { log::error!("Analysis returned error: {}", e); }
|
||||||
}
|
}
|
||||||
@ -482,10 +482,10 @@ pub fn read_tags(db_path: &str, mpaths: &Vec<PathBuf>) {
|
|||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn export(db_path: &str, mpaths: &Vec<PathBuf>) {
|
pub fn export(db_path: &str, mpaths: &Vec<PathBuf>, preserve_mod_times: bool) {
|
||||||
let db = db::Db::new(&String::from(db_path));
|
let db = db::Db::new(&String::from(db_path));
|
||||||
db.init();
|
db.init();
|
||||||
db.export(&mpaths);
|
db.export(&mpaths, preserve_mod_times);
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,7 +344,7 @@ impl Db {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn export(&self, mpaths: &Vec<PathBuf>) {
|
pub fn export(&self, mpaths: &Vec<PathBuf>, preserve_mod_times: bool) {
|
||||||
let total = self.get_track_count();
|
let total = self.get_track_count();
|
||||||
if total > 0 {
|
if total > 0 {
|
||||||
let progress = ProgressBar::new(total.try_into().unwrap()).with_style(
|
let progress = ProgressBar::new(total.try_into().unwrap()).with_style(
|
||||||
@ -377,7 +377,7 @@ impl Db {
|
|||||||
let spath = String::from(track_path.to_string_lossy());
|
let spath = String::from(track_path.to_string_lossy());
|
||||||
let meta = tags::read(&spath, true);
|
let meta = tags::read(&spath, true);
|
||||||
if meta.is_empty() || meta.analysis.is_none() || meta.analysis.unwrap()!=dbtags.analysis {
|
if meta.is_empty() || meta.analysis.is_none() || meta.analysis.unwrap()!=dbtags.analysis {
|
||||||
tags::write_analysis(&spath, &dbtags.analysis);
|
tags::write_analysis(&spath, &dbtags.analysis, preserve_mod_times);
|
||||||
updated+=1;
|
updated+=1;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
10
src/main.rs
10
src/main.rs
@ -42,6 +42,7 @@ fn main() {
|
|||||||
let mut music_paths: Vec<PathBuf> = Vec::new();
|
let mut music_paths: Vec<PathBuf> = Vec::new();
|
||||||
let mut max_threads: usize = 0;
|
let mut max_threads: usize = 0;
|
||||||
let mut use_tags = false;
|
let mut use_tags = false;
|
||||||
|
let mut preserve_mod_times = false;
|
||||||
|
|
||||||
match dirs::home_dir() {
|
match dirs::home_dir() {
|
||||||
Some(path) => {
|
Some(path) => {
|
||||||
@ -76,6 +77,7 @@ fn main() {
|
|||||||
arg_parse.refer(&mut max_num_files).add_option(&["-n", "--numfiles"], Store, "Maximum number of files to analyse");
|
arg_parse.refer(&mut max_num_files).add_option(&["-n", "--numfiles"], Store, "Maximum number of files to analyse");
|
||||||
arg_parse.refer(&mut max_threads).add_option(&["-t", "--threads"], Store, "Maximum number of threads to use for analysis");
|
arg_parse.refer(&mut max_threads).add_option(&["-t", "--threads"], Store, "Maximum number of threads to use for analysis");
|
||||||
arg_parse.refer(&mut use_tags).add_option(&["-T", "--tags"], StoreTrue, "Read/write analysis results from/to source files");
|
arg_parse.refer(&mut use_tags).add_option(&["-T", "--tags"], StoreTrue, "Read/write analysis results from/to source files");
|
||||||
|
arg_parse.refer(&mut preserve_mod_times).add_option(&["-p", "--preserve"], StoreTrue, "Preserve modification time when writing tags to files");
|
||||||
arg_parse.refer(&mut task).add_argument("task", Store, "Task to perform; analyse, tags, ignore, upload, export, stopmixer.");
|
arg_parse.refer(&mut task).add_argument("task", Store, "Task to perform; analyse, tags, ignore, upload, export, stopmixer.");
|
||||||
arg_parse.parse_args_or_exit();
|
arg_parse.parse_args_or_exit();
|
||||||
}
|
}
|
||||||
@ -147,6 +149,10 @@ fn main() {
|
|||||||
Some(val) => { use_tags = val.eq("true"); }
|
Some(val) => { use_tags = val.eq("true"); }
|
||||||
None => { }
|
None => { }
|
||||||
}
|
}
|
||||||
|
match config.get(TOP_LEVEL_INI_TAG, "preserve") {
|
||||||
|
Some(val) => { preserve_mod_times = val.eq("true"); }
|
||||||
|
None => { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to load config file. {}", e);
|
log::error!("Failed to load config file. {}", e);
|
||||||
@ -207,10 +213,10 @@ fn main() {
|
|||||||
}
|
}
|
||||||
analyse::update_ignore(&db_path, &ignore_path);
|
analyse::update_ignore(&db_path, &ignore_path);
|
||||||
} else if task.eq_ignore_ascii_case("export") {
|
} else if task.eq_ignore_ascii_case("export") {
|
||||||
analyse::export(&db_path, &music_paths);
|
analyse::export(&db_path, &music_paths, preserve_mod_times);
|
||||||
} else {
|
} else {
|
||||||
let ignore_path = PathBuf::from(&ignore_file);
|
let ignore_path = PathBuf::from(&ignore_file);
|
||||||
analyse::analyse_files(&db_path, &music_paths, dry_run, keep_old, max_num_files, max_threads, &ignore_path, use_tags);
|
analyse::analyse_files(&db_path, &music_paths, dry_run, keep_old, max_num_files, max_threads, &ignore_path, use_tags, preserve_mod_times);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
21
src/tags.rs
21
src/tags.rs
@ -12,8 +12,11 @@ use lofty::file::FileType;
|
|||||||
use lofty::prelude::{Accessor, AudioFile, ItemKey, TagExt, TaggedFileExt};
|
use lofty::prelude::{Accessor, AudioFile, ItemKey, TagExt, TaggedFileExt};
|
||||||
use lofty::tag::{ItemValue, Tag, TagItem};
|
use lofty::tag::{ItemValue, Tag, TagItem};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use substring::Substring;
|
use substring::Substring;
|
||||||
|
use std::time::SystemTime;
|
||||||
use bliss_audio::{Analysis, AnalysisIndex};
|
use bliss_audio::{Analysis, AnalysisIndex};
|
||||||
|
|
||||||
const MAX_GENRE_VAL: usize = 192;
|
const MAX_GENRE_VAL: usize = 192;
|
||||||
@ -22,7 +25,7 @@ const ANALYSIS_TAG:ItemKey = ItemKey::Comment;
|
|||||||
const ANALYSIS_TAG_START: &str = "BLISS_ANALYSIS";
|
const ANALYSIS_TAG_START: &str = "BLISS_ANALYSIS";
|
||||||
const ANALYSIS_TAG_VER: u16 = 1;
|
const ANALYSIS_TAG_VER: u16 = 1;
|
||||||
|
|
||||||
pub fn write_analysis(track: &String, analysis: &Analysis) {
|
pub fn write_analysis(track: &String, analysis: &Analysis, preserve_mod_times: bool) {
|
||||||
let value = format!("{},{},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24}", ANALYSIS_TAG_START, ANALYSIS_TAG_VER,
|
let value = format!("{},{},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24},{:.24}", ANALYSIS_TAG_START, ANALYSIS_TAG_VER,
|
||||||
analysis[AnalysisIndex::Tempo], analysis[AnalysisIndex::Zcr], analysis[AnalysisIndex::MeanSpectralCentroid], analysis[AnalysisIndex::StdDeviationSpectralCentroid], analysis[AnalysisIndex::MeanSpectralRolloff],
|
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],
|
analysis[AnalysisIndex::StdDeviationSpectralRolloff], analysis[AnalysisIndex::MeanSpectralFlatness], analysis[AnalysisIndex::StdDeviationSpectralFlatness], analysis[AnalysisIndex::MeanLoudness], analysis[AnalysisIndex::StdDeviationLoudness],
|
||||||
@ -58,7 +61,23 @@ pub fn write_analysis(track: &String, analysis: &Analysis) {
|
|||||||
|
|
||||||
// Store analysis results
|
// Store analysis results
|
||||||
tag.push(TagItem::new(ANALYSIS_TAG, ItemValue::Text(value)));
|
tag.push(TagItem::new(ANALYSIS_TAG, ItemValue::Text(value)));
|
||||||
|
let now = SystemTime::now();
|
||||||
|
let mut mod_time = now;
|
||||||
|
if preserve_mod_times {
|
||||||
|
if let Ok(fmeta) = fs::metadata(track) {
|
||||||
|
if let Ok(time) = fmeta.modified() {
|
||||||
|
mod_time = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
let _ = tag.save_to_path(Path::new(track), WriteOptions::default());
|
let _ = tag.save_to_path(Path::new(track), WriteOptions::default());
|
||||||
|
if preserve_mod_times {
|
||||||
|
if mod_time<now {
|
||||||
|
if let Ok(f) = File::open(track) {
|
||||||
|
let _ = f.set_modified(mod_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user