ape/
meta.rs

1use crate::{
2    error::{Error, Result},
3    util::{probe_ape, probe_id3v1, probe_lyrics3v2, ID3V1_OFFSET},
4};
5use byteorder::{LittleEndian, ReadBytesExt};
6use std::io::{Read, Seek, SeekFrom};
7
8pub(super) const APE_VERSION: u32 = 2000;
9
10#[derive(Debug)]
11pub(super) struct Meta {
12    // Tag size in bytes including footer and all tag items excluding the header.
13    pub(super) size: u32,
14    // Position of the metadata.
15    pub(super) position: MetaPosition,
16    // Tag contains a header.
17    pub(super) has_header: bool,
18    // Number of items in the Tag.
19    pub(super) item_count: u32,
20    // Initial position of the Tag items.
21    pub(super) start_pos: u64,
22    // End position of the Tag items.
23    pub(super) end_pos: u64,
24}
25
26impl Meta {
27    pub(super) fn read<R: Read + Seek>(reader: &mut R) -> Result<Meta> {
28        const APE_HEADER_SIZE: i64 = 32;
29
30        let mut found = probe_ape(reader, SeekFrom::End(-APE_HEADER_SIZE))? || probe_ape(reader, SeekFrom::Start(0))?;
31        // When located at the end of an MP3 file, an APE tag should be placed after
32        // the last frame, just before the ID3v1 tag (if any).
33        if !found && probe_id3v1(reader)? {
34            found = probe_ape(reader, SeekFrom::End(ID3V1_OFFSET - APE_HEADER_SIZE))?;
35            if !found {
36                // ID3v1 tag maybe preceded by Lyrics3v2: http://id3.org/Lyrics3v2
37                let size = probe_lyrics3v2(reader)?;
38                if size != -1 {
39                    found = probe_ape(reader, SeekFrom::End(ID3V1_OFFSET - size - APE_HEADER_SIZE))?;
40                }
41            }
42        }
43        if !found {
44            return Err(Error::TagNotFound);
45        }
46        if reader.read_u32::<LittleEndian>()? != APE_VERSION {
47            return Err(Error::InvalidApeVersion);
48        }
49        let size = reader.read_u32::<LittleEndian>()?;
50        let item_count = reader.read_u32::<LittleEndian>()?;
51        let flags = MetaFlags::from_raw(reader.read_u32::<LittleEndian>()?);
52        // The following 8 bytes are reserved
53        const RESERVED_BYTES_NUM: i64 = 8;
54        let end_pos = reader.seek(SeekFrom::Current(RESERVED_BYTES_NUM))?;
55        Ok(Meta {
56            size,
57            position: flags.position,
58            has_header: flags.has_header,
59            item_count,
60            start_pos: match flags.position {
61                MetaPosition::Header => end_pos,
62                MetaPosition::Footer => end_pos - size as u64,
63            },
64            end_pos: match flags.position {
65                MetaPosition::Header => {
66                    let mut pos = end_pos + size as u64;
67                    if flags.has_footer {
68                        pos -= APE_HEADER_SIZE as u64;
69                    }
70                    pos
71                }
72                MetaPosition::Footer => end_pos - APE_HEADER_SIZE as u64,
73            },
74        })
75    }
76}
77
78#[derive(Clone, Copy, Debug, PartialEq)]
79pub(super) enum MetaPosition {
80    // It's header of the tag.
81    Header,
82    // It's footer of the tag.
83    Footer,
84}
85
86const HAS_HEADER: u32 = 1 << 31;
87const HAS_NO_FOOTER: u32 = 1 << 30;
88const IS_HEADER: u32 = 1 << 29;
89
90struct MetaFlags {
91    position: MetaPosition,
92    has_header: bool,
93    has_footer: bool,
94}
95
96impl MetaFlags {
97    fn from_raw(raw: u32) -> Self {
98        Self {
99            position: if raw & IS_HEADER != 0 {
100                MetaPosition::Header
101            } else {
102                MetaPosition::Footer
103            },
104            has_header: raw & HAS_HEADER != 0,
105            has_footer: raw & HAS_NO_FOOTER == 0,
106        }
107    }
108}
109
110#[cfg(test)]
111mod test {
112    use super::*;
113    use byteorder::{LittleEndian, WriteBytesExt};
114    use std::io::{Cursor, Write};
115
116    #[test]
117    fn found_at_end() {
118        let mut data = Cursor::new(Vec::<u8>::new());
119        let size = 40;
120        let item_count = 4;
121        let flags = 0;
122        data.write_all(&[0; 100]).unwrap();
123        data.write_all(b"APETAGEX").unwrap();
124        data.write_u32::<LittleEndian>(2000).unwrap();
125        data.write_u32::<LittleEndian>(size).unwrap();
126        data.write_u32::<LittleEndian>(item_count).unwrap();
127        data.write_u32::<LittleEndian>(flags).unwrap();
128        data.write_all(&[0; 8]).unwrap();
129        let meta = Meta::read(&mut data).unwrap();
130        assert_eq!(size, meta.size);
131        assert_eq!(item_count, meta.item_count);
132        assert_eq!(meta.position, MetaPosition::Footer);
133        assert!(!meta.has_header);
134        assert_eq!(92, meta.start_pos);
135        assert_eq!(100, meta.end_pos);
136    }
137
138    #[test]
139    fn found_at_start() {
140        let mut data = Cursor::new(Vec::<u8>::new());
141        let size = 50;
142        let item_count = 5;
143        let flags = HAS_HEADER | IS_HEADER | HAS_NO_FOOTER;
144        data.write_all(b"APETAGEX").unwrap();
145        data.write_u32::<LittleEndian>(2000).unwrap();
146        data.write_u32::<LittleEndian>(size).unwrap();
147        data.write_u32::<LittleEndian>(item_count).unwrap();
148        data.write_u32::<LittleEndian>(flags).unwrap();
149        data.write_all(&[0; 8]).unwrap();
150        data.write_all(&[0; 200]).unwrap();
151        let meta = Meta::read(&mut data).unwrap();
152        assert_eq!(size, meta.size);
153        assert_eq!(item_count, meta.item_count);
154        assert_eq!(meta.position, MetaPosition::Header);
155        assert!(meta.has_header);
156        assert_eq!(32, meta.start_pos);
157        assert_eq!(82, meta.end_pos);
158    }
159
160    #[test]
161    fn found_before_id3v1() {
162        let mut data = Cursor::new(Vec::<u8>::new());
163        let size = 62;
164        let item_count = 3;
165        let flags = 0;
166        data.write_all(&[0; 300]).unwrap();
167        data.write_all(b"APETAGEX").unwrap();
168        data.write_u32::<LittleEndian>(2000).unwrap();
169        data.write_u32::<LittleEndian>(size).unwrap();
170        data.write_u32::<LittleEndian>(item_count).unwrap();
171        data.write_u32::<LittleEndian>(flags).unwrap();
172        data.write_all(&[0; 8]).unwrap();
173        data.write_all(b"TAG").unwrap();
174        data.write_all(&[0; 125]).unwrap();
175        let meta = Meta::read(&mut data).unwrap();
176        assert_eq!(size, meta.size);
177        assert_eq!(item_count, meta.item_count);
178        assert_eq!(meta.position, MetaPosition::Footer);
179        assert!(!meta.has_header);
180        assert_eq!(270, meta.start_pos);
181        assert_eq!(300, meta.end_pos);
182    }
183
184    #[test]
185    fn found_before_lyrics3v2() {
186        let mut data = Cursor::new(Vec::<u8>::new());
187        let size = 70;
188        let item_count = 2;
189        let flags = 0;
190        data.write_all(&[0; 600]).unwrap();
191        data.write_all(b"APETAGEX").unwrap();
192        data.write_u32::<LittleEndian>(2000).unwrap();
193        data.write_u32::<LittleEndian>(size).unwrap();
194        data.write_u32::<LittleEndian>(item_count).unwrap();
195        data.write_u32::<LittleEndian>(flags).unwrap();
196        data.write_all(&[0; 8]).unwrap();
197        data.write_all(&[0; 120]).unwrap();
198        data.write_all(b"000120LYRICS200").unwrap();
199        data.write_all(b"TAG").unwrap();
200        data.write_all(&[0; 125]).unwrap();
201        let meta = Meta::read(&mut data).unwrap();
202        assert_eq!(size, meta.size);
203        assert_eq!(item_count, meta.item_count);
204        assert_eq!(meta.position, MetaPosition::Footer);
205        assert!(!meta.has_header);
206        assert_eq!(562, meta.start_pos);
207        assert_eq!(600, meta.end_pos);
208    }
209
210    #[test]
211    fn not_found() {
212        let mut data = Cursor::new((1..200).collect::<Vec<u8>>());
213        let err = Meta::read(&mut data).unwrap_err().to_string();
214        assert_eq!(err, "APE tag does not exist");
215    }
216
217    #[test]
218    fn invalid_ape_version() {
219        let mut data = Cursor::new(Vec::<u8>::new());
220        data.write_all(b"APETAGEX").unwrap();
221        data.write_u32::<LittleEndian>(1000).unwrap();
222        data.write_all(&[0; 20]).unwrap();
223        let err = Meta::read(&mut data).unwrap_err().to_string();
224        assert_eq!(err, "invalid APE version");
225    }
226}