diff --git a/src/components/logic/aiff.rs b/src/components/logic/aiff.rs index b563a31a..bd9c291a 100644 --- a/src/components/logic/aiff.rs +++ b/src/components/logic/aiff.rs @@ -1,11 +1,11 @@ use crate::{FileProperties, LoftyError, Result}; -use std::cmp::{max, min}; use std::fs::File; use std::io::{Read, Seek, SeekFrom, Write}; use std::time::Duration; use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; +use std::collections::HashMap; fn verify_aiff(data: &mut T) -> Result<()> where @@ -132,20 +132,11 @@ where cfg_if::cfg_if! { if #[cfg(feature = "format-aiff")] { - type AiffTags = ( - Option, - Option, - Option, - FileProperties, - ); - - pub(crate) fn read_from(data: &mut T) -> Result + pub(crate) fn read_from(data: &mut T) -> Result<(HashMap, FileProperties)> where T: Read + Seek, { - let mut name_id: Option = None; - let mut author_id: Option = None; - let mut copyright_id: Option = None; + let mut metadata = HashMap::::new(); let properties = read_properties(data)?; @@ -153,84 +144,49 @@ cfg_if::cfg_if! { data.read_u32::(), data.read_u32::(), ) { - match &fourcc.to_le_bytes() { - f if f == b"NAME" && name_id.is_none() => { - let mut name = vec![0; size as usize]; - data.read_exact(&mut name)?; + let fourcc_b = &fourcc.to_le_bytes(); - name_id = Some(String::from_utf8(name)?); - }, - f if f == b"AUTH" && author_id.is_none() => { - let mut auth = vec![0; size as usize]; - data.read_exact(&mut auth)?; + if fourcc_b == b"NAME" || fourcc_b == b"AUTH" || fourcc_b == b"(c) " { + let mut value = vec![0; size as usize]; + data.read_exact(&mut value)?; - author_id = Some(String::from_utf8(auth)?); - }, - f if f == b"(c) " && copyright_id.is_none() => { - let mut copy = vec![0; size as usize]; - data.read_exact(&mut copy)?; - - copyright_id = Some(String::from_utf8(copy)?); - }, - _ => { - data.seek(SeekFrom::Current(i64::from(size)))?; - }, + metadata.insert( + String::from_utf8(fourcc_b.to_vec())?, + String::from_utf8(value)?, + ); + continue; } + + data.seek(SeekFrom::Current(i64::from(size)))?; } - Ok((name_id, author_id, copyright_id, properties)) + Ok((metadata, properties)) } - pub(crate) fn write_to( - data: &mut File, - metadata: (Option<&String>, Option<&String>, Option<&String>), - ) -> Result<()> { + pub(crate) fn write_to(data: &mut File, metadata: &HashMap) -> Result<()> { verify_aiff(data)?; let mut text_chunks = Vec::new(); - if let Some(name_id) = metadata.0 { - let len = (name_id.len() as u32).to_be_bytes(); + for (k, v) in metadata { + let len = (v.len() as u32).to_be_bytes(); - text_chunks.extend(b"NAME".iter()); + text_chunks.extend(k.as_bytes().iter()); text_chunks.extend(len.iter()); - text_chunks.extend(name_id.as_bytes().iter()); + text_chunks.extend(v.as_bytes().iter()); } - if let Some(author_id) = metadata.1 { - let len = (author_id.len() as u32).to_be_bytes(); - - text_chunks.extend(b"AUTH".iter()); - text_chunks.extend(len.iter()); - text_chunks.extend(author_id.as_bytes().iter()); - } - - if let Some(copyright_id) = metadata.2 { - let len = (copyright_id.len() as u32).to_be_bytes(); - - text_chunks.extend(b"(c) ".iter()); - text_chunks.extend(len.iter()); - text_chunks.extend(copyright_id.as_bytes().iter()); - } - - let mut name: Option<(usize, usize)> = None; - let mut auth: Option<(usize, usize)> = None; - let mut copy: Option<(usize, usize)> = None; + let mut chunks_remove = Vec::new(); while let (Ok(fourcc), Ok(size)) = ( data.read_u32::(), data.read_u32::(), ) { + let fourcc_b = &fourcc.to_le_bytes(); let pos = (data.seek(SeekFrom::Current(0))? - 8) as usize; - match &fourcc.to_le_bytes() { - f if f == b"NAME" && name.is_none() => name = Some((pos, (pos + 8 + size as usize))), - f if f == b"AUTH" && auth.is_none() => auth = Some((pos, (pos + 8 + size as usize))), - f if f == b"(c) " && copy.is_none() => copy = Some((pos, (pos + 8 + size as usize))), - _ => { - data.seek(SeekFrom::Current(i64::from(size)))?; - continue; - }, + if fourcc_b == b"NAME" || fourcc_b == b"AUTH" || fourcc_b == b"(c) " { + chunks_remove.push((pos, (pos + 8 + size as usize))) } data.seek(SeekFrom::Current(i64::from(size)))?; @@ -241,43 +197,25 @@ cfg_if::cfg_if! { let mut file_bytes = Vec::new(); data.read_to_end(&mut file_bytes)?; - match (name, auth, copy) { - (None, None, None) => { - data.seek(SeekFrom::Start(16))?; + if chunks_remove.is_empty() { + data.seek(SeekFrom::Start(16))?; - let mut size = [0; 4]; - data.read_exact(&mut size)?; + let mut size = [0; 4]; + data.read_exact(&mut size)?; - let comm_end = (20 + u32::from_le_bytes(size)) as usize; - file_bytes.splice(comm_end..comm_end, text_chunks); - }, - (Some(single_value), None, None) - | (None, Some(single_value), None) - | (None, None, Some(single_value)) => { - file_bytes.splice(single_value.0..single_value.1, text_chunks); - }, - #[rustfmt::skip] - (Some(a), Some(b), None) - | (Some(a), None, Some(b)) - | (None, Some(a), Some(b)) => { - let first = min(a, b); - let end = max(a, b); + let comm_end = (20 + u32::from_le_bytes(size)) as usize; + file_bytes.splice(comm_end..comm_end, text_chunks); + } else { + chunks_remove.sort_unstable(); + chunks_remove.reverse(); - file_bytes.drain(end.0..end.1); - file_bytes.splice(first.0..first.1, text_chunks); - }, - (Some(title), Some(author), Some(copyright)) => { - let mut items = vec![title, author, copyright]; - items.sort_unstable(); + let first = chunks_remove.pop().unwrap(); - let first = items[0]; - let mid = items[1]; - let end = items[2]; + for (s, e) in &chunks_remove { + file_bytes.drain(*s as usize..*e as usize); + } - file_bytes.drain(end.0..end.1); - file_bytes.drain(mid.0..mid.1); - file_bytes.splice(first.0..first.1, text_chunks); - }, + file_bytes.splice(first.0 as usize..first.1 as usize, text_chunks); } let total_size = ((file_bytes.len() - 8) as u32).to_be_bytes(); diff --git a/src/components/logic/ogg/flac.rs b/src/components/logic/ogg/flac.rs index 72acaa06..f72dac07 100644 --- a/src/components/logic/ogg/flac.rs +++ b/src/components/logic/ogg/flac.rs @@ -8,8 +8,8 @@ use std::fs::File; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use std::time::Duration; -use unicase::UniCase; use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt}; +use unicase::UniCase; struct Block { byte: u8, diff --git a/src/components/logic/ogg/opus.rs b/src/components/logic/ogg/opus.rs index 7d71e2ba..431bd2f2 100644 --- a/src/components/logic/ogg/opus.rs +++ b/src/components/logic/ogg/opus.rs @@ -1,8 +1,8 @@ use super::find_last_page; use crate::{FileProperties, LoftyError, Result}; -use std::io::{Read, Seek, SeekFrom, Write}; use std::fs::File; +use std::io::{Read, Seek, SeekFrom, Write}; use std::time::Duration; use byteorder::{LittleEndian, ReadBytesExt}; diff --git a/src/components/logic/ogg/vorbis.rs b/src/components/logic/ogg/vorbis.rs index e1805a3c..32a523c1 100644 --- a/src/components/logic/ogg/vorbis.rs +++ b/src/components/logic/ogg/vorbis.rs @@ -2,8 +2,8 @@ use super::find_last_page; use crate::components::logic::ogg::constants::VORBIS_SETUP_HEAD; use crate::{FileProperties, LoftyError, Result}; -use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use std::fs::File; +use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use std::time::Duration; use byteorder::{LittleEndian, ReadBytesExt}; diff --git a/src/components/tags/aiff_tag.rs b/src/components/tags/aiff_tag.rs index d0ea7769..fd0a6dbe 100644 --- a/src/components/tags/aiff_tag.rs +++ b/src/components/tags/aiff_tag.rs @@ -4,16 +4,15 @@ use crate::{ ToAnyTag, }; +use std::collections::HashMap; use std::fs::File; use std::io::{Read, Seek}; -use lofty_attr::LoftyTag; +use lofty_attr::{get_set_methods, LoftyTag}; #[derive(Default)] struct AiffInnerTag { - name_id: Option, - author_id: Option, - copyright_id: Option, + data: HashMap, } #[derive(LoftyTag)] @@ -32,50 +31,35 @@ impl AiffTag { where R: Read + Seek, { - let (name_id, author_id, copyright_id, properties) = aiff::read_from(reader)?; + let (data, properties) = aiff::read_from(reader)?; Ok(Self { - inner: AiffInnerTag { - name_id, - author_id, - copyright_id, - }, + inner: AiffInnerTag { data }, properties, _format: TagType::AiffText, }) } + + fn get_value(&self, key: &str) -> Option<&str> { + self.inner.data.get_key_value(key).map(|(_, v)| v.as_str()) + } + + fn set_value(&mut self, key: &str, val: V) + where + V: Into, + { + self.inner.data.insert(key.into(), val.into()); + } + + fn remove_key(&mut self, key: &str) { + self.inner.data.remove(key); + } } impl AudioTagEdit for AiffTag { - fn title(&self) -> Option<&str> { - self.inner.name_id.as_deref() - } - fn set_title(&mut self, title: &str) { - self.inner.name_id = Some(title.to_string()) - } - fn remove_title(&mut self) { - self.inner.name_id = None - } - - fn artist(&self) -> Option<&str> { - self.inner.author_id.as_deref() - } - fn set_artist(&mut self, artist: &str) { - self.inner.author_id = Some(artist.to_string()) - } - fn remove_artist(&mut self) { - self.inner.author_id = None - } - - fn copyright(&self) -> Option<&str> { - self.inner.copyright_id.as_deref() - } - fn set_copyright(&mut self, copyright: &str) { - self.inner.copyright_id = Some(copyright.to_string()) - } - fn remove_copyright(&mut self) { - self.inner.copyright_id = None - } + get_set_methods!(title, "NAME"); + get_set_methods!(artist, "AUTH"); + get_set_methods!(copyright, "(c) "); fn tag_type(&self) -> TagType { TagType::AiffText @@ -88,13 +72,6 @@ impl AudioTagEdit for AiffTag { impl AudioTagWrite for AiffTag { fn write_to(&self, file: &mut File) -> Result<()> { - aiff::write_to( - file, - ( - self.inner.name_id.as_ref(), - self.inner.author_id.as_ref(), - self.inner.copyright_id.as_ref(), - ), - ) + aiff::write_to(file, &self.inner.data) } }