From 417ac5f6520061db66189eb7511c1a78ae407439 Mon Sep 17 00:00:00 2001 From: CDrummond Date: Mon, 24 Mar 2025 07:17:51 +0000 Subject: [PATCH] Add option to preserve file modification time when writing tags. Issue #21 --- ChangeLog | 1 + UserGuide.md | 3 +++ src/analyse.rs | 16 ++++++++-------- src/db.rs | 4 ++-- src/main.rs | 10 ++++++++-- src/tags.rs | 21 ++++++++++++++++++++- 6 files changed, 42 insertions(+), 13 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7c4e584..2456ab5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,7 @@ 0.4.0 ----- 1. Add action to export results from database to files. +2. Add option to preserve file modification time when writing tags. 0.3.0 ----- diff --git a/UserGuide.md b/UserGuide.md index af1b90a..5c44c32 100644 --- a/UserGuide.md +++ b/UserGuide.md @@ -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, files. Set to `true` or `false`. If enabled, then results are stored in a `COMMENT` 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 @@ -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. * `-n` / `--numtracks` Specify maximum number of tracks to analyse. * `-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 any specified on the commandline. diff --git a/src/analyse.rs b/src/analyse.rs index 70b1f73..969a535 100644 --- a/src/analyse.rs +++ b/src/analyse.rs @@ -161,7 +161,7 @@ fn show_errors(failed: &mut Vec, tag_error: &mut Vec) { } #[cfg(not(feature = "ffmpeg"))] -fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec, max_threads: usize, use_tags: bool) -> Result<()> { +fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec, max_threads: usize, use_tags: bool, preserve_mod_times: bool) -> Result<()> { let total = track_paths.len(); let progress = ProgressBar::new(total.try_into().unwrap()).with_style( ProgressStyle::default_bar() @@ -237,7 +237,7 @@ fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec, max tag_error.push(sname.clone()); } 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); } @@ -259,7 +259,7 @@ fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec, max } #[cfg(feature = "ffmpeg")] -fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec, max_threads: usize, use_tags: bool) -> Result<()> { +fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec, max_threads: usize, use_tags: bool, preserve_mod_times: bool) -> Result<()> { let total = track_paths.len(); let progress = ProgressBar::new(total.try_into().unwrap()).with_style( ProgressStyle::default_bar() @@ -292,7 +292,7 @@ fn analyse_new_files(db: &db::Db, mpath: &PathBuf, track_paths: Vec, max tag_error.push(sname.clone()); } 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); analysed += 1; @@ -405,7 +405,7 @@ fn analyse_new_cue_tracks(db:&db::Db, mpath: &PathBuf, cue_tracks:Vec, 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, 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)); db.init(); @@ -450,7 +450,7 @@ pub fn analyse_files(db_path: &str, mpaths: &Vec, dry_run: bool, keep_o } } else { 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; } Err(e) => { log::error!("Analysis returned error: {}", e); } } @@ -482,10 +482,10 @@ pub fn read_tags(db_path: &str, mpaths: &Vec) { db.close(); } -pub fn export(db_path: &str, mpaths: &Vec) { +pub fn export(db_path: &str, mpaths: &Vec, preserve_mod_times: bool) { let db = db::Db::new(&String::from(db_path)); db.init(); - db.export(&mpaths); + db.export(&mpaths, preserve_mod_times); db.close(); } diff --git a/src/db.rs b/src/db.rs index be0e879..d8cc3f5 100644 --- a/src/db.rs +++ b/src/db.rs @@ -344,7 +344,7 @@ impl Db { } } - pub fn export(&self, mpaths: &Vec) { + pub fn export(&self, mpaths: &Vec, preserve_mod_times: bool) { let total = self.get_track_count(); if total > 0 { 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 meta = tags::read(&spath, true); 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; } break; diff --git a/src/main.rs b/src/main.rs index 01c122f..df4aa01 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,6 +42,7 @@ fn main() { let mut music_paths: Vec = Vec::new(); let mut max_threads: usize = 0; let mut use_tags = false; + let mut preserve_mod_times = false; match dirs::home_dir() { 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_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 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.parse_args_or_exit(); } @@ -147,6 +149,10 @@ fn main() { Some(val) => { use_tags = val.eq("true"); } None => { } } + match config.get(TOP_LEVEL_INI_TAG, "preserve") { + Some(val) => { preserve_mod_times = val.eq("true"); } + None => { } + } } Err(e) => { log::error!("Failed to load config file. {}", e); @@ -207,10 +213,10 @@ fn main() { } analyse::update_ignore(&db_path, &ignore_path); } else if task.eq_ignore_ascii_case("export") { - analyse::export(&db_path, &music_paths); + analyse::export(&db_path, &music_paths, preserve_mod_times); } else { 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); } } } diff --git a/src/tags.rs b/src/tags.rs index d65b6a1..0a2f907 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -12,8 +12,11 @@ use lofty::file::FileType; use lofty::prelude::{Accessor, AudioFile, ItemKey, TagExt, TaggedFileExt}; use lofty::tag::{ItemValue, Tag, TagItem}; use regex::Regex; +use std::fs::File; +use std::fs; use std::path::Path; use substring::Substring; +use std::time::SystemTime; use bliss_audio::{Analysis, AnalysisIndex}; 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_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, 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], @@ -58,7 +61,23 @@ pub fn write_analysis(track: &String, analysis: &Analysis) { // Store analysis results 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()); + if preserve_mod_times { + if mod_time