1pub(crate) mod item;
2pub(crate) mod read;
3mod write;
4
5use crate::ape::tag::item::{ApeItem, ApeItemRef};
6use crate::config::WriteOptions;
7use crate::error::{LoftyError, Result};
8use crate::id3::v2::util::pairs::{NUMBER_PAIR_KEYS, format_number_pair, set_number};
9use crate::tag::item::ItemValueRef;
10use crate::tag::{
11 Accessor, ItemKey, ItemValue, MergeTag, SplitTag, Tag, TagExt, TagItem, TagType, try_parse_year,
12};
13use crate::util::flag_item;
14use crate::util::io::{FileLike, Truncate};
15
16use std::borrow::Cow;
17use std::io::Write;
18use std::ops::Deref;
19
20use lofty_attr::tag;
21
22macro_rules! impl_accessor {
23 ($($name:ident => $($key:literal)|+;)+) => {
24 paste::paste! {
25 $(
26 fn $name(&self) -> Option<Cow<'_, str>> {
27 $(
28 if let Some(i) = self.get($key) {
29 if let ItemValue::Text(val) = i.value() {
30 return Some(Cow::Borrowed(val));
31 }
32 }
33 )+
34
35 None
36 }
37
38 fn [<set_ $name>](&mut self, value: String) {
39 self.insert(ApeItem {
40 read_only: false,
41 key: String::from(crate::tag::item::first_key!($($key)|*)),
42 value: ItemValue::Text(value)
43 })
44 }
45
46 fn [<remove_ $name>](&mut self) {
47 $(
48 self.remove($key);
49 )+
50 }
51 )+
52 }
53 }
54}
55
56#[derive(Default, Debug, PartialEq, Eq, Clone)]
76#[tag(
77 description = "An `APE` tag",
78 supported_formats(Ape, Mpeg, Mpc, WavPack)
79)]
80pub struct ApeTag {
81 pub read_only: bool,
83 pub(super) items: Vec<ApeItem>,
84}
85
86impl ApeTag {
87 pub fn new() -> Self {
99 Self::default()
100 }
101
102 pub fn get(&self, key: &str) -> Option<&ApeItem> {
121 self.items
122 .iter()
123 .find(|i| i.key().eq_ignore_ascii_case(key))
124 }
125
126 pub fn insert(&mut self, value: ApeItem) {
130 self.remove(value.key());
131 self.items.push(value);
132 }
133
134 pub fn remove(&mut self, key: &str) {
158 self.items.retain(|i| !i.key().eq_ignore_ascii_case(key));
159 }
160
161 fn insert_item(&mut self, item: TagItem) {
162 match item.key() {
163 ItemKey::TrackNumber => set_number(&item, |number| self.set_track(number)),
164 ItemKey::TrackTotal => set_number(&item, |number| self.set_track_total(number)),
165 ItemKey::DiscNumber => set_number(&item, |number| self.set_disk(number)),
166 ItemKey::DiscTotal => set_number(&item, |number| self.set_disk_total(number)),
167
168 ItemKey::FlagCompilation => {
170 let Some(text) = item.item_value.text() else {
171 return;
172 };
173
174 let Some(flag) = flag_item(text) else {
175 return;
176 };
177
178 let value = u8::from(flag).to_string();
179 self.insert(ApeItem::text("Compilation", value));
180 },
181 _ => {
182 if let Ok(item) = item.try_into() {
183 self.insert(item);
184 }
185 },
186 }
187 }
188
189 fn split_num_pair(&self, key: &str) -> (Option<u32>, Option<u32>) {
190 if let Some(ApeItem {
191 value: ItemValue::Text(text),
192 ..
193 }) = self.get(key)
194 {
195 let mut split = text.split('/').flat_map(str::parse::<u32>);
196 return (split.next(), split.next());
197 }
198
199 (None, None)
200 }
201
202 fn insert_number_pair(&mut self, key: &'static str, number: Option<u32>, total: Option<u32>) {
203 if let Some(value) = format_number_pair(number, total) {
204 self.insert(ApeItem::text(key, value));
205 } else {
206 log::warn!("{key} is not set. number: {number:?}, total: {total:?}");
207 }
208 }
209}
210
211impl IntoIterator for ApeTag {
212 type Item = ApeItem;
213 type IntoIter = std::vec::IntoIter<Self::Item>;
214
215 fn into_iter(self) -> Self::IntoIter {
216 self.items.into_iter()
217 }
218}
219
220impl<'a> IntoIterator for &'a ApeTag {
221 type Item = &'a ApeItem;
222 type IntoIter = std::slice::Iter<'a, ApeItem>;
223
224 fn into_iter(self) -> Self::IntoIter {
225 self.items.iter()
226 }
227}
228
229impl Accessor for ApeTag {
230 impl_accessor!(
231 artist => "Artist";
232 title => "Title";
233 album => "Album";
234 genre => "GENRE";
235 comment => "Comment";
236 );
237
238 fn track(&self) -> Option<u32> {
239 self.split_num_pair("Track").0
240 }
241
242 fn set_track(&mut self, value: u32) {
243 self.insert_number_pair("Track", Some(value), self.track_total());
244 }
245
246 fn remove_track(&mut self) {
247 self.remove("Track");
248 }
249
250 fn track_total(&self) -> Option<u32> {
251 self.split_num_pair("Track").1
252 }
253
254 fn set_track_total(&mut self, value: u32) {
255 self.insert_number_pair("Track", self.track(), Some(value));
256 }
257
258 fn remove_track_total(&mut self) {
259 let existing_track_number = self.track();
260 self.remove("Track");
261
262 if let Some(track) = existing_track_number {
263 self.insert(ApeItem::text("Track", track.to_string()));
264 }
265 }
266
267 fn disk(&self) -> Option<u32> {
268 self.split_num_pair("Disc").0
269 }
270
271 fn set_disk(&mut self, value: u32) {
272 self.insert_number_pair("Disc", Some(value), self.disk_total());
273 }
274
275 fn remove_disk(&mut self) {
276 self.remove("Disc");
277 }
278
279 fn disk_total(&self) -> Option<u32> {
280 self.split_num_pair("Disc").1
281 }
282
283 fn set_disk_total(&mut self, value: u32) {
284 self.insert_number_pair("Disc", self.disk(), Some(value));
285 }
286
287 fn remove_disk_total(&mut self) {
288 let existing_track_number = self.track();
289 self.remove("Disc");
290
291 if let Some(track) = existing_track_number {
292 self.insert(ApeItem::text("Disc", track.to_string()));
293 }
294 }
295
296 fn year(&self) -> Option<u32> {
297 if let Some(ApeItem {
298 value: ItemValue::Text(text),
299 ..
300 }) = self.get("Year")
301 {
302 return try_parse_year(text);
303 }
304
305 None
306 }
307
308 fn set_year(&mut self, value: u32) {
309 self.insert(ApeItem::text("Year", value.to_string()));
310 }
311
312 fn remove_year(&mut self) {
313 self.remove("Year");
314 }
315}
316
317impl TagExt for ApeTag {
318 type Err = LoftyError;
319 type RefKey<'a> = &'a str;
320
321 #[inline]
322 fn tag_type(&self) -> TagType {
323 TagType::Ape
324 }
325
326 fn len(&self) -> usize {
327 self.items.len()
328 }
329
330 fn contains<'a>(&'a self, key: Self::RefKey<'a>) -> bool {
331 self.items.iter().any(|i| i.key().eq_ignore_ascii_case(key))
332 }
333
334 fn is_empty(&self) -> bool {
335 self.items.is_empty()
336 }
337
338 fn save_to<F>(
345 &self,
346 file: &mut F,
347 write_options: WriteOptions,
348 ) -> std::result::Result<(), Self::Err>
349 where
350 F: FileLike,
351 LoftyError: From<<F as Truncate>::Error>,
352 {
353 ApeTagRef {
354 read_only: self.read_only,
355 items: self.items.iter().map(Into::into),
356 }
357 .write_to(file, write_options)
358 }
359
360 fn dump_to<W: Write>(
366 &self,
367 writer: &mut W,
368 write_options: WriteOptions,
369 ) -> std::result::Result<(), Self::Err> {
370 ApeTagRef {
371 read_only: self.read_only,
372 items: self.items.iter().map(Into::into),
373 }
374 .dump_to(writer, write_options)
375 }
376
377 fn clear(&mut self) {
378 self.items.clear();
379 }
380}
381
382#[derive(Debug, Clone, Default)]
383pub struct SplitTagRemainder(ApeTag);
384
385impl From<SplitTagRemainder> for ApeTag {
386 fn from(from: SplitTagRemainder) -> Self {
387 from.0
388 }
389}
390
391impl Deref for SplitTagRemainder {
392 type Target = ApeTag;
393
394 fn deref(&self) -> &Self::Target {
395 &self.0
396 }
397}
398
399impl SplitTag for ApeTag {
400 type Remainder = SplitTagRemainder;
401
402 fn split_tag(mut self) -> (Self::Remainder, Tag) {
403 fn split_pair(
404 content: &str,
405 tag: &mut Tag,
406 current_key: ItemKey,
407 total_key: ItemKey,
408 ) -> Option<()> {
409 let mut split = content.splitn(2, '/');
410 let current = split.next()?.to_string();
411 tag.items
412 .push(TagItem::new(current_key, ItemValue::Text(current)));
413
414 if let Some(total) = split.next() {
415 tag.items
416 .push(TagItem::new(total_key, ItemValue::Text(total.to_string())))
417 }
418
419 Some(())
420 }
421
422 let mut tag = Tag::new(TagType::Ape);
423
424 for item in std::mem::take(&mut self.items) {
425 let item_key = ItemKey::from_key(TagType::Ape, item.key());
426
427 match (item_key, item.value()) {
429 (ItemKey::TrackNumber | ItemKey::TrackTotal, ItemValue::Text(val))
430 if split_pair(val, &mut tag, ItemKey::TrackNumber, ItemKey::TrackTotal)
431 .is_some() =>
432 {
433 continue; },
435 (ItemKey::DiscNumber | ItemKey::DiscTotal, ItemValue::Text(val))
436 if split_pair(val, &mut tag, ItemKey::DiscNumber, ItemKey::DiscTotal)
437 .is_some() =>
438 {
439 continue; },
441 (ItemKey::MovementNumber | ItemKey::MovementTotal, ItemValue::Text(val))
442 if split_pair(
443 val,
444 &mut tag,
445 ItemKey::MovementNumber,
446 ItemKey::MovementTotal,
447 )
448 .is_some() =>
449 {
450 continue; },
452 (k, _) => {
453 tag.items.push(TagItem::new(k, item.value));
454 },
455 }
456 }
457
458 (SplitTagRemainder(self), tag)
459 }
460}
461
462impl MergeTag for SplitTagRemainder {
463 type Merged = ApeTag;
464
465 fn merge_tag(self, tag: Tag) -> Self::Merged {
466 let Self(mut merged) = self;
467
468 for item in tag.items {
469 merged.insert_item(item);
470 }
471
472 for pic in tag.pictures {
473 if let Some(key) = pic.pic_type.as_ape_key() {
474 if let Ok(item) =
475 ApeItem::new(key.to_string(), ItemValue::Binary(pic.as_ape_bytes()))
476 {
477 merged.insert(item)
478 }
479 }
480 }
481
482 merged
483 }
484}
485
486impl From<ApeTag> for Tag {
487 fn from(input: ApeTag) -> Self {
488 input.split_tag().1
489 }
490}
491
492impl From<Tag> for ApeTag {
493 fn from(input: Tag) -> Self {
494 SplitTagRemainder::default().merge_tag(input)
495 }
496}
497
498pub(crate) struct ApeTagRef<'a, I>
499where
500 I: Iterator<Item = ApeItemRef<'a>>,
501{
502 pub(crate) read_only: bool,
503 pub(crate) items: I,
504}
505
506impl<'a, I> ApeTagRef<'a, I>
507where
508 I: Iterator<Item = ApeItemRef<'a>>,
509{
510 pub(crate) fn write_to<F>(&mut self, file: &mut F, write_options: WriteOptions) -> Result<()>
511 where
512 F: FileLike,
513 LoftyError: From<<F as Truncate>::Error>,
514 {
515 write::write_to(file, self, write_options)
516 }
517
518 pub(crate) fn dump_to<W: Write>(
519 &mut self,
520 writer: &mut W,
521 write_options: WriteOptions,
522 ) -> Result<()> {
523 let temp = write::create_ape_tag(self, std::iter::empty(), write_options)?;
524 writer.write_all(&temp)?;
525
526 Ok(())
527 }
528}
529
530pub(crate) fn tagitems_into_ape(tag: &Tag) -> impl Iterator<Item = ApeItemRef<'_>> {
531 fn create_apeitemref_for_number_pair<'a>(
532 number: Option<&str>,
533 total: Option<&str>,
534 key: &'a str,
535 ) -> Option<ApeItemRef<'a>> {
536 format_number_pair(number, total).map(|value| ApeItemRef {
537 read_only: false,
538 key,
539 value: ItemValueRef::Text(Cow::Owned(value)),
540 })
541 }
542
543 tag.items()
544 .filter(|item| !NUMBER_PAIR_KEYS.contains(item.key()))
545 .filter_map(|i| {
546 i.key().map_key(TagType::Ape, true).map(|key| ApeItemRef {
547 read_only: false,
548 key,
549 value: (&i.item_value).into(),
550 })
551 })
552 .chain(create_apeitemref_for_number_pair(
553 tag.get_string(&ItemKey::TrackNumber),
554 tag.get_string(&ItemKey::TrackTotal),
555 "Track",
556 ))
557 .chain(create_apeitemref_for_number_pair(
558 tag.get_string(&ItemKey::DiscNumber),
559 tag.get_string(&ItemKey::DiscTotal),
560 "Disk",
561 ))
562}
563
564#[cfg(test)]
565mod tests {
566 use crate::ape::{ApeItem, ApeTag};
567 use crate::config::{ParseOptions, WriteOptions};
568 use crate::id3::v2::util::pairs::DEFAULT_NUMBER_IN_PAIR;
569 use crate::prelude::*;
570 use crate::tag::{ItemValue, Tag, TagItem, TagType};
571
572 use crate::picture::{MimeType, Picture, PictureType};
573 use std::io::Cursor;
574
575 #[test_log::test]
576 fn parse_ape() {
577 let mut expected_tag = ApeTag::default();
578
579 let title_item = ApeItem::new(
580 String::from("TITLE"),
581 ItemValue::Text(String::from("Foo title")),
582 )
583 .unwrap();
584
585 let artist_item = ApeItem::new(
586 String::from("ARTIST"),
587 ItemValue::Text(String::from("Bar artist")),
588 )
589 .unwrap();
590
591 let album_item = ApeItem::new(
592 String::from("ALBUM"),
593 ItemValue::Text(String::from("Baz album")),
594 )
595 .unwrap();
596
597 let comment_item = ApeItem::new(
598 String::from("COMMENT"),
599 ItemValue::Text(String::from("Qux comment")),
600 )
601 .unwrap();
602
603 let year_item =
604 ApeItem::new(String::from("YEAR"), ItemValue::Text(String::from("1984"))).unwrap();
605
606 let track_number_item =
607 ApeItem::new(String::from("TRACK"), ItemValue::Text(String::from("1"))).unwrap();
608
609 let genre_item = ApeItem::new(
610 String::from("GENRE"),
611 ItemValue::Text(String::from("Classical")),
612 )
613 .unwrap();
614
615 expected_tag.insert(title_item);
616 expected_tag.insert(artist_item);
617 expected_tag.insert(album_item);
618 expected_tag.insert(comment_item);
619 expected_tag.insert(year_item);
620 expected_tag.insert(track_number_item);
621 expected_tag.insert(genre_item);
622
623 let tag = crate::tag::utils::test_utils::read_path("tests/tags/assets/test.apev2");
624 let mut reader = Cursor::new(tag);
625
626 let (Some(parsed_tag), _) =
627 crate::ape::tag::read::read_ape_tag(&mut reader, false, ParseOptions::new()).unwrap()
628 else {
629 unreachable!();
630 };
631
632 assert_eq!(expected_tag.len(), parsed_tag.len());
633
634 for item in &expected_tag.items {
635 assert!(parsed_tag.items.contains(item));
636 }
637 }
638
639 #[test_log::test]
640 fn ape_re_read() {
641 let tag_bytes = crate::tag::utils::test_utils::read_path("tests/tags/assets/test.apev2");
642 let mut reader = Cursor::new(tag_bytes);
643
644 let (Some(parsed_tag), _) =
645 crate::ape::tag::read::read_ape_tag(&mut reader, false, ParseOptions::new()).unwrap()
646 else {
647 unreachable!();
648 };
649
650 let mut writer = Vec::new();
651 parsed_tag
652 .dump_to(&mut writer, WriteOptions::default())
653 .unwrap();
654
655 let mut temp_reader = Cursor::new(writer);
656
657 let (Some(temp_parsed_tag), _) =
658 crate::ape::tag::read::read_ape_tag(&mut temp_reader, false, ParseOptions::new())
659 .unwrap()
660 else {
661 unreachable!();
662 };
663
664 assert_eq!(parsed_tag, temp_parsed_tag);
665 }
666
667 #[test_log::test]
668 fn ape_to_tag() {
669 let tag_bytes = crate::tag::utils::test_utils::read_path("tests/tags/assets/test.apev2");
670 let mut reader = Cursor::new(tag_bytes);
671
672 let (Some(ape), _) =
673 crate::ape::tag::read::read_ape_tag(&mut reader, false, ParseOptions::new()).unwrap()
674 else {
675 unreachable!();
676 };
677
678 let tag: Tag = ape.into();
679
680 crate::tag::utils::test_utils::verify_tag(&tag, true, true);
681 }
682
683 #[test_log::test]
684 fn tag_to_ape() {
685 fn verify_key(tag: &ApeTag, key: &str, expected_val: &str) {
686 assert_eq!(
687 tag.get(key).map(ApeItem::value),
688 Some(&ItemValue::Text(String::from(expected_val)))
689 );
690 }
691
692 let tag = crate::tag::utils::test_utils::create_tag(TagType::Ape);
693
694 let ape_tag: ApeTag = tag.into();
695
696 verify_key(&ape_tag, "Title", "Foo title");
697 verify_key(&ape_tag, "Artist", "Bar artist");
698 verify_key(&ape_tag, "Album", "Baz album");
699 verify_key(&ape_tag, "Comment", "Qux comment");
700 verify_key(&ape_tag, "Track", "1");
701 verify_key(&ape_tag, "Genre", "Classical");
702 }
703
704 #[test_log::test]
705 fn set_track() {
706 let mut ape = ApeTag::default();
707 let track = 1;
708
709 ape.set_track(track);
710
711 assert_eq!(ape.track().unwrap(), track);
712 assert!(ape.track_total().is_none());
713 }
714
715 #[test_log::test]
716 fn set_track_total() {
717 let mut ape = ApeTag::default();
718 let track_total = 2;
719
720 ape.set_track_total(track_total);
721
722 assert_eq!(ape.track().unwrap(), DEFAULT_NUMBER_IN_PAIR);
723 assert_eq!(ape.track_total().unwrap(), track_total);
724 }
725
726 #[test_log::test]
727 fn set_track_and_track_total() {
728 let mut ape = ApeTag::default();
729 let track = 1;
730 let track_total = 2;
731
732 ape.set_track(track);
733 ape.set_track_total(track_total);
734
735 assert_eq!(ape.track().unwrap(), track);
736 assert_eq!(ape.track_total().unwrap(), track_total);
737 }
738
739 #[test_log::test]
740 fn set_track_total_and_track() {
741 let mut ape = ApeTag::default();
742 let track_total = 2;
743 let track = 1;
744
745 ape.set_track_total(track_total);
746 ape.set_track(track);
747
748 assert_eq!(ape.track_total().unwrap(), track_total);
749 assert_eq!(ape.track().unwrap(), track);
750 }
751
752 #[test_log::test]
753 fn set_disk() {
754 let mut ape = ApeTag::default();
755 let disk = 1;
756
757 ape.set_disk(disk);
758
759 assert_eq!(ape.disk().unwrap(), disk);
760 assert!(ape.disk_total().is_none());
761 }
762
763 #[test_log::test]
764 fn set_disk_total() {
765 let mut ape = ApeTag::default();
766 let disk_total = 2;
767
768 ape.set_disk_total(disk_total);
769
770 assert_eq!(ape.disk().unwrap(), DEFAULT_NUMBER_IN_PAIR);
771 assert_eq!(ape.disk_total().unwrap(), disk_total);
772 }
773
774 #[test_log::test]
775 fn set_disk_and_disk_total() {
776 let mut ape = ApeTag::default();
777 let disk = 1;
778 let disk_total = 2;
779
780 ape.set_disk(disk);
781 ape.set_disk_total(disk_total);
782
783 assert_eq!(ape.disk().unwrap(), disk);
784 assert_eq!(ape.disk_total().unwrap(), disk_total);
785 }
786
787 #[test_log::test]
788 fn set_disk_total_and_disk() {
789 let mut ape = ApeTag::default();
790 let disk_total = 2;
791 let disk = 1;
792
793 ape.set_disk_total(disk_total);
794 ape.set_disk(disk);
795
796 assert_eq!(ape.disk_total().unwrap(), disk_total);
797 assert_eq!(ape.disk().unwrap(), disk);
798 }
799
800 #[test_log::test]
801 fn track_number_tag_to_ape() {
802 let track_number = 1;
803
804 let mut tag = Tag::new(TagType::Ape);
805
806 tag.push(TagItem::new(
807 ItemKey::TrackNumber,
808 ItemValue::Text(track_number.to_string()),
809 ));
810
811 let tag: ApeTag = tag.into();
812
813 assert_eq!(tag.track().unwrap(), track_number);
814 assert!(tag.track_total().is_none());
815 }
816
817 #[test_log::test]
818 fn track_total_tag_to_ape() {
819 let track_total = 2;
820
821 let mut tag = Tag::new(TagType::Ape);
822
823 tag.push(TagItem::new(
824 ItemKey::TrackTotal,
825 ItemValue::Text(track_total.to_string()),
826 ));
827
828 let tag: ApeTag = tag.into();
829
830 assert_eq!(tag.track().unwrap(), DEFAULT_NUMBER_IN_PAIR);
831 assert_eq!(tag.track_total().unwrap(), track_total);
832 }
833
834 #[test_log::test]
835 fn track_number_and_track_total_tag_to_ape() {
836 let track_number = 1;
837 let track_total = 2;
838
839 let mut tag = Tag::new(TagType::Ape);
840
841 tag.push(TagItem::new(
842 ItemKey::TrackNumber,
843 ItemValue::Text(track_number.to_string()),
844 ));
845
846 tag.push(TagItem::new(
847 ItemKey::TrackTotal,
848 ItemValue::Text(track_total.to_string()),
849 ));
850
851 let tag: ApeTag = tag.into();
852
853 assert_eq!(tag.track().unwrap(), track_number);
854 assert_eq!(tag.track_total().unwrap(), track_total);
855 }
856
857 #[test_log::test]
858 fn disk_number_tag_to_ape() {
859 let disk_number = 1;
860
861 let mut tag = Tag::new(TagType::Ape);
862
863 tag.push(TagItem::new(
864 ItemKey::DiscNumber,
865 ItemValue::Text(disk_number.to_string()),
866 ));
867
868 let tag: ApeTag = tag.into();
869
870 assert_eq!(tag.disk().unwrap(), disk_number);
871 assert!(tag.disk_total().is_none());
872 }
873
874 #[test_log::test]
875 fn disk_total_tag_to_ape() {
876 let disk_total = 2;
877
878 let mut tag = Tag::new(TagType::Ape);
879
880 tag.push(TagItem::new(
881 ItemKey::DiscTotal,
882 ItemValue::Text(disk_total.to_string()),
883 ));
884
885 let tag: ApeTag = tag.into();
886
887 assert_eq!(tag.disk().unwrap(), DEFAULT_NUMBER_IN_PAIR);
888 assert_eq!(tag.disk_total().unwrap(), disk_total);
889 }
890
891 #[test_log::test]
892 fn disk_number_and_disk_total_tag_to_ape() {
893 let disk_number = 1;
894 let disk_total = 2;
895
896 let mut tag = Tag::new(TagType::Ape);
897
898 tag.push(TagItem::new(
899 ItemKey::DiscNumber,
900 ItemValue::Text(disk_number.to_string()),
901 ));
902
903 tag.push(TagItem::new(
904 ItemKey::DiscTotal,
905 ItemValue::Text(disk_total.to_string()),
906 ));
907
908 let tag: ApeTag = tag.into();
909
910 assert_eq!(tag.disk().unwrap(), disk_number);
911 assert_eq!(tag.disk_total().unwrap(), disk_total);
912 }
913
914 #[test_log::test]
915 fn skip_reading_cover_art() {
916 let p = Picture::new_unchecked(
917 PictureType::CoverFront,
918 Some(MimeType::Jpeg),
919 None,
920 std::iter::repeat(0).take(50).collect::<Vec<u8>>(),
921 );
922
923 let mut tag = Tag::new(TagType::Ape);
924 tag.push_picture(p);
925
926 tag.set_artist(String::from("Foo artist"));
927
928 let mut writer = Vec::new();
929 tag.dump_to(&mut writer, WriteOptions::new()).unwrap();
930
931 let mut reader = Cursor::new(writer);
932 let (Some(ape), _) = crate::ape::tag::read::read_ape_tag(
933 &mut reader,
934 false,
935 ParseOptions::new().read_cover_art(false),
936 )
937 .unwrap() else {
938 unreachable!()
939 };
940
941 assert_eq!(ape.len(), 1);
942 }
943}