use crate::*; use std::fs::File; use std::io::{BufReader, Read}; use std::borrow::Borrow; use byte_unit::{Byte, Unit::MB}; use lofty::{file::TaggedFileExt, probe::Probe, tag::{Accessor, Tag, TagItem, ItemKey, ItemValue}}; pub enum Metadata { Directory { hash_file: Option<()>, catalog_file: Option<()>, artist_file: Option<()>, release_file: Option<()>, }, Music { invalid: bool, hash: Arc, size: Arc, tag: Option>, new_tag: Option>, }, Image { invalid: bool, hash: Arc, size: Arc, title: Option, author: Option, }, Unknown { hash: Arc, size: Arc, } } impl Metadata { pub fn new (path: &Path) -> Usually { let reader = BufReader::new(File::open(path)?); let probe = Probe::new(reader) //.options(ParseOptions::new().parsing_mode(ParsingMode::Strict)) .guess_file_type()?; if probe.file_type().is_some() { let file = lofty::read_from_path(path)?; let tag = file.primary_tag(); let data = std::fs::read(path)?; let hash = hex::encode(xxh3_64(data.as_slice()).to_be_bytes()).into(); let size = Byte::from_u64(data.len() as u64).get_adjusted_unit(MB); Ok(Self::Music { hash, size: format!("{:#>8.2}", size).into(), tag: tag.map(|t|t.clone().into()), new_tag: tag.map(|t|t.clone().into()), invalid: false, }) } else { Self::new_fallback(path) } } pub fn new_fallback (path: &Path) -> Usually { let file = File::open(path)?; let size = Byte::from_u64(file.metadata()?.len() as u64).get_adjusted_unit(MB); let mut reader = BufReader::new(file); let mut bytes = vec![0;16]; reader.read(&mut bytes)?; // PNG if bytes.starts_with(&[0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A]) { let mut bytes = vec![]; BufReader::new(File::open(path)?).read(&mut bytes)?; return Ok(Self::Image { hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), size: format!("{:#>8.2}", size).into(), title: None, author: None, invalid: false, }) } // JPG if bytes.starts_with(&[0xFF, 0xD8, 0xFF, 0xDB]) || bytes.starts_with(&[0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01]) || bytes.starts_with(&[0xFF, 0xD8, 0xFF, 0xEE]) || (bytes.starts_with(&[0xFF, 0xD8, 0xFF, 0xE1]) && bytes.get(6) == Some(&0x45) && bytes.get(7) == Some(&0x78) && bytes.get(8) == Some(&0x69) && bytes.get(9) == Some(&0x66) && bytes.get(10) == Some(&0x00) && bytes.get(11) == Some(&0x00)) { return Ok(Self::Image { hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), size: format!("{:#>8.2}", size).into(), title: None, author: None, invalid: false, }) } Ok(Self::Unknown { size: format!("{:#>8.2}", size).into(), hash: hex::encode(xxh3_64(&bytes).to_be_bytes()).into(), }) } pub fn hash (&self) -> Option> { match self { Metadata::Image { hash, .. } => Some(hash.clone()), Metadata::Music { hash, .. } => Some(hash.clone()), Metadata::Unknown { hash, .. } => Some(hash.clone()), _ => None } } pub fn size (&self) -> Option> { match self { Metadata::Image { size, .. } => Some(size.clone()), Metadata::Music { size, .. } => Some(size.clone()), Metadata::Unknown { size, .. } => Some(size.clone()), _ => None } } } macro_rules! metadata_field { (string: $get:ident $set:ident $key:expr) => { impl Metadata { pub fn $get (&self) -> Option> { if let Metadata::Music { tag: Some(tag), .. } = self { return tag.$get().map(|t|t.into()) } None } pub fn $set (&mut self, value: &impl AsRef) -> Option { if let Metadata::Music { tag: Some(old_tag), new_tag, .. } = self { if old_tag.$get().is_none() { } if let Some(old_value) = old_tag.$get() { if old_value != value.as_ref() { } } } //match self { //Metadata::Music { tag: Some(tag), new_tag, .. } => { //} //_ => None //} //let old_tag = &mut self.tag; //let new_tag = &mut self.new_tag; //if let Metadata::Music { tag: Some(tag), .. } = self //&& Some(value.as_ref()) != tag.$get().as_deref() //{ //*$field = Some(value.as_ref().into()); //return Some(TagItem::new($key, ItemValue::Text(value.as_ref().into()))) //} None } } }; (number: $get:ident $set:ident $key:expr) => { impl Metadata { pub fn $get (&self) -> Option> { if let Metadata::Music { tag: Some(tag), .. } = self { return tag.$get().map(|t|format!("{t}").into()) } None } pub fn $set (&mut self, value: &impl AsRef) -> Option { if let Ok(value) = value.as_ref().trim().parse::() { if let Metadata::Music { tag: Some(old_tag), new_tag, .. } = self { if old_tag.$get().is_none() { } if let Some(old_value) = old_tag.$get() { if old_value != value { } } } } //let old_tag = &mut self.tag; //let new_tag = &mut self.new_tag; //if let Metadata::Music { tag: Some(tag), .. } = self //&& let Ok(value) = value.as_ref().trim().parse::() //&& Some(value) != *$field //{ //*$field = Some(value); //return Some(TagItem::new($key, ItemValue::Text(format!("{value}")))) //} None } } }; } metadata_field!(string: artist set_artist ItemKey::TrackArtist); metadata_field!(number: year set_year ItemKey::Year); metadata_field!(string: album set_album ItemKey::AlbumTitle); metadata_field!(number: track set_track ItemKey::TrackNumber); metadata_field!(string: title set_title ItemKey::TrackTitle);