use std::{env, fs::{self, File}, path::Path, fmt::format, os::fd::IntoRawFd}; use std::io::{Error, ErrorKind, BufReader, Read}; use lofty::{Probe, TaggedFileExt, LoftyError, TagExt, Tag, Picture, Accessor, PictureType}; use clap::Parser; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { /// Path to your music folder #[arg(short, long)] path: Option, /// Print more logs while running TODO #[arg(short, long, default_value_t = false)] verbose: bool, } /* Title Artist - Title Artist - Album - Title Artist - Album - Nr - Title Artist - Album - Nr - Max Nr - Title */ struct InnerTag { title: String, artist: Option, album: Option, track: Option, } impl InnerTag { pub fn empty() -> InnerTag { InnerTag {title: "".into(), artist: None, album: None, track: None} } pub fn from(name: &str) -> Result{ let mut inner_tag: InnerTag = InnerTag::empty(); let mut tags: Vec = Vec::new(); { // TODO make it faster and cleaner let mut text: String = "".into(); let mut connected = false; for s in name.split("-") { if text == "" { text = s.into(); } else { if s == "" { connected = true; text += "-"; } else if connected { text += s; connected = false; } else { tags.push(text); text = s.into(); } } } tags.push(text); } match tags.len() { 1 => { inner_tag.title = tags.remove(0); }, 2 => { inner_tag.artist = Some(tags.remove(0)); inner_tag.title = tags.remove(0); }, 3 => { inner_tag.artist = Some(tags.remove(0)); inner_tag.album = Some(tags.remove(0)); inner_tag.title = tags.remove(0); }, 4 => { inner_tag.artist = Some(tags.remove(0)); inner_tag.album = Some(tags.remove(0)); inner_tag.track = Some(tags.remove(0).parse().unwrap()); inner_tag.title = tags.remove(0); }, 5 => { inner_tag.artist = Some(tags.remove(0)); inner_tag.album = Some(tags.remove(0)); inner_tag.track = Some(tags.remove(0).parse().unwrap()); inner_tag.title = tags.remove(1); }, _ => panic!("🔥 To many tags in {}", name) } return Ok(inner_tag); } } fn tag_ogg_file(path: &Path) -> Result<(), LoftyError> { let mut tagged_file = Probe::open(path) .expect("ERROR: Bad path provided!") .read()?; let tag = match tagged_file.primary_tag_mut() { Some(primary_tag) => primary_tag, None => { if let Some(first_tag) = tagged_file.first_tag_mut() { first_tag } else { let tag_type = tagged_file.primary_tag_type(); eprintln!("👻 No tags found, creating a new tag of type `{tag_type:?}`"); tagged_file.insert_tag(Tag::new(tag_type)); tagged_file.primary_tag_mut().unwrap() } }, }; let path_without_ext = path.with_extension(""); let name = path_without_ext.file_name().unwrap().to_str().unwrap(); let inner_tag = InnerTag::from(name).unwrap(); tag.clear(); tag.set_title(inner_tag.title); tag.set_artist(inner_tag.artist.unwrap_or("".into())); tag.set_album(inner_tag.album.unwrap_or("".into())); if let Some(track) = inner_tag.track { tag.set_track(track); } if let Some(album) = tag.album() { if album != "" { let image_path = path.parent().unwrap().join(format!(".cover/{}/{}.jpg", tag.artist().unwrap(), album)); if image_path.exists() { let image_file = &mut File::open(image_path.clone()).unwrap(); let mut picture = Picture::from_reader(image_file).unwrap(); picture.set_pic_type(PictureType::CoverFront); tag.set_picture(0, picture) } else { eprintln!("👻 Can't found image {}", image_path.to_str().unwrap()); } } } tag.save_to_path(path).expect("ERROR: Can't save ogg file"); println!("✅ {}", name); return Ok(()); } fn main() -> Result<(), Box> { let args = Args::parse(); let music_path = args.path.unwrap_or_else(|| { println!("💡 Trying to find the music folder automatically"); env::var("HOME").map(|home| format!("{}/Music", home)) .expect("🔥 Couldn't find a music folder automatically") }); let music_dir = fs::read_dir(&music_path) .map_err(|_| format!("🔥 Dir \"{}\" doesn't exist", music_path))?; println!("💡 Start scanning \"{}\" dir for ogg file", music_path.as_str()); for entry in music_dir { let path = entry?.path(); if path.is_file() { // Improved error handling if let Err(e) = tag_ogg_file(&path) { eprintln!("🔥 Error tagging file {:?}: {}", path, e); } } } println!("💡 Ended successfully"); Ok(()) }