use std::{env, fs::{self, File}, path::Path, io::{Error, BufReader, Read}, fmt::format, os::fd::IntoRawFd}; 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 truncate(s: &str, min_chars: usize, max_chars: usize) -> &str { let e = match s.char_indices().nth(min_chars) { None => s, Some((idx, _)) => &s[idx..], }; if max_chars < min_chars { return ""; } return match e.char_indices().nth(max_chars - min_chars) { None => e, Some((idx, _)) => &e[..idx], } } 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() { let args = Args::parse(); let music_path: String; if let Some(path) = args.path { music_path = path; } else if let Ok(home) = env::var("HOME") { println!("💡 Trying to find the music folder automatically"); music_path = home + "/Music" } else { eprintln!("🔥 Couldn't find a music folder automatically"); std::process::exit(1); } let music_dir: fs::ReadDir; if let Ok(dir) = fs::read_dir(music_path.as_str()) { music_dir = dir; } else { eprintln!("🔥 Dir \"{}\" doesn't exits", music_path.as_str()); std::process::exit(1); } println!("💡 Start scanning \"{}\" dir for ogg file", music_path.as_str()); for path in music_dir { // println!("Name: {}", path.unwrap().file_name().to_str().unwrap()) if path.as_ref().unwrap().file_type().unwrap().is_file() { tag_ogg_file(path.unwrap().path().as_path()); } } println!("💡 Ended successfully"); }