id3/stream/
tag.rs

1use crate::chunk;
2use crate::storage::{plain::PlainStorage, Format, Storage, StorageFile};
3use crate::stream::{frame, unsynch};
4use crate::tag::{Tag, Version};
5use crate::taglike::TagLike;
6use crate::{Error, ErrorKind};
7use bitflags::bitflags;
8use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
9use std::cmp;
10use std::fs;
11use std::io::{self, Read, Seek, Write};
12use std::ops::Range;
13use std::path::Path;
14
15static DEFAULT_FILE_DISCARD: &[&str] = &[
16    "AENC", "ETCO", "EQUA", "MLLT", "POSS", "SYLT", "SYTC", "RVAD", "TENC", "TLEN", "TSIZ",
17];
18
19bitflags! {
20    struct Flags: u8 {
21        const UNSYNCHRONISATION = 0x80; // All versions
22        const COMPRESSION       = 0x40; // =ID3v2.2
23        const EXTENDED_HEADER   = 0x40; // >ID3v2.3, duplicate with TAG_COMPRESSION :(
24        const EXPERIMENTAL      = 0x20; // >ID3v2.3
25        const FOOTER            = 0x10; // >ID3v2.4
26    }
27
28    struct ExtFlags: u8 {
29        const TAG_IS_UPDATE    = 0x40;
30        const CRC_DATA_PRESENT = 0x20;
31        const TAG_RESTRICTIONS = 0x10;
32    }
33}
34
35/// Used for sharing code between sync/async parsers, which is mainly complicated by ext_headers.
36struct HeaderBuilder {
37    version: Version,
38    flags: Flags,
39    tag_size: u32,
40}
41
42impl HeaderBuilder {
43    fn with_ext_header(self, size: u32) -> Header {
44        Header {
45            version: self.version,
46            flags: self.flags,
47            tag_size: self.tag_size,
48            ext_header_size: size,
49        }
50    }
51}
52
53struct Header {
54    version: Version,
55    flags: Flags,
56    tag_size: u32,
57
58    // TODO: Extended header.
59    ext_header_size: u32,
60}
61
62impl Header {
63    fn size(&self) -> u64 {
64        10 // Raw header.
65    }
66
67    fn frame_bytes(&self) -> u64 {
68        u64::from(self.tag_size).saturating_sub(u64::from(self.ext_header_size))
69    }
70
71    fn tag_size(&self) -> u64 {
72        self.size() + self.frame_bytes()
73    }
74}
75
76impl Header {
77    fn decode(mut reader: impl io::Read) -> crate::Result<Header> {
78        let mut header = [0; 10];
79        let nread = reader.read(&mut header)?;
80        let base_header = Self::decode_base_header(&header[..nread])?;
81
82        // TODO: actually use the extended header data.
83        let ext_header_size = if base_header.flags.contains(Flags::EXTENDED_HEADER) {
84            let mut ext_header = [0; 6];
85            reader.read_exact(&mut ext_header)?;
86            let ext_size = unsynch::decode_u32(BigEndian::read_u32(&ext_header[0..4]));
87            // The extended header size includes itself and always has at least 2 bytes following.
88            if ext_size < 6 {
89                return Err(Error::new(
90                    ErrorKind::Parsing,
91                    "Extended header requires has a minimum size of 6",
92                ));
93            }
94
95            let _ext_flags = ExtFlags::from_bits_truncate(ext_header[5]);
96
97            let ext_remaining_size = ext_size - ext_header.len() as u32;
98            let mut ext_header = Vec::with_capacity(cmp::min(ext_remaining_size as usize, 0xffff));
99            reader
100                .by_ref()
101                .take(ext_remaining_size as u64)
102                .read_to_end(&mut ext_header)?;
103
104            ext_size
105        } else {
106            0
107        };
108
109        Ok(base_header.with_ext_header(ext_header_size))
110    }
111
112    #[cfg(feature = "tokio")]
113    async fn async_decode(
114        mut reader: impl tokio::io::AsyncRead + std::marker::Unpin,
115    ) -> crate::Result<Header> {
116        use tokio::io::AsyncReadExt;
117
118        let mut header = [0; 10];
119        let nread = reader.read(&mut header).await?;
120        let base_header = Self::decode_base_header(&header[..nread])?;
121
122        // TODO: actually use the extended header data.
123        let ext_header_size = if base_header.flags.contains(Flags::EXTENDED_HEADER) {
124            let mut ext_header = [0; 6];
125            reader.read_exact(&mut ext_header).await?;
126            let ext_size = unsynch::decode_u32(BigEndian::read_u32(&ext_header[0..4]));
127            // The extended header size includes itself and always has at least 2 bytes following.
128            if ext_size < 6 {
129                return Err(Error::new(
130                    ErrorKind::Parsing,
131                    "Extended header requires has a minimum size of 6",
132                ));
133            }
134
135            let _ext_flags = ExtFlags::from_bits_truncate(ext_header[5]);
136
137            let ext_remaining_size = ext_size - ext_header.len() as u32;
138            let mut ext_header = Vec::with_capacity(cmp::min(ext_remaining_size as usize, 0xffff));
139            reader
140                .take(ext_remaining_size as u64)
141                .read_to_end(&mut ext_header)
142                .await?;
143
144            ext_size
145        } else {
146            0
147        };
148
149        Ok(base_header.with_ext_header(ext_header_size))
150    }
151
152    fn decode_base_header(header: &[u8]) -> crate::Result<HeaderBuilder> {
153        if header.len() != 10 {
154            return Err(Error::new(
155                ErrorKind::NoTag,
156                "reader is not large enough to contain a id3 tag",
157            ));
158        }
159
160        if &header[0..3] != b"ID3" {
161            return Err(Error::new(
162                ErrorKind::NoTag,
163                "reader does not contain an id3 tag",
164            ));
165        }
166
167        let (ver_major, ver_minor) = (header[3], header[4]);
168        let version = match (ver_major, ver_minor) {
169            (2, _) => Version::Id3v22,
170            (3, _) => Version::Id3v23,
171            (4, _) => Version::Id3v24,
172            (_, _) => {
173                return Err(Error::new(
174                    ErrorKind::UnsupportedFeature,
175                    format!(
176                        "Unsupported id3 tag version: v2.{}.{}",
177                        ver_major, ver_minor
178                    ),
179                ));
180            }
181        };
182        let flags = Flags::from_bits(header[5])
183            .ok_or_else(|| Error::new(ErrorKind::Parsing, "unknown tag header flags are set"))?;
184        let tag_size = unsynch::decode_u32(BigEndian::read_u32(&header[6..10]));
185
186        // compression only exists on 2.2 and conflicts with 2.3+'s extended header
187        if version == Version::Id3v22 && flags.contains(Flags::COMPRESSION) {
188            return Err(Error::new(
189                ErrorKind::UnsupportedFeature,
190                "id3v2.2 compression is not supported",
191            ));
192        }
193
194        Ok(HeaderBuilder {
195            version,
196            flags,
197            tag_size,
198        })
199    }
200}
201
202pub fn decode(mut reader: impl io::Read) -> crate::Result<Tag> {
203    let header = Header::decode(&mut reader)?;
204
205    decode_remaining(reader, header)
206}
207
208#[cfg(feature = "tokio")]
209pub async fn async_decode(
210    mut reader: impl tokio::io::AsyncRead + std::marker::Unpin,
211) -> crate::Result<Tag> {
212    let header = Header::async_decode(&mut reader).await?;
213
214    let reader = {
215        use tokio::io::AsyncReadExt;
216
217        let mut buf = Vec::new();
218
219        reader
220            .take(header.frame_bytes())
221            .read_to_end(&mut buf)
222            .await?;
223        std::io::Cursor::new(buf)
224    };
225
226    decode_remaining(reader, header)
227}
228
229fn decode_remaining(mut reader: impl io::Read, header: Header) -> crate::Result<Tag> {
230    match header.version {
231        Version::Id3v22 => {
232            // Limit the reader only to the given tag_size, don't return any more bytes after that.
233            let v2_reader = reader.take(header.frame_bytes());
234
235            if header.flags.contains(Flags::UNSYNCHRONISATION) {
236                // Unwrap all 'unsynchronized' bytes in the tag before parsing frames.
237                decode_v2_frames(unsynch::Reader::new(v2_reader))
238            } else {
239                decode_v2_frames(v2_reader)
240            }
241        }
242        Version::Id3v23 => {
243            // Unsynchronization is applied to the whole tag, excluding the header.
244            let mut reader: Box<dyn io::Read> = if header.flags.contains(Flags::UNSYNCHRONISATION) {
245                Box::new(unsynch::Reader::new(reader))
246            } else {
247                Box::new(reader)
248            };
249
250            let mut offset = 0;
251            let mut tag = Tag::with_version(header.version);
252            while offset < header.frame_bytes() {
253                let v = match frame::v3::decode(&mut reader) {
254                    Ok(v) => v,
255                    Err(err) => return Err(err.with_tag(tag)),
256                };
257                let (bytes_read, frame) = match v {
258                    Some(v) => v,
259                    None => break, // Padding.
260                };
261                tag.add_frame(frame);
262                offset += bytes_read as u64;
263            }
264            Ok(tag)
265        }
266        Version::Id3v24 => {
267            let mut offset = 0;
268            let mut tag = Tag::with_version(header.version);
269
270            while offset < header.frame_bytes() {
271                let v = match frame::v4::decode(&mut reader) {
272                    Ok(v) => v,
273                    Err(err) => return Err(err.with_tag(tag)),
274                };
275                let (bytes_read, frame) = match v {
276                    Some(v) => v,
277                    None => break, // Padding.
278                };
279                tag.add_frame(frame);
280                offset += bytes_read as u64;
281            }
282            Ok(tag)
283        }
284    }
285}
286
287pub fn decode_v2_frames(mut reader: impl io::Read) -> crate::Result<Tag> {
288    let mut tag = Tag::with_version(Version::Id3v22);
289    // Add all frames, until either an error is thrown or there are no more frames to parse
290    // (because of EOF or a Padding).
291    loop {
292        let v = match frame::v2::decode(&mut reader) {
293            Ok(v) => v,
294            Err(err) => return Err(err.with_tag(tag)),
295        };
296        match v {
297            Some((_bytes_read, frame)) => {
298                tag.add_frame(frame);
299            }
300            None => break Ok(tag),
301        }
302    }
303}
304
305/// The `Encoder` may be used to encode tags with custom settings.
306#[derive(Clone, Debug)]
307pub struct Encoder {
308    version: Version,
309    unsynchronisation: bool,
310    compression: bool,
311    file_altered: bool,
312    padding: Option<usize>,
313}
314
315impl Encoder {
316    /// Constructs a new `Encoder` with the following configuration:
317    ///
318    /// * [`Version`] is ID3v2.4
319    /// * Unsynchronization is disabled due to compatibility issues
320    /// * No compression
321    /// * File is not marked as altered
322    pub fn new() -> Self {
323        Self {
324            version: Version::Id3v24,
325            unsynchronisation: false,
326            compression: false,
327            file_altered: false,
328            padding: None,
329        }
330    }
331
332    /// Sets the padding that is written after the tag.
333    ///
334    /// Should be only used when writing to a MP3 file
335    pub fn padding(mut self, padding: usize) -> Self {
336        self.padding = Some(padding);
337        self
338    }
339
340    /// Sets the ID3 version.
341    pub fn version(mut self, version: Version) -> Self {
342        self.version = version;
343        self
344    }
345
346    /// Enables or disables the unsynchronisation scheme.
347    ///
348    /// This avoids patterns that resemble MP3-frame headers from being
349    /// encoded. If you are encoding to MP3 files and wish to be compatible
350    /// with very old tools, you probably want this enabled.
351    pub fn unsynchronisation(mut self, unsynchronisation: bool) -> Self {
352        self.unsynchronisation = unsynchronisation;
353        self
354    }
355
356    /// Enables or disables compression.
357    pub fn compression(mut self, compression: bool) -> Self {
358        self.compression = compression;
359        self
360    }
361
362    /// Informs the encoder whether the file this tag belongs to has been changed.
363    ///
364    /// This subsequently discards any tags that have their File Alter Preservation bits set and
365    /// that have a relation to the file contents:
366    ///
367    ///   AENC, ETCO, EQUA, MLLT, POSS, SYLT, SYTC, RVAD, TENC, TLEN, TSIZ
368    pub fn file_altered(mut self, file_altered: bool) -> Self {
369        self.file_altered = file_altered;
370        self
371    }
372
373    /// Encodes the specified [`Tag`] using the settings set in the [`Encoder`].
374    ///
375    /// Note that the plain tag is written, regardless of the original contents. To safely encode a
376    /// tag to an MP3 file, use [`Encoder::encode_to_path`].
377    pub fn encode(&self, tag: &Tag, mut writer: impl io::Write) -> crate::Result<()> {
378        // remove frames which have the flags indicating they should be removed
379        let saved_frames = tag
380            .frames()
381            // Assert that by encoding, we are changing the tag. If the Tag Alter Preservation bit
382            // is set, discard the frame.
383            .filter(|frame| !frame.tag_alter_preservation())
384            // If the file this tag belongs to is updated, check for the File Alter Preservation
385            // bit.
386            .filter(|frame| !self.file_altered || !frame.file_alter_preservation())
387            // Check whether this frame is part of the set of frames that should always be
388            // discarded when the file is changed.
389            .filter(|frame| !self.file_altered || !DEFAULT_FILE_DISCARD.contains(&frame.id()));
390
391        let mut flags = Flags::empty();
392        flags.set(Flags::UNSYNCHRONISATION, self.unsynchronisation);
393        if self.version == Version::Id3v22 {
394            flags.set(Flags::COMPRESSION, self.compression);
395        }
396
397        let mut frame_data = Vec::new();
398        for frame in saved_frames {
399            frame.validate()?;
400            frame::encode(&mut frame_data, frame, self.version, self.unsynchronisation)?;
401        }
402        // In ID3v2.2/ID3v2.3, Unsynchronization is applied to the whole tag data at once, not for
403        // each frame separately.
404        if self.unsynchronisation {
405            match self.version {
406                Version::Id3v22 | Version::Id3v23 => unsynch::encode_vec(&mut frame_data),
407                Version::Id3v24 => {}
408            };
409        }
410        let tag_size = frame_data.len() + self.padding.unwrap_or(0);
411        writer.write_all(b"ID3")?;
412        writer.write_all(&[self.version.minor(), 0])?;
413        writer.write_u8(flags.bits())?;
414        writer.write_u32::<BigEndian>(unsynch::encode_u32(tag_size as u32))?;
415        writer.write_all(&frame_data[..])?;
416
417        if let Some(padding) = self.padding {
418            writer.write_all(&vec![0; padding])?;
419        }
420        Ok(())
421    }
422
423    /// Encodes a [`Tag`] and replaces any existing tag in the file.
424    pub fn write_to_file(&self, tag: &Tag, mut file: impl StorageFile) -> crate::Result<()> {
425        let mut probe = [0; 12];
426        let nread = file.read(&mut probe)?;
427        file.seek(io::SeekFrom::Start(0))?;
428        let storage_format = Format::magic(&probe[..nread]);
429
430        match storage_format {
431            Some(Format::Aiff) => {
432                chunk::write_id3_chunk_file::<chunk::AiffFormat>(file, tag, self.version)?;
433            }
434            Some(Format::Wav) => {
435                chunk::write_id3_chunk_file::<chunk::WavFormat>(file, tag, self.version)?;
436            }
437            Some(Format::Header) => {
438                let location = locate_id3v2(&mut file)?;
439                let mut storage = PlainStorage::new(file, location);
440                let mut w = storage.writer()?;
441                self.encode(tag, &mut w)?;
442                w.flush()?;
443            }
444            None => {
445                let mut storage = PlainStorage::new(file, 0..0);
446                let mut w = storage.writer()?;
447                self.encode(tag, &mut w)?;
448                w.flush()?;
449            }
450        };
451
452        Ok(())
453    }
454
455    /// Encodes a [`Tag`] and replaces any existing tag in the file.
456    #[deprecated(note = "Use write_to_file")]
457    pub fn encode_to_file(&self, tag: &Tag, file: &mut fs::File) -> crate::Result<()> {
458        self.write_to_file(tag, file)
459    }
460
461    /// Encodes a [`Tag`] and replaces any existing tag in the file pointed to by the specified path.
462    pub fn write_to_path(&self, tag: &Tag, path: impl AsRef<Path>) -> crate::Result<()> {
463        let mut file = fs::OpenOptions::new().read(true).write(true).open(path)?;
464        self.write_to_file(tag, &mut file)?;
465        file.flush()?;
466        Ok(())
467    }
468
469    /// Encodes a [`Tag`] and replaces any existing tag in the file pointed to by the specified path.
470    #[deprecated(note = "Use write_to_path")]
471    pub fn encode_to_path(&self, tag: &Tag, path: impl AsRef<Path>) -> crate::Result<()> {
472        self.write_to_path(tag, path)
473    }
474}
475
476impl Default for Encoder {
477    fn default() -> Self {
478        Self::new()
479    }
480}
481
482pub fn locate_id3v2(reader: impl io::Read + io::Seek) -> crate::Result<Range<u64>> {
483    let mut reader = io::BufReader::new(reader);
484    let start = reader.stream_position()?;
485
486    let file_size = reader.seek(io::SeekFrom::End(0))?;
487    reader.seek(io::SeekFrom::Start(start))?;
488
489    let header = Header::decode(&mut reader)?;
490    let tag_size = header.tag_size();
491
492    if start + tag_size >= file_size {
493        // Seen in the wild: tags that are encoded to be larger than the files actuall are.
494        reader.seek(io::SeekFrom::End(0))?;
495        return Ok(start..file_size);
496    }
497
498    reader.seek(io::SeekFrom::Start(tag_size))?;
499    let num_padding = reader
500        .bytes()
501        .take_while(|rs| rs.as_ref().map(|b| *b == 0x00).unwrap_or(false))
502        .count();
503    Ok(start..tag_size + num_padding as u64)
504}
505
506#[cfg(test)]
507mod tests {
508    use super::*;
509    use crate::frame::{
510        Chapter, Content, EncapsulatedObject, Frame, MpegLocationLookupTable,
511        MpegLocationLookupTableReference, Picture, PictureType, Popularimeter, Private,
512        SynchronisedLyrics, SynchronisedLyricsType, TableOfContents, TimestampFormat,
513        UniqueFileIdentifier, Unknown,
514    };
515    use std::fs::{self};
516    use std::io::{self, Read};
517
518    fn make_tag(version: Version) -> Tag {
519        let mut tag = Tag::new();
520        tag.set_title("Title");
521        tag.set_artist("Artist");
522        tag.set_genre("Genre");
523        tag.add_frame(Frame::with_content(
524            "TPE1",
525            Content::new_text_values(["artist 1", "artist 2", "artist 3"]),
526        ));
527        tag.set_duration(1337);
528        tag.add_frame(EncapsulatedObject {
529            mime_type: "Some Object".to_string(),
530            filename: "application/octet-stream".to_string(),
531            description: "".to_string(),
532            data: b"\xC0\xFF\xEE\x00".to_vec(),
533        });
534        let mut image_data = Vec::new();
535        fs::File::open("testdata/image.jpg")
536            .unwrap()
537            .read_to_end(&mut image_data)
538            .unwrap();
539        tag.add_frame(Picture {
540            mime_type: "image/jpeg".to_string(),
541            picture_type: PictureType::CoverFront,
542            description: "an image".to_string(),
543            data: image_data,
544        });
545        tag.add_frame(Popularimeter {
546            user: "user@example.com".to_string(),
547            rating: 255,
548            counter: 1337,
549        });
550        tag.add_frame(SynchronisedLyrics {
551            lang: "eng".to_string(),
552            timestamp_format: TimestampFormat::Ms,
553            content_type: SynchronisedLyricsType::Lyrics,
554            content: vec![
555                (1000, "he".to_string()),
556                (1100, "llo".to_string()),
557                (1200, "world".to_string()),
558            ],
559            description: String::from("description"),
560        });
561        if let Version::Id3v23 | Version::Id3v24 = version {
562            tag.add_frame(Chapter {
563                element_id: "01".to_string(),
564                start_time: 1000,
565                end_time: 2000,
566                start_offset: 0xff,
567                end_offset: 0xff,
568                frames: vec![
569                    Frame::with_content("TIT2", Content::Text("Foo".to_string())),
570                    Frame::with_content("TALB", Content::Text("Bar".to_string())),
571                    Frame::with_content("TCON", Content::Text("Baz".to_string())),
572                ],
573            });
574            tag.add_frame(TableOfContents {
575                element_id: "table01".to_string(),
576                top_level: true,
577                ordered: true,
578                elements: vec!["01".to_string()],
579                frames: Vec::new(),
580            });
581            tag.add_frame(MpegLocationLookupTable {
582                frames_between_reference: 1,
583                bytes_between_reference: 418,
584                millis_between_reference: 12,
585                bits_for_bytes: 4,
586                bits_for_millis: 4,
587                references: vec![
588                    MpegLocationLookupTableReference {
589                        deviate_bytes: 0xa,
590                        deviate_millis: 0xf,
591                    },
592                    MpegLocationLookupTableReference {
593                        deviate_bytes: 0xa,
594                        deviate_millis: 0x0,
595                    },
596                ],
597            });
598            tag.add_frame(Private {
599                owner_identifier: "PrivateFrameIdentifier1".to_string(),
600                private_data: "SomePrivateBytes".into(),
601            });
602            tag.add_frame(UniqueFileIdentifier {
603                owner_identifier: String::from("http://www.id3.org/dummy/ufid.html"),
604                identifier: "7FZo5fMqyG5Ys1dm8F1FHa".into(),
605            });
606            tag.add_frame(UniqueFileIdentifier {
607                owner_identifier: String::from("example.com"),
608                identifier: "3107f6e3-99c0-44c1-9785-655fc9c32d8b".into(),
609            });
610        }
611        tag
612    }
613
614    #[test]
615    fn read_id3v22() {
616        let mut file = fs::File::open("testdata/id3v22.id3").unwrap();
617        let tag: Tag = decode(&mut file).unwrap();
618        assert_eq!("Henry Frottey INTRO", tag.title().unwrap());
619        assert_eq!("Hörbuch & Gesprochene Inhalte", tag.genre().unwrap());
620        assert_eq!(1, tag.disc().unwrap());
621        assert_eq!(27, tag.total_discs().unwrap());
622        assert_eq!(2015, tag.year().unwrap());
623        if cfg!(feature = "decode_picture") {
624            assert_eq!(
625                PictureType::Other,
626                tag.pictures().next().unwrap().picture_type
627            );
628            assert_eq!("", tag.pictures().next().unwrap().description);
629            assert_eq!("image/jpeg", tag.pictures().next().unwrap().mime_type);
630        }
631    }
632
633    #[cfg(feature = "tokio")]
634    #[tokio::test]
635    async fn read_id3v22_tokio() {
636        let mut file = tokio::fs::File::open("testdata/id3v22.id3").await.unwrap();
637        let tag: Tag = async_decode(&mut file).await.unwrap();
638        assert_eq!("Henry Frottey INTRO", tag.title().unwrap());
639        assert_eq!("Hörbuch & Gesprochene Inhalte", tag.genre().unwrap());
640        assert_eq!(1, tag.disc().unwrap());
641        assert_eq!(27, tag.total_discs().unwrap());
642        assert_eq!(2015, tag.year().unwrap());
643        if cfg!(feature = "decode_picture") {
644            assert_eq!(
645                PictureType::Other,
646                tag.pictures().next().unwrap().picture_type
647            );
648            assert_eq!("", tag.pictures().next().unwrap().description);
649            assert_eq!("image/jpeg", tag.pictures().next().unwrap().mime_type);
650        }
651    }
652
653    #[test]
654    fn read_id3v23() {
655        let mut file = fs::File::open("testdata/id3v23.id3").unwrap();
656        let tag = decode(&mut file).unwrap();
657        assert_eq!("Title", tag.title().unwrap());
658        assert_eq!("Genre", tag.genre().unwrap());
659        assert_eq!(1, tag.disc().unwrap());
660        assert_eq!(1, tag.total_discs().unwrap());
661        if cfg!(feature = "decode_picture") {
662            assert_eq!(
663                PictureType::CoverFront,
664                tag.pictures().next().unwrap().picture_type
665            );
666        }
667    }
668
669    #[cfg(feature = "tokio")]
670    #[tokio::test]
671    async fn read_id3v23_tokio() {
672        let mut file = tokio::fs::File::open("testdata/id3v23.id3").await.unwrap();
673        let tag = async_decode(&mut file).await.unwrap();
674        assert_eq!("Title", tag.title().unwrap());
675        assert_eq!("Genre", tag.genre().unwrap());
676        assert_eq!(1, tag.disc().unwrap());
677        assert_eq!(1, tag.total_discs().unwrap());
678        if cfg!(feature = "decode_picture") {
679            assert_eq!(
680                PictureType::CoverFront,
681                tag.pictures().next().unwrap().picture_type
682            );
683        }
684    }
685
686    #[test]
687    fn read_id3v23_geob() {
688        let mut file = fs::File::open("testdata/id3v23_geob.id3").unwrap();
689        let tag = decode(&mut file).unwrap();
690        assert_eq!(tag.encapsulated_objects().count(), 7);
691
692        let geob = tag.encapsulated_objects().next().unwrap();
693        assert_eq!(geob.description, "Serato Overview");
694        assert_eq!(geob.mime_type, "application/octet-stream");
695        assert_eq!(geob.filename, "");
696        assert_eq!(geob.data.len(), 3842);
697
698        let geob = tag.encapsulated_objects().nth(1).unwrap();
699        assert_eq!(geob.description, "Serato Analysis");
700        assert_eq!(geob.mime_type, "application/octet-stream");
701        assert_eq!(geob.filename, "");
702        assert_eq!(geob.data.len(), 2);
703
704        let geob = tag.encapsulated_objects().nth(2).unwrap();
705        assert_eq!(geob.description, "Serato Autotags");
706        assert_eq!(geob.mime_type, "application/octet-stream");
707        assert_eq!(geob.filename, "");
708        assert_eq!(geob.data.len(), 21);
709
710        let geob = tag.encapsulated_objects().nth(3).unwrap();
711        assert_eq!(geob.description, "Serato Markers_");
712        assert_eq!(geob.mime_type, "application/octet-stream");
713        assert_eq!(geob.filename, "");
714        assert_eq!(geob.data.len(), 318);
715
716        let geob = tag.encapsulated_objects().nth(4).unwrap();
717        assert_eq!(geob.description, "Serato Markers2");
718        assert_eq!(geob.mime_type, "application/octet-stream");
719        assert_eq!(geob.filename, "");
720        assert_eq!(geob.data.len(), 470);
721
722        let geob = tag.encapsulated_objects().nth(5).unwrap();
723        assert_eq!(geob.description, "Serato BeatGrid");
724        assert_eq!(geob.mime_type, "application/octet-stream");
725        assert_eq!(geob.filename, "");
726        assert_eq!(geob.data.len(), 39);
727
728        let geob = tag.encapsulated_objects().nth(6).unwrap();
729        assert_eq!(geob.description, "Serato Offsets_");
730        assert_eq!(geob.mime_type, "application/octet-stream");
731        assert_eq!(geob.filename, "");
732        assert_eq!(geob.data.len(), 29829);
733    }
734
735    #[test]
736    fn read_id3v23_chap() {
737        let mut file = fs::File::open("testdata/id3v23_chap.id3").unwrap();
738        let tag = decode(&mut file).unwrap();
739        assert_eq!(tag.chapters().count(), 7);
740
741        let chapter_titles = tag
742            .chapters()
743            .map(|chap| chap.frames.first().unwrap().content().text().unwrap())
744            .collect::<Vec<&str>>();
745        assert_eq!(
746            chapter_titles,
747            &[
748                "MPU 554",
749                "Read-it-Later Services?",
750                "Safari Reading List",
751                "Third-Party Services",
752                "What We’re Using",
753                "David’s Research Workflow",
754                "Apple’s September"
755            ]
756        );
757    }
758
759    #[test]
760    fn read_id3v23_ctoc() {
761        let mut file = fs::File::open("testdata/id3v23_chap.id3").unwrap();
762        let tag = decode(&mut file).unwrap();
763        assert_eq!(tag.tables_of_contents().count(), 1);
764
765        for x in tag.tables_of_contents() {
766            println!("{:?}", x);
767        }
768
769        let ctoc = tag.tables_of_contents().last().unwrap();
770
771        assert_eq!(ctoc.element_id, "toc");
772        assert!(ctoc.top_level);
773        assert!(ctoc.ordered);
774        assert_eq!(
775            ctoc.elements,
776            &["chp0", "chp1", "chp2", "chp3", "chp4", "chp5", "chp6"]
777        );
778        assert!(ctoc.frames.is_empty());
779    }
780
781    #[test]
782    fn read_id3v24() {
783        let mut file = fs::File::open("testdata/id3v24.id3").unwrap();
784        let tag = decode(&mut file).unwrap();
785        assert_eq!("Title", tag.title().unwrap());
786        assert_eq!(1, tag.disc().unwrap());
787        assert_eq!(1, tag.total_discs().unwrap());
788        if cfg!(feature = "decode_picture") {
789            assert_eq!(
790                PictureType::CoverFront,
791                tag.pictures().next().unwrap().picture_type
792            );
793        }
794    }
795
796    #[test]
797    fn read_id3v24_extended() {
798        let mut file = fs::File::open("testdata/id3v24_ext.id3").unwrap();
799        let tag = decode(&mut file).unwrap();
800        assert_eq!("Title", tag.title().unwrap());
801        assert_eq!("Genre", tag.genre().unwrap());
802        assert_eq!("Artist", tag.artist().unwrap());
803        assert_eq!("Album", tag.album().unwrap());
804        assert_eq!(2, tag.track().unwrap());
805    }
806
807    #[cfg(feature = "tokio")]
808    #[tokio::test]
809    async fn read_id3v24_extended_tokio() {
810        let mut file = tokio::fs::File::open("testdata/id3v24_ext.id3")
811            .await
812            .unwrap();
813        let tag = async_decode(&mut file).await.unwrap();
814        assert_eq!("Title", tag.title().unwrap());
815        assert_eq!("Genre", tag.genre().unwrap());
816        assert_eq!("Artist", tag.artist().unwrap());
817        assert_eq!("Album", tag.album().unwrap());
818        assert_eq!(2, tag.track().unwrap());
819    }
820
821    #[test]
822    fn write_id3v22() {
823        if !cfg!(feature = "decode_picture") {
824            return;
825        }
826
827        let tag = make_tag(Version::Id3v22);
828        let mut buffer = Vec::new();
829        Encoder::new()
830            .version(Version::Id3v22)
831            .encode(&tag, &mut buffer)
832            .unwrap();
833        let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
834        assert_eq!(tag, tag_read);
835    }
836
837    #[test]
838    fn write_id3v22_unsynch() {
839        if !cfg!(feature = "decode_picture") {
840            return;
841        }
842
843        let tag = make_tag(Version::Id3v22);
844        let mut buffer = Vec::new();
845        Encoder::new()
846            .unsynchronisation(true)
847            .version(Version::Id3v22)
848            .encode(&tag, &mut buffer)
849            .unwrap();
850        let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
851        assert_eq!(tag, tag_read);
852    }
853
854    #[test]
855    fn write_id3v22_invalid_id() {
856        if !cfg!(feature = "decode_picture") {
857            return;
858        }
859
860        let mut tag = make_tag(Version::Id3v22);
861        tag.add_frame(Frame::with_content(
862            "XXX",
863            Content::Unknown(Unknown {
864                version: Version::Id3v22,
865                data: vec![1, 2, 3],
866            }),
867        ));
868        tag.add_frame(Frame::with_content(
869            "YYY",
870            Content::Unknown(Unknown {
871                version: Version::Id3v22,
872                data: vec![4, 5, 6],
873            }),
874        ));
875        tag.add_frame(Frame::with_content(
876            "ZZZ",
877            Content::Unknown(Unknown {
878                version: Version::Id3v22,
879                data: vec![7, 8, 9],
880            }),
881        ));
882        let mut buffer = Vec::new();
883        Encoder::new()
884            .version(Version::Id3v22)
885            .encode(&tag, &mut buffer)
886            .unwrap();
887        let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
888        assert_eq!(tag, tag_read);
889    }
890
891    #[test]
892    fn write_id3v23() {
893        if !cfg!(feature = "decode_picture") {
894            return;
895        }
896
897        let tag = make_tag(Version::Id3v23);
898        let mut buffer = Vec::new();
899        Encoder::new()
900            .version(Version::Id3v23)
901            .encode(&tag, &mut buffer)
902            .unwrap();
903        let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
904        assert_eq!(tag, tag_read);
905    }
906
907    #[test]
908    fn write_id3v23_compression() {
909        if !cfg!(feature = "decode_picture") {
910            return;
911        }
912
913        let tag = make_tag(Version::Id3v23);
914        let mut buffer = Vec::new();
915        Encoder::new()
916            .compression(true)
917            .version(Version::Id3v23)
918            .encode(&tag, &mut buffer)
919            .unwrap();
920        let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
921        assert_eq!(tag, tag_read);
922    }
923
924    #[test]
925    fn write_id3v23_unsynch() {
926        if !cfg!(feature = "decode_picture") {
927            return;
928        }
929
930        let tag = make_tag(Version::Id3v23);
931        let mut buffer = Vec::new();
932        Encoder::new()
933            .unsynchronisation(true)
934            .version(Version::Id3v23)
935            .encode(&tag, &mut buffer)
936            .unwrap();
937        let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
938        assert_eq!(tag, tag_read);
939    }
940
941    #[test]
942    fn write_id3v24() {
943        if !cfg!(feature = "decode_picture") {
944            return;
945        }
946
947        let tag = make_tag(Version::Id3v24);
948        let mut buffer = Vec::new();
949        Encoder::new()
950            .version(Version::Id3v24)
951            .encode(&tag, &mut buffer)
952            .unwrap();
953        let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
954        assert_eq!(tag, tag_read);
955    }
956
957    #[test]
958    fn write_id3v24_compression() {
959        if !cfg!(feature = "decode_picture") {
960            return;
961        }
962
963        let tag = make_tag(Version::Id3v24);
964        let mut buffer = Vec::new();
965        Encoder::new()
966            .compression(true)
967            .version(Version::Id3v24)
968            .encode(&tag, &mut buffer)
969            .unwrap();
970        let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
971        assert_eq!(tag, tag_read);
972    }
973
974    #[test]
975    fn write_id3v24_unsynch() {
976        if !cfg!(feature = "decode_picture") {
977            return;
978        }
979
980        let tag = make_tag(Version::Id3v24);
981        let mut buffer = Vec::new();
982        Encoder::new()
983            .unsynchronisation(true)
984            .version(Version::Id3v24)
985            .encode(&tag, &mut buffer)
986            .unwrap();
987        let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
988        assert_eq!(tag, tag_read);
989    }
990
991    #[test]
992    fn write_id3v24_alter_file() {
993        if !cfg!(feature = "decode_picture") {
994            return;
995        }
996
997        let mut tag = Tag::new();
998        tag.set_duration(1337);
999
1000        let mut buffer = Vec::new();
1001        Encoder::new()
1002            .version(Version::Id3v24)
1003            .file_altered(true)
1004            .encode(&tag, &mut buffer)
1005            .unwrap();
1006
1007        let tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
1008        assert!(tag_read.get("TLEN").is_none());
1009    }
1010
1011    #[test]
1012    fn test_locate_id3v22() {
1013        let file = fs::File::open("testdata/id3v22.id3").unwrap();
1014        let location = locate_id3v2(file).unwrap();
1015        assert_eq!(0..0x0000c3ea, location);
1016    }
1017
1018    #[test]
1019    fn test_locate_id3v23() {
1020        let file = fs::File::open("testdata/id3v23.id3").unwrap();
1021        let location = locate_id3v2(file).unwrap();
1022        assert_eq!(0..0x00006c0a, location);
1023    }
1024
1025    #[test]
1026    fn test_locate_id3v24() {
1027        let file = fs::File::open("testdata/id3v24.id3").unwrap();
1028        let location = locate_id3v2(file).unwrap();
1029        assert_eq!(0..0x00006c0a, location);
1030    }
1031
1032    #[test]
1033    fn test_locate_id3v24_ext() {
1034        let file = fs::File::open("testdata/id3v24_ext.id3").unwrap();
1035        let location = locate_id3v2(file).unwrap();
1036        assert_eq!(0..0x0000018d, location);
1037    }
1038
1039    #[test]
1040    fn test_locate_no_tag() {
1041        let file = fs::File::open("testdata/mpeg-header").unwrap();
1042        let location = locate_id3v2(file).unwrap_err();
1043        assert!(matches!(
1044            location,
1045            Error {
1046                kind: ErrorKind::NoTag,
1047                ..
1048            }
1049        ));
1050    }
1051
1052    #[test]
1053    fn read_github_issue_60() {
1054        let mut file = fs::File::open("testdata/github-issue-60.id3").unwrap();
1055        let _tag = decode(&mut file).unwrap();
1056    }
1057
1058    #[test]
1059    fn read_github_issue_73() {
1060        let mut file = fs::File::open("testdata/github-issue-73.id3").unwrap();
1061        let mut tag = decode(&mut file).unwrap();
1062        assert_eq!(tag.track(), Some(9));
1063
1064        tag.set_total_tracks(16);
1065        assert_eq!(tag.track(), Some(9));
1066        assert_eq!(tag.total_tracks(), Some(16));
1067    }
1068
1069    #[test]
1070    fn write_id3v24_ufids() {
1071        let mut tag = make_tag(Version::Id3v24);
1072        tag.add_frame(UniqueFileIdentifier {
1073            owner_identifier: String::from("http://www.id3.org/dummy/ufid.html"),
1074            identifier: "7FZo5fMqyG5Ys1dm8F1FHa".into(),
1075        });
1076        assert_eq!(tag.unique_file_identifiers().count(), 2);
1077
1078        tag.add_frame(UniqueFileIdentifier {
1079            owner_identifier: String::from("http://www.id3.org/dummy/ufid.html"),
1080            identifier: "09FxXfNTQsCgzkPmCeFwlr".into(),
1081        });
1082        assert_eq!(tag.unique_file_identifiers().count(), 2);
1083
1084        tag.add_frame(UniqueFileIdentifier {
1085            owner_identifier: String::from("open.blotchify.com"),
1086            identifier: "09FxXfNTQsCgzkPmCeFwlr".into(),
1087        });
1088
1089        assert_eq!(tag.unique_file_identifiers().count(), 3);
1090
1091        let mut buffer = Vec::new();
1092        Encoder::new()
1093            .compression(true)
1094            .version(Version::Id3v24)
1095            .encode(&tag, &mut buffer)
1096            .unwrap();
1097        let mut tag_read = decode(&mut io::Cursor::new(buffer)).unwrap();
1098
1099        if !cfg!(feature = "decode_picture") {
1100            tag_read.remove_all_pictures();
1101            tag.remove_all_pictures();
1102        }
1103
1104        assert_eq!(tag, tag_read);
1105    }
1106
1107    #[test]
1108    fn test_frame_bytes_underflow() {
1109        let header = Header {
1110            version: Version::Id3v24,
1111            flags: Flags::empty(),
1112            tag_size: 10,
1113            ext_header_size: 20,
1114        };
1115
1116        // Without saturating_sub, this would underflow and cause a panic.
1117        assert_eq!(header.frame_bytes(), 0);
1118    }
1119}