Cleanup AIFF text chunk read/write

This commit is contained in:
Serial 2021-07-26 20:56:17 -04:00
parent 549a5d4730
commit d94816e439
5 changed files with 65 additions and 150 deletions

View file

@ -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<T>(data: &mut T) -> Result<()>
where
@ -132,20 +132,11 @@ where
cfg_if::cfg_if! {
if #[cfg(feature = "format-aiff")] {
type AiffTags = (
Option<String>,
Option<String>,
Option<String>,
FileProperties,
);
pub(crate) fn read_from<T>(data: &mut T) -> Result<AiffTags>
pub(crate) fn read_from<T>(data: &mut T) -> Result<(HashMap<String, String>, FileProperties)>
where
T: Read + Seek,
{
let mut name_id: Option<String> = None;
let mut author_id: Option<String> = None;
let mut copyright_id: Option<String> = None;
let mut metadata = HashMap::<String, String>::new();
let properties = read_properties(data)?;
@ -153,84 +144,49 @@ cfg_if::cfg_if! {
data.read_u32::<LittleEndian>(),
data.read_u32::<BigEndian>(),
) {
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<String, String>) -> 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::<LittleEndian>(),
data.read_u32::<BigEndian>(),
) {
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();

View file

@ -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,

View file

@ -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};

View file

@ -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};

View file

@ -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<String>,
author_id: Option<String>,
copyright_id: Option<String>,
data: HashMap<String, String>,
}
#[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<V>(&mut self, key: &str, val: V)
where
V: Into<String>,
{
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)
}
}