From 5638326ff2a0ff67e39bd424d44480f8bc0d21a3 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Sat, 27 Nov 2021 13:28:40 -0500 Subject: [PATCH] Add tests for reading individual tag formats --- src/logic/ape/tag/item.rs | 1 + src/logic/ape/tag/mod.rs | 10 +- src/logic/id3/v1/read.rs | 6 +- src/logic/id3/v1/tag.rs | 6 +- src/logic/id3/v2/read.rs | 5 +- src/logic/id3/v2/tag.rs | 11 +- src/logic/iff/chunk.rs | 4 +- src/logic/iff/wav/tag/mod.rs | 20 ++- src/logic/mp3/read.rs | 2 +- src/logic/mp4/atom_info.rs | 2 +- src/logic/mp4/ilst/mod.rs | 39 ++++- src/logic/mp4/ilst/read.rs | 8 +- src/logic/mp4/moov.rs | 2 +- src/logic/ogg/tag.rs | 56 ++++++- tests/tags/assets/test.apev2 | Bin 0 -> 209 bytes tests/tags/assets/test.id3v1 | Bin 0 -> 128 bytes tests/tags/assets/test.id3v2 | Bin 0 -> 1168 bytes tests/tags/assets/test.ilst | Bin 0 -> 1024 bytes tests/tags/assets/test.riff | Bin 0 -> 100 bytes tests/tags/assets/test.vorbis | Bin 0 -> 152 bytes tests/tags/main.rs | 1 + tests/tags/read.rs | 268 ++++++++++++++++++++++++++++++++++ 22 files changed, 418 insertions(+), 23 deletions(-) create mode 100644 tests/tags/assets/test.apev2 create mode 100644 tests/tags/assets/test.id3v1 create mode 100644 tests/tags/assets/test.id3v2 create mode 100644 tests/tags/assets/test.ilst create mode 100644 tests/tags/assets/test.riff create mode 100644 tests/tags/assets/test.vorbis create mode 100644 tests/tags/main.rs create mode 100644 tests/tags/read.rs diff --git a/src/logic/ape/tag/item.rs b/src/logic/ape/tag/item.rs index cdd68697..3f79480e 100644 --- a/src/logic/ape/tag/item.rs +++ b/src/logic/ape/tag/item.rs @@ -5,6 +5,7 @@ use crate::types::tag::TagType; use std::convert::TryFrom; +#[derive(Debug, PartialEq)] pub struct ApeItem { pub read_only: bool, pub(crate) key: String, diff --git a/src/logic/ape/tag/mod.rs b/src/logic/ape/tag/mod.rs index 631d6106..3d8c9a6b 100644 --- a/src/logic/ape/tag/mod.rs +++ b/src/logic/ape/tag/mod.rs @@ -10,8 +10,9 @@ use crate::types::tag::{Tag, TagType}; use std::collections::HashMap; use std::convert::TryInto; use std::fs::File; +use std::io::{Read, Seek}; -#[derive(Default)] +#[derive(Default, Debug, PartialEq)] /// An APE tag pub struct ApeTag { pub read_only: bool, @@ -33,6 +34,13 @@ impl ApeTag { } impl ApeTag { + pub fn read_from(reader: &mut R) -> Result + where + R: Read + Seek, + { + Ok(read::read_ape_tag(reader, false)?.0) + } + pub fn write_to(&self, file: &mut File) -> Result<()> { Into::::into(self).write_to(file) } diff --git a/src/logic/id3/v1/read.rs b/src/logic/id3/v1/read.rs index 1ecf9c2d..f269c5b8 100644 --- a/src/logic/id3/v1/read.rs +++ b/src/logic/id3/v1/read.rs @@ -19,10 +19,10 @@ pub fn parse_id3v1(reader: [u8; 128]) -> Id3v1Tag { tag.album = decode_text(&reader[60..90]); tag.year = decode_text(&reader[90..94]); - let range = if reader[119] == 0 && reader[122] != 0 { - tag.track_number = Some(reader[122]); + let range = if reader[119] == 0 && reader[123] != 0 { + tag.track_number = Some(reader[123]); - 94_usize..123 + 94_usize..122 } else { 94..124 }; diff --git a/src/logic/id3/v1/tag.rs b/src/logic/id3/v1/tag.rs index af601373..71bb1abe 100644 --- a/src/logic/id3/v1/tag.rs +++ b/src/logic/id3/v1/tag.rs @@ -5,7 +5,7 @@ use crate::types::tag::{Tag, TagType}; use std::fs::File; -#[derive(Default, Debug)] +#[derive(Default, Debug, PartialEq)] /// An ID3v1 tag /// /// ID3v1 is a severely limited format, with each field @@ -59,6 +59,10 @@ impl Id3v1Tag { && self.genre.is_none() } + pub fn read_from(tag: [u8; 128]) -> Self { + super::read::parse_id3v1(tag) + } + pub fn write_to(&self, file: &mut File) -> Result<()> { Into::::into(self).write_to(file) } diff --git a/src/logic/id3/v2/read.rs b/src/logic/id3/v2/read.rs index 66751a55..49b5e2df 100644 --- a/src/logic/id3/v2/read.rs +++ b/src/logic/id3/v2/read.rs @@ -11,7 +11,10 @@ use std::io::Read; use byteorder::{BigEndian, ReadBytesExt}; -pub(crate) fn parse_id3v2(bytes: &mut &[u8]) -> Result { +pub(crate) fn parse_id3v2(bytes: &mut R) -> Result +where + R: Read, +{ let mut header = [0; 10]; bytes.read_exact(&mut header)?; diff --git a/src/logic/id3/v2/tag.rs b/src/logic/id3/v2/tag.rs index d5917935..a083a38d 100644 --- a/src/logic/id3/v2/tag.rs +++ b/src/logic/id3/v2/tag.rs @@ -11,9 +11,11 @@ use crate::types::tag::{Tag, TagType}; use std::convert::TryInto; use std::fs::File; +use std::io::Read; use byteorder::ByteOrder; +#[derive(PartialEq, Debug)] pub struct Id3v2Tag { flags: Id3v2TagFlags, pub(super) original_version: Id3v2Version, @@ -80,6 +82,13 @@ impl Id3v2Tag { } impl Id3v2Tag { + pub fn read_from(reader: &mut R) -> Result + where + R: Read, + { + super::read::parse_id3v2(reader) + } + pub fn write_to(&self, file: &mut File) -> Result<()> { Into::::into(self).write_to(file) } @@ -156,7 +165,7 @@ impl From for Id3v2Tag { } } -#[derive(Default, Copy, Clone)] +#[derive(Default, Copy, Clone, Debug, PartialEq)] #[allow(clippy::struct_excessive_bools)] /// Flags that apply to the entire tag pub struct Id3v2TagFlags { diff --git a/src/logic/iff/chunk.rs b/src/logic/iff/chunk.rs index 8451e301..49fa2557 100644 --- a/src/logic/iff/chunk.rs +++ b/src/logic/iff/chunk.rs @@ -27,7 +27,7 @@ impl Chunks { pub fn next(&mut self, data: &mut R) -> Result<()> where - R: Read + Seek, + R: Read, { data.read_exact(&mut self.fourcc)?; self.size = data.read_u32::()?; @@ -37,7 +37,7 @@ impl Chunks { pub fn content(&mut self, data: &mut R) -> Result> where - R: Read + Seek, + R: Read, { let mut content = vec![0; self.size as usize]; data.read_exact(&mut content)?; diff --git a/src/logic/iff/wav/tag/mod.rs b/src/logic/iff/wav/tag/mod.rs index 66d7c5e1..9a7de86c 100644 --- a/src/logic/iff/wav/tag/mod.rs +++ b/src/logic/iff/wav/tag/mod.rs @@ -6,8 +6,9 @@ use crate::types::item::{ItemKey, ItemValue, TagItem}; use crate::types::tag::{Tag, TagType}; use std::fs::File; +use std::io::{Read, Seek}; -#[derive(Default)] +#[derive(Default, Debug, PartialEq)] /// A RIFF INFO LIST pub struct RiffInfoList { /// A collection of chunk-value pairs @@ -15,8 +16,12 @@ pub struct RiffInfoList { } impl RiffInfoList { - pub fn push(&mut self, key: String, value: String) { + pub fn insert(&mut self, key: String, value: String) { if valid_key(key.as_str()) { + self.items + .iter() + .position(|(k, _)| k == &key) + .map(|p| self.items.remove(p)); self.items.push((key, value)) } } @@ -34,6 +39,17 @@ impl RiffInfoList { } impl RiffInfoList { + pub fn read_from(reader: &mut R, end: u64) -> Result + where + R: Read + Seek, + { + let mut tag = Self::default(); + + read::parse_riff_info(reader, end, &mut tag)?; + + Ok(tag) + } + pub fn write_to(&self, file: &mut File) -> Result<()> { Into::::into(self).write_to(file) } diff --git a/src/logic/mp3/read.rs b/src/logic/mp3/read.rs index 8566838a..444a173d 100644 --- a/src/logic/mp3/read.rs +++ b/src/logic/mp3/read.rs @@ -13,7 +13,7 @@ use std::time::Duration; use byteorder::{BigEndian, ByteOrder, ReadBytesExt}; fn read_properties( - mut first_frame: (Header, u64), + first_frame: (Header, u64), last_frame: (Header, u64), xing_header: Option, file_length: u64, diff --git a/src/logic/mp4/atom_info.rs b/src/logic/mp4/atom_info.rs index 45a28a6f..20217509 100644 --- a/src/logic/mp4/atom_info.rs +++ b/src/logic/mp4/atom_info.rs @@ -1,8 +1,8 @@ use crate::error::{LoftyError, Result}; +use crate::logic::mp4::ilst::AtomIdent; use std::io::{Read, Seek, SeekFrom}; -use crate::mp4::AtomIdent; use byteorder::{BigEndian, ReadBytesExt}; pub(crate) struct AtomInfo { diff --git a/src/logic/mp4/ilst/mod.rs b/src/logic/mp4/ilst/mod.rs index f63d91fe..eb4053f3 100644 --- a/src/logic/mp4/ilst/mod.rs +++ b/src/logic/mp4/ilst/mod.rs @@ -1,19 +1,48 @@ pub(in crate::logic::mp4) mod read; pub(in crate::logic) mod write; +use crate::error::Result; use crate::types::item::{ItemKey, ItemValue, TagItem}; use crate::types::picture::{Picture, PictureType}; use crate::types::tag::{Tag, TagType}; use std::convert::TryInto; +use std::io::Read; #[cfg(feature = "mp4_atoms")] -#[derive(Default)] +#[derive(Default, PartialEq, Debug)] /// An Mp4 pub struct Ilst { pub(crate) atoms: Vec, } +impl Ilst { + pub fn atom(&self, ident: &AtomIdent) -> Option<&Atom> { + self.atoms.iter().find(|a| &a.ident == ident) + } + + pub fn insert_atom(&mut self, atom: Atom) { + self.remove_atom(&atom.ident); + self.atoms.push(atom); + } + + pub fn remove_atom(&mut self, ident: &AtomIdent) { + self.atoms + .iter() + .position(|a| &a.ident == ident) + .map(|p| self.atoms.remove(p)); + } +} + +impl Ilst { + pub fn read_from(reader: &mut R, len: u64) -> Result + where + R: Read, + { + read::parse_ilst(reader, len) + } +} + #[cfg(feature = "mp4_atoms")] impl From for Tag { fn from(input: Ilst) -> Self { @@ -80,11 +109,18 @@ impl From for Ilst { } #[cfg(feature = "mp4_atoms")] +#[derive(Debug, PartialEq)] pub struct Atom { ident: AtomIdent, data: AtomData, } +impl Atom { + pub fn new(ident: AtomIdent, data: AtomData) -> Self { + Self { ident, data } + } +} + #[derive(Eq, PartialEq, Debug)] pub enum AtomIdent { /// A four byte identifier @@ -109,6 +145,7 @@ pub enum AtomIdent { } #[cfg(feature = "mp4_atoms")] +#[derive(Debug, PartialEq)] /// The data of an atom /// /// NOTES: diff --git a/src/logic/mp4/ilst/read.rs b/src/logic/mp4/ilst/read.rs index b5a6bddc..a14771fb 100644 --- a/src/logic/mp4/ilst/read.rs +++ b/src/logic/mp4/ilst/read.rs @@ -10,12 +10,12 @@ use std::io::{Cursor, Read, Seek, SeekFrom}; use byteorder::ReadBytesExt; -pub(crate) fn parse_ilst(data: &mut R, len: u64) -> Result> +pub(crate) fn parse_ilst(reader: &mut R, len: u64) -> Result where - R: Read + Seek, + R: Read, { let mut contents = vec![0; len as usize]; - data.read_exact(&mut contents)?; + reader.read_exact(&mut contents)?; let mut cursor = Cursor::new(contents); @@ -71,7 +71,7 @@ where tag.atoms.push(Atom { ident, data }) } - Ok(Some(tag)) + Ok(tag) } fn parse_data(data: &mut R) -> Result diff --git a/src/logic/mp4/moov.rs b/src/logic/mp4/moov.rs index 4cce4619..f5fb4428 100644 --- a/src/logic/mp4/moov.rs +++ b/src/logic/mp4/moov.rs @@ -111,7 +111,7 @@ where #[cfg(feature = "mp4_atoms")] if islt.0 { - return parse_ilst(data, islt.1 - 8); + return parse_ilst(data, islt.1 - 8).map(Some); } Ok(None) diff --git a/src/logic/ogg/tag.rs b/src/logic/ogg/tag.rs index 2f78d398..0cf576cd 100644 --- a/src/logic/ogg/tag.rs +++ b/src/logic/ogg/tag.rs @@ -8,19 +8,67 @@ use crate::types::picture::PictureInformation; use crate::types::tag::{Tag, TagType}; use std::fs::File; +use std::io::Read; -#[derive(Default)] +#[derive(Default, PartialEq, Debug)] /// Vorbis comments pub struct VorbisComments { /// An identifier for the encoding software - pub vendor: String, + pub(crate) vendor: String, /// A collection of key-value pairs - pub items: Vec<(String, String)>, + pub(crate) items: Vec<(String, String)>, /// A collection of all pictures - pub pictures: Vec<(Picture, PictureInformation)>, + pub(crate) pictures: Vec<(Picture, PictureInformation)>, } impl VorbisComments { + pub fn vendor(&self) -> &str { + &self.vendor + } + + pub fn set_vendor(&mut self, vendor: String) { + self.vendor = vendor + } + + pub fn items(&self) -> &[(String, String)] { + &self.items + } + + pub fn get_item(&self, key: &str) -> Option<&str> { + self.items + .iter() + .find(|(k, _)| k == key) + .map(|(_, v)| v.as_str()) + } + + pub fn insert_item(&mut self, key: String, value: String, replace_all: bool) { + if replace_all { + self.items + .iter() + .position(|(k, _)| k == &key) + .map(|p| self.items.remove(p)); + } + + self.items.push((key, value)) + } + + pub fn remove_key(&mut self, key: &str) { + self.items.retain(|(k, _)| k != key); + } +} + +impl VorbisComments { + pub fn read_from(reader: &mut R) -> Result + where + R: Read, + { + let mut tag = Self::default(); + + super::read::read_comments(reader, &mut tag)?; + + Ok(tag) + } + pub fn write_to(&self, file: &mut File) -> Result<()> { Into::::into(self).write_to(file) } diff --git a/tests/tags/assets/test.apev2 b/tests/tags/assets/test.apev2 new file mode 100644 index 0000000000000000000000000000000000000000..266798c6e76c928d34681551ee2336e0ab522862 GIT binary patch literal 209 zcmcb>&cLvffq?-?fxrSV2_!h73`ZZQP+tb8#43fvoTSoRE|_poh-YvJP`pSXv8W`o zxP%)j;q340>*^Q65LjBFker{Jo0?YwGs@l7FUXa_IVZ8WI5Rmh2PPci8RFy0;Fh1S vP?A}algbD+C?v?y*_*+T1rDs5%A!ZxSUg literal 0 HcmV?d00001 diff --git a/tests/tags/assets/test.id3v1 b/tests/tags/assets/test.id3v1 new file mode 100644 index 0000000000000000000000000000000000000000..4ab4388ea76971f64d12d25683eb60f0a0799025 GIT binary patch literal 128 zcmWG>ba%_oS18FW$w_6v2AmR$6cUR{GK)*F%T_5Q<|LKoVv}Srw6rh@EUi#T&d<$F K&BI~gGr^F(K#G;bS;u4^Y;>yIF zBp@w`s-j8(C|sHglu%xooC{JYjjSNBv_c^{KQ}iu4%e!fCUW=T#egJ(dH3rNH%u}UE^C#f_SBoe{|(!l@#xKbA# literal 0 HcmV?d00001 diff --git a/tests/tags/assets/test.vorbis b/tests/tags/assets/test.vorbis new file mode 100644 index 0000000000000000000000000000000000000000..b08308315e2b26c7dcec7d36ce15de215bd363c4 GIT binary patch literal 152 zcmd;OU|{e`EK4)B&@(sFGc+(@2MX{5v7?VusIRS4VwFN-PEu*EAV?@E#4|VqC|0D9 zSX7c(Tp|n