use lofty::config::WriteOptions; use lofty::tag::TagItem; use lofty::tag::TagType::VorbisComments; use lofty::tag::{ItemKey, ItemValue}; use lofty::tag::{Tag, TagExt}; use regex::Regex; use std::collections::HashMap; use std::env::var; use std::io::Write; use std::path::PathBuf; use std::{fs::read_dir, fs::File, io::Read, process::Command}; pub fn edit(file_path: Vec, extra_files: [String; 8]) -> std::io::Result<()> { let tmp_file_path = PathBuf::from("/tmp/keks-sort-metadata"); let mut tmp_file = File::create(&tmp_file_path)?; let editor = var("EDITOR").unwrap(); let mut files = Vec::new(); struct FileMetadata { path: PathBuf, tag: Tag, } for file in &file_path { if file.is_dir() { for music_file in read_dir(file)? { let path = music_file?.path(); if path .extension() .and_then(|ext| ext.to_str()) .is_some_and(|ext| extra_files.iter().any(|x| x == ext)) { continue; } let tag = crate::get_metadata(&path); files.push(FileMetadata { path, tag }); } } else { let tag = &crate::get_metadata(file); files.push(FileMetadata { path: file.to_path_buf(), tag: tag.clone(), }); } } for file in &files { writeln!( tmp_file.try_clone()?, "------------ {:?} ------------", file.path )?; for item in file.tag.items() { writeln!( tmp_file, "{} = {}", item.key().map_key(VorbisComments).unwrap(), item.value().text().unwrap() )?; } writeln!(tmp_file.try_clone()?)?; // writes empty line } Command::new(editor) .arg(&tmp_file_path) .status() .expect("Something went wrong"); let mut editable = String::new(); File::open(&tmp_file_path) .expect("Could not open file") .read_to_string(&mut editable)?; let parsed = parse(&editable); for (mut file, parse) in files.into_iter().zip(parsed) { for (key, value) in &parse { let item_key = ItemKey::from_key(VorbisComments, key).expect("ERROR: Failed to get item key"); let item = TagItem::new(item_key, ItemValue::Text(value.to_string())); file.tag.insert(item); } file.tag .save_to_path(&file.path, WriteOptions::default()) .expect("ERROR: Failed to write tag"); } Ok(()) } fn parse(input: &str) -> Vec> { let re = Regex::new(r#"(?m)^------------ ".*?" ------------\r?\n"#).unwrap(); let parts: Vec<&str> = re .split(input) .filter(|part| !part.trim().is_empty()) .collect(); let result: Vec> = parts .into_iter() .filter_map(|part| { let part = part.trim(); if part.is_empty() { return None; } Some( part.lines() .filter_map(|line| { let (key, value) = line.split_once('=')?; Some((key.trim().to_string(), value.trim().to_string())) }) .collect(), ) }) .collect(); result }