commit 2f5ba04a772e26838b2731cff112a894e1f25fba from: mtmn date: Sat May 9 00:05:54 2026 UTC fix: missing flush for manual edits commit - 7c7d0a475c8515e0ce5aaa61c6f90780b27c418d commit + 2f5ba04a772e26838b2731cff112a894e1f25fba blob - d71e382216f13f4b7bbd57d20583ae2f733d8de6 blob + fe70eb45e9a9308bc0afb9bbaf059bbaa07b1df7 --- src/main.rs +++ src/main.rs @@ -110,11 +110,7 @@ async fn main() -> Result<()> { } if args.edit { - for file_path in all_files { - if let Err(e) = tagging::edit_file(&file_path).await { - eprintln!("Failed to edit file {}: {e:?}", file_path.display()); - } - } + tagging::edit_files(&all_files).await?; } else if args.show_tags { for file_path in all_files { if let Err(e) = tagging::show_file_tags(&file_path) { blob - a2aa5005fdd3974774b35e37c4c1920aece40e7b blob + d0037a387c771c596a5c4e5ac7d564379f650d4a --- src/tagging.rs +++ src/tagging.rs @@ -8,8 +8,9 @@ use lofty::probe::Probe; use lofty::tag::{ItemKey, Tag}; use std::collections::HashMap; use std::fs::File; +use std::io::Write; use std::path::Path; -use tokio::io::{self, AsyncBufReadExt, AsyncWriteExt, BufReader}; +use tokio::io::{self, AsyncBufReadExt, BufReader}; pub async fn process_files_batch( ctx: &AppContext, @@ -165,12 +166,11 @@ fn parse_comma_separated(input: &str) -> Vec { async fn prompt_field( stdin: &mut BufReader, - stdout: &mut tokio::io::Stdout, field_name: &str, current: &str, ) -> Result { print!("{field_name} [{current}]: "); - stdout.flush().await?; + std::io::stdout().flush()?; let mut input = String::new(); stdin.read_line(&mut input).await?; @@ -233,44 +233,70 @@ fn save_with_backup(tagged_file: &mut TaggedFile, path Ok(()) } -pub async fn edit_file(path: &Path) -> Result<()> { - let abs_path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf()); - - if !abs_path.exists() { - return Err(anyhow::anyhow!( - "File does not exist: {}", - abs_path.display() - )); +pub async fn edit_files(paths: &[std::path::PathBuf]) -> Result<()> { + if paths.is_empty() { + println!("No audio files found."); + return Ok(()); } - let (mut tagged_file, current_artist, current_album) = read_file_tags(&abs_path)?; + let mut files_by_release: HashMap<(String, String), Vec> = HashMap::new(); - let tag = tagged_file.primary_tag().context("No primary tag found")?; - let current_genre = tag - .get(ItemKey::Genre) - .and_then(|i| i.value().text()) - .map(ToString::to_string) - .unwrap_or_default(); - let current_label = tag - .get(ItemKey::Label) - .and_then(|i| i.value().text()) - .map(ToString::to_string) - .unwrap_or_default(); + for path in paths { + let abs_path = path.canonicalize().unwrap_or_else(|_| path.clone()); + if !abs_path.exists() { + eprintln!("Skipping non-existent file: {}", path.display()); + continue; + } + match read_file_tags(&abs_path) { + Ok((_, artist, album)) => { + files_by_release + .entry((artist, album)) + .or_default() + .push(abs_path); + } + Err(e) => eprintln!("Skipping {} - {e:?}", path.display()), + } + } let mut stdin = BufReader::new(io::stdin()); - let mut stdout = io::stdout(); - let artist = prompt_field(&mut stdin, &mut stdout, "Artist", ¤t_artist).await?; - let album = prompt_field(&mut stdin, &mut stdout, "Album", ¤t_album).await?; - let genre = prompt_field(&mut stdin, &mut stdout, "Genre", ¤t_genre).await?; - let label = prompt_field(&mut stdin, &mut stdout, "Label", ¤t_label).await?; + for ((artist, album), file_paths) in files_by_release { + let (tagged_file, _, _) = read_file_tags(&file_paths[0])?; + let tag = tagged_file.primary_tag().context("No primary tag found")?; + let current_genre = tag + .get(ItemKey::Genre) + .and_then(|i| i.value().text()) + .map(ToString::to_string) + .unwrap_or_default(); + let current_label = tag + .get(ItemKey::Label) + .and_then(|i| i.value().text()) + .map(ToString::to_string) + .unwrap_or_default(); - let tag = tagged_file - .primary_tag_mut() - .context("No primary tag found")?; - update_tag(tag, &artist, &album, &genre, &label); + println!( + "\nEditing: {artist} - {album} ({} file(s))", + file_paths.len() + ); - save_with_backup(&mut tagged_file, &abs_path) + let new_artist = prompt_field(&mut stdin, "Artist", &artist).await?; + let new_album = prompt_field(&mut stdin, "Album", &album).await?; + let new_genre = prompt_field(&mut stdin, "Genre", ¤t_genre).await?; + let new_label = prompt_field(&mut stdin, "Label", ¤t_label).await?; + + for file_path in &file_paths { + let (mut tagged_file, _, _) = read_file_tags(file_path)?; + let tag = tagged_file + .primary_tag_mut() + .context("No primary tag found")?; + update_tag(tag, &new_artist, &new_album, &new_genre, &new_label); + if let Err(e) = save_with_backup(&mut tagged_file, file_path) { + eprintln!("Failed to write {}: {e:?}", file_path.display()); + } + } + } + + Ok(()) } fn read_file_tags(path: &Path) -> Result<(TaggedFile, String, String)> {