commit - 7c7d0a475c8515e0ce5aaa61c6f90780b27c418d
commit + 2f5ba04a772e26838b2731cff112a894e1f25fba
blob - d71e382216f13f4b7bbd57d20583ae2f733d8de6
blob + fe70eb45e9a9308bc0afb9bbaf059bbaa07b1df7
--- src/main.rs
+++ src/main.rs
}
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
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,
async fn prompt_field(
stdin: &mut BufReader<tokio::io::Stdin>,
- stdout: &mut tokio::io::Stdout,
field_name: &str,
current: &str,
) -> Result<String> {
print!("{field_name} [{current}]: ");
- stdout.flush().await?;
+ std::io::stdout().flush()?;
let mut input = String::new();
stdin.read_line(&mut input).await?;
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<std::path::PathBuf>> = 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)> {