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; const COMPRESSION = 0x40; const EXTENDED_HEADER = 0x40; const EXPERIMENTAL = 0x20; const FOOTER = 0x10; }
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
35struct 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 ext_header_size: u32,
60}
61
62impl Header {
63 fn size(&self) -> u64 {
64 10 }
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 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 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 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 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 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 let v2_reader = reader.take(header.frame_bytes());
234
235 if header.flags.contains(Flags::UNSYNCHRONISATION) {
236 decode_v2_frames(unsynch::Reader::new(v2_reader))
238 } else {
239 decode_v2_frames(v2_reader)
240 }
241 }
242 Version::Id3v23 => {
243 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, };
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, };
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 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#[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 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 pub fn padding(mut self, padding: usize) -> Self {
336 self.padding = Some(padding);
337 self
338 }
339
340 pub fn version(mut self, version: Version) -> Self {
342 self.version = version;
343 self
344 }
345
346 pub fn unsynchronisation(mut self, unsynchronisation: bool) -> Self {
352 self.unsynchronisation = unsynchronisation;
353 self
354 }
355
356 pub fn compression(mut self, compression: bool) -> Self {
358 self.compression = compression;
359 self
360 }
361
362 pub fn file_altered(mut self, file_altered: bool) -> Self {
369 self.file_altered = file_altered;
370 self
371 }
372
373 pub fn encode(&self, tag: &Tag, mut writer: impl io::Write) -> crate::Result<()> {
378 let saved_frames = tag
380 .frames()
381 .filter(|frame| !frame.tag_alter_preservation())
384 .filter(|frame| !self.file_altered || !frame.file_alter_preservation())
387 .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 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 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 #[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 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 #[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 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 assert_eq!(header.frame_bytes(), 0);
1118 }
1119}