1use crate::config::ParsingMode;
4use crate::error::{ErrorKind, LoftyError, Result};
5use crate::macros::err;
6use crate::util::text::utf8_decode_str;
7
8use std::borrow::Cow;
9use std::fmt::{Debug, Display, Formatter};
10use std::io::{Cursor, Read, Seek, SeekFrom};
11
12use byteorder::{BigEndian, ReadBytesExt as _};
13use data_encoding::BASE64;
14
15pub const APE_PICTURE_TYPES: [&str; 21] = [
17 "Cover Art (Other)",
18 "Cover Art (Png Icon)",
19 "Cover Art (Icon)",
20 "Cover Art (Front)",
21 "Cover Art (Back)",
22 "Cover Art (Leaflet)",
23 "Cover Art (Media)",
24 "Cover Art (Lead Artist)",
25 "Cover Art (Artist)",
26 "Cover Art (Conductor)",
27 "Cover Art (Band)",
28 "Cover Art (Composer)",
29 "Cover Art (Lyricist)",
30 "Cover Art (Recording Location)",
31 "Cover Art (During Recording)",
32 "Cover Art (During Performance)",
33 "Cover Art (Video Capture)",
34 "Cover Art (Fish)",
35 "Cover Art (Illustration)",
36 "Cover Art (Band Logotype)",
37 "Cover Art (Publisher Logotype)",
38];
39
40#[derive(Debug, Clone, Eq, PartialEq, Hash)]
42#[non_exhaustive]
43pub enum MimeType {
44 Png,
46 Jpeg,
48 Tiff,
50 Bmp,
52 Gif,
54 Unknown(String),
56}
57
58impl MimeType {
59 #[must_use]
70 #[allow(clippy::should_implement_trait)] pub fn from_str(mime_type: &str) -> Self {
72 match &*mime_type.to_lowercase() {
73 "image/jpeg" | "image/jpg" => Self::Jpeg,
74 "image/png" => Self::Png,
75 "image/tiff" => Self::Tiff,
76 "image/bmp" => Self::Bmp,
77 "image/gif" => Self::Gif,
78 _ => Self::Unknown(mime_type.to_owned()),
79 }
80 }
81
82 #[must_use]
93 pub fn as_str(&self) -> &str {
94 match self {
95 MimeType::Jpeg => "image/jpeg",
96 MimeType::Png => "image/png",
97 MimeType::Tiff => "image/tiff",
98 MimeType::Bmp => "image/bmp",
99 MimeType::Gif => "image/gif",
100 MimeType::Unknown(unknown) => unknown,
101 }
102 }
103
104 pub fn ext(&self) -> Option<&str> {
114 match self {
115 MimeType::Jpeg => Some("jpg"),
116 MimeType::Png => Some("png"),
117 MimeType::Tiff => Some("tif"),
118 MimeType::Bmp => Some("bmp"),
119 MimeType::Gif => Some("gif"),
120 MimeType::Unknown(_) => None,
121 }
122 }
123}
124
125impl Display for MimeType {
126 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
127 f.write_str(self.as_str())
128 }
129}
130
131#[allow(missing_docs)]
133#[allow(clippy::unsafe_derive_deserialize)]
134#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
135#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
136#[non_exhaustive]
137pub enum PictureType {
138 Other,
139 Icon,
140 OtherIcon,
141 CoverFront,
142 CoverBack,
143 Leaflet,
144 Media,
145 LeadArtist,
146 Artist,
147 Conductor,
148 Band,
149 Composer,
150 Lyricist,
151 RecordingLocation,
152 DuringRecording,
153 DuringPerformance,
154 ScreenCapture,
155 BrightFish,
156 Illustration,
157 BandLogo,
158 PublisherLogo,
159 Undefined(u8),
160}
161
162impl PictureType {
163 pub fn as_u8(&self) -> u8 {
167 match self {
168 Self::Other => 0,
169 Self::Icon => 1,
170 Self::OtherIcon => 2,
171 Self::CoverFront => 3,
172 Self::CoverBack => 4,
173 Self::Leaflet => 5,
174 Self::Media => 6,
175 Self::LeadArtist => 7,
176 Self::Artist => 8,
177 Self::Conductor => 9,
178 Self::Band => 10,
179 Self::Composer => 11,
180 Self::Lyricist => 12,
181 Self::RecordingLocation => 13,
182 Self::DuringRecording => 14,
183 Self::DuringPerformance => 15,
184 Self::ScreenCapture => 16,
185 Self::BrightFish => 17,
186 Self::Illustration => 18,
187 Self::BandLogo => 19,
188 Self::PublisherLogo => 20,
189 Self::Undefined(i) => *i,
190 }
191 }
192
193 pub fn from_u8(byte: u8) -> Self {
195 match byte {
196 0 => Self::Other,
197 1 => Self::Icon,
198 2 => Self::OtherIcon,
199 3 => Self::CoverFront,
200 4 => Self::CoverBack,
201 5 => Self::Leaflet,
202 6 => Self::Media,
203 7 => Self::LeadArtist,
204 8 => Self::Artist,
205 9 => Self::Conductor,
206 10 => Self::Band,
207 11 => Self::Composer,
208 12 => Self::Lyricist,
209 13 => Self::RecordingLocation,
210 14 => Self::DuringRecording,
211 15 => Self::DuringPerformance,
212 16 => Self::ScreenCapture,
213 17 => Self::BrightFish,
214 18 => Self::Illustration,
215 19 => Self::BandLogo,
216 20 => Self::PublisherLogo,
217 i => Self::Undefined(i),
218 }
219 }
220
221 pub fn as_ape_key(&self) -> Option<&str> {
225 match self {
226 Self::Other => Some("Cover Art (Other)"),
227 Self::Icon => Some("Cover Art (Png Icon)"),
228 Self::OtherIcon => Some("Cover Art (Icon)"),
229 Self::CoverFront => Some("Cover Art (Front)"),
230 Self::CoverBack => Some("Cover Art (Back)"),
231 Self::Leaflet => Some("Cover Art (Leaflet)"),
232 Self::Media => Some("Cover Art (Media)"),
233 Self::LeadArtist => Some("Cover Art (Lead Artist)"),
234 Self::Artist => Some("Cover Art (Artist)"),
235 Self::Conductor => Some("Cover Art (Conductor)"),
236 Self::Band => Some("Cover Art (Band)"),
237 Self::Composer => Some("Cover Art (Composer)"),
238 Self::Lyricist => Some("Cover Art (Lyricist)"),
239 Self::RecordingLocation => Some("Cover Art (Recording Location)"),
240 Self::DuringRecording => Some("Cover Art (During Recording)"),
241 Self::DuringPerformance => Some("Cover Art (During Performance)"),
242 Self::ScreenCapture => Some("Cover Art (Video Capture)"),
243 Self::BrightFish => Some("Cover Art (Fish)"),
244 Self::Illustration => Some("Cover Art (Illustration)"),
245 Self::BandLogo => Some("Cover Art (Band Logotype)"),
246 Self::PublisherLogo => Some("Cover Art (Publisher Logotype)"),
247 Self::Undefined(_) => None,
248 }
249 }
250
251 pub fn from_ape_key(key: &str) -> Self {
253 match key {
254 "Cover Art (Other)" => Self::Other,
255 "Cover Art (Png Icon)" => Self::Icon,
256 "Cover Art (Icon)" => Self::OtherIcon,
257 "Cover Art (Front)" => Self::CoverFront,
258 "Cover Art (Back)" => Self::CoverBack,
259 "Cover Art (Leaflet)" => Self::Leaflet,
260 "Cover Art (Media)" => Self::Media,
261 "Cover Art (Lead Artist)" => Self::LeadArtist,
262 "Cover Art (Artist)" => Self::Artist,
263 "Cover Art (Conductor)" => Self::Conductor,
264 "Cover Art (Band)" => Self::Band,
265 "Cover Art (Composer)" => Self::Composer,
266 "Cover Art (Lyricist)" => Self::Lyricist,
267 "Cover Art (Recording Location)" => Self::RecordingLocation,
268 "Cover Art (During Recording)" => Self::DuringRecording,
269 "Cover Art (During Performance)" => Self::DuringPerformance,
270 "Cover Art (Video Capture)" => Self::ScreenCapture,
271 "Cover Art (Fish)" => Self::BrightFish,
272 "Cover Art (Illustration)" => Self::Illustration,
273 "Cover Art (Band Logotype)" => Self::BandLogo,
274 "Cover Art (Publisher Logotype)" => Self::PublisherLogo,
275 _ => Self::Undefined(0),
276 }
277 }
278}
279
280#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
285pub struct PictureInformation {
286 pub width: u32,
288 pub height: u32,
290 pub color_depth: u32,
292 pub num_colors: u32,
294}
295
296impl PictureInformation {
297 pub fn from_picture(picture: &Picture) -> Result<Self> {
307 let reader = &mut &*picture.data;
308
309 if reader.len() < 8 {
310 err!(NotAPicture);
311 }
312
313 match reader[..4] {
314 [0x89, b'P', b'N', b'G'] => Ok(Self::from_png(reader).unwrap_or_default()),
315 [0xFF, 0xD8, 0xFF, ..] => Ok(Self::from_jpeg(reader).unwrap_or_default()),
316 _ => Ok(Self::default()),
317 }
318 }
319
320 pub fn from_png(mut data: &[u8]) -> Result<Self> {
326 let reader = &mut data;
327
328 let mut sig = [0; 8];
329 reader.read_exact(&mut sig)?;
330
331 if sig != [0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A] {
332 err!(NotAPicture);
333 }
334
335 let mut ihdr = [0; 8];
336 reader.read_exact(&mut ihdr)?;
337
338 if !ihdr.ends_with(&[0x49, 0x48, 0x44, 0x52]) {
340 err!(NotAPicture);
341 }
342
343 let width = reader.read_u32::<BigEndian>()?;
344 let height = reader.read_u32::<BigEndian>()?;
345 let mut color_depth = u32::from(reader.read_u8()?);
346 let color_type = reader.read_u8()?;
347
348 match color_type {
349 2 => color_depth *= 3,
350 4 | 6 => color_depth *= 4,
351 _ => {},
352 }
353
354 let mut ret = Self {
355 width,
356 height,
357 color_depth,
358 num_colors: 0,
359 };
360
361 if color_type != 3 {
365 return Ok(ret);
366 }
367
368 let mut reader = Cursor::new(reader);
369
370 reader.seek(SeekFrom::Current(7))?;
376
377 let mut chunk_type = [0; 4];
378
379 while let (Ok(size), Ok(())) = (
380 reader.read_u32::<BigEndian>(),
381 reader.read_exact(&mut chunk_type),
382 ) {
383 if &chunk_type == b"PLTE" {
384 ret.num_colors = size / 3;
386 break;
387 }
388
389 let (content_size, overflowed) = size.overflowing_add(4);
391 if overflowed {
392 break;
393 }
394
395 reader.seek(SeekFrom::Current(i64::from(content_size)))?;
396 }
397
398 Ok(ret)
399 }
400
401 pub fn from_jpeg(mut data: &[u8]) -> Result<Self> {
408 let reader = &mut data;
409
410 let mut frame_marker = [0; 4];
411 reader.read_exact(&mut frame_marker)?;
412
413 if !matches!(frame_marker, [0xFF, 0xD8, 0xFF, ..]) {
414 err!(NotAPicture);
415 }
416
417 let mut section_len = reader.read_u16::<BigEndian>()?;
418
419 let mut reader = Cursor::new(reader);
420
421 let (content_len, overflowed) = section_len.overflowing_sub(2);
423 if overflowed {
424 err!(NotAPicture);
425 }
426 reader.seek(SeekFrom::Current(i64::from(content_len)))?;
427
428 while let Ok(0xFF) = reader.read_u8() {
429 let marker = reader.read_u8()?;
430 section_len = reader.read_u16::<BigEndian>()?;
431
432 if marker == 0xDA {
435 break;
436 }
437
438 if marker == 0xC0 || marker == 0xC2 {
443 let precision = reader.read_u8()?;
444 let height = u32::from(reader.read_u16::<BigEndian>()?);
445 let width = u32::from(reader.read_u16::<BigEndian>()?);
446 let components = reader.read_u8()?;
447
448 return Ok(Self {
449 width,
450 height,
451 color_depth: u32::from(precision * components),
452 num_colors: 0,
453 });
454 }
455
456 reader.seek(SeekFrom::Current(i64::from(section_len - 2)))?;
457 }
458
459 err!(NotAPicture)
460 }
461}
462
463pub struct PictureBuilder {
467 pic_type: PictureType,
468 mime_type: Option<MimeType>,
469 description: Option<Cow<'static, str>>,
470 data: Cow<'static, [u8]>,
471}
472
473impl PictureBuilder {
474 fn new(data: Cow<'static, [u8]>) -> Self {
475 Self {
476 pic_type: PictureType::Other,
477 mime_type: None,
478 description: None,
479 data,
480 }
481 }
482
483 pub fn pic_type(mut self, pic_type: PictureType) -> Self {
501 self.pic_type = pic_type;
502 self
503 }
504
505 pub fn mime_type(mut self, mime_type: MimeType) -> Self {
523 self.mime_type = Some(mime_type);
524 self
525 }
526
527 pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
545 self.description = Some(description.into());
546 self
547 }
548
549 pub fn build(self) -> Picture {
551 self.into()
552 }
553}
554
555impl From<PictureBuilder> for Picture {
556 fn from(builder: PictureBuilder) -> Self {
557 Self {
558 pic_type: builder.pic_type,
559 mime_type: builder.mime_type,
560 description: builder.description,
561 data: builder.data,
562 }
563 }
564}
565
566#[derive(Clone, Eq, PartialEq, Hash)]
568pub struct Picture {
569 pub(crate) pic_type: PictureType,
571 pub(crate) mime_type: Option<MimeType>,
573 pub(crate) description: Option<Cow<'static, str>>,
575 pub(crate) data: Cow<'static, [u8]>,
577}
578
579impl Debug for Picture {
580 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
581 f.debug_struct("Picture")
582 .field("pic_type", &self.pic_type)
583 .field("mime_type", &self.mime_type)
584 .field("description", &self.description)
585 .field("data", &format!("<{} bytes>", self.data.len()))
586 .finish()
587 }
588}
589
590impl Picture {
591 pub(crate) const EMPTY: Self = Picture {
593 pic_type: PictureType::Other,
594 mime_type: None,
595 description: None,
596 data: Cow::Owned(Vec::new()),
597 };
598
599 pub fn from_reader<R>(reader: &mut R) -> Result<Self>
612 where
613 R: Read,
614 {
615 let mut data = Vec::new();
616 reader.read_to_end(&mut data)?;
617
618 if data.len() < 8 {
619 err!(NotAPicture);
620 }
621
622 let mime_type = Self::mimetype_from_bin(&data[..8])?;
623
624 Ok(Self {
625 pic_type: PictureType::Other,
626 mime_type: Some(mime_type),
627 description: None,
628 data: data.into(),
629 })
630 }
631
632 pub fn unchecked(data: Vec<u8>) -> PictureBuilder {
656 PictureBuilder::new(Cow::Owned(data))
657 }
658
659 pub fn pic_type(&self) -> PictureType {
661 self.pic_type
662 }
663
664 pub fn set_pic_type(&mut self, pic_type: PictureType) {
666 self.pic_type = pic_type
667 }
668
669 pub fn mime_type(&self) -> Option<&MimeType> {
674 self.mime_type.as_ref()
675 }
676
677 pub(crate) fn mime_str(&self) -> &str {
679 match self.mime_type.as_ref() {
680 Some(mime_type) => mime_type.as_str(),
681 None => "",
682 }
683 }
684
685 pub fn description(&self) -> Option<&str> {
687 self.description.as_deref()
688 }
689
690 pub fn set_description(&mut self, description: Option<String>) {
692 self.description = description.map(Cow::from);
693 }
694
695 pub fn data(&self) -> &[u8] {
697 &self.data
698 }
699
700 pub fn into_data(self) -> Vec<u8> {
702 self.data.into_owned()
703 }
704
705 pub fn as_flac_bytes(&self, picture_information: PictureInformation, encode: bool) -> Vec<u8> {
715 let mut data = Vec::<u8>::new();
716
717 let picture_type = u32::from(self.pic_type.as_u8()).to_be_bytes();
718
719 let mime_str = self.mime_str();
720 let mime_len = mime_str.len() as u32;
721
722 data.extend(picture_type);
723 data.extend(mime_len.to_be_bytes());
724 data.extend(mime_str.as_bytes());
725
726 if let Some(desc) = &self.description {
727 let desc_len = desc.len() as u32;
728
729 data.extend(desc_len.to_be_bytes());
730 data.extend(desc.as_bytes());
731 } else {
732 data.extend([0; 4]);
733 }
734
735 data.extend(picture_information.width.to_be_bytes());
736 data.extend(picture_information.height.to_be_bytes());
737 data.extend(picture_information.color_depth.to_be_bytes());
738 data.extend(picture_information.num_colors.to_be_bytes());
739
740 let pic_data = &self.data;
741 let pic_data_len = pic_data.len() as u32;
742
743 data.extend(pic_data_len.to_be_bytes());
744 data.extend(pic_data.iter());
745
746 if encode {
747 BASE64.encode(&data).into_bytes()
748 } else {
749 data
750 }
751 }
752
753 pub fn from_flac_bytes(
763 bytes: &[u8],
764 encoded: bool,
765 parse_mode: ParsingMode,
766 ) -> Result<(Self, PictureInformation)> {
767 if encoded {
768 let data = BASE64
769 .decode(bytes)
770 .map_err(|_| LoftyError::new(ErrorKind::NotAPicture))?;
771 Self::from_flac_bytes_inner(&data, parse_mode)
772 } else {
773 Self::from_flac_bytes_inner(bytes, parse_mode)
774 }
775 }
776
777 fn from_flac_bytes_inner(
778 content: &[u8],
779 parse_mode: ParsingMode,
780 ) -> Result<(Self, PictureInformation)> {
781 use crate::macros::try_vec;
782
783 let mut size = content.len();
784 let mut reader = Cursor::new(content);
785
786 if size < 32 {
787 err!(NotAPicture);
788 }
789
790 let pic_ty = reader.read_u32::<BigEndian>()?;
791 size -= 4;
792
793 if pic_ty > 255 && parse_mode == ParsingMode::Strict {
797 err!(NotAPicture);
798 }
799
800 let mime_len = reader.read_u32::<BigEndian>()? as usize;
801 size -= 4;
802
803 if mime_len > size {
804 err!(SizeMismatch);
805 }
806
807 let mime_type_str = utf8_decode_str(&content[8..8 + mime_len])?;
808 size -= mime_len;
809
810 reader.seek(SeekFrom::Current(mime_len as i64))?;
811
812 let desc_len = reader.read_u32::<BigEndian>()? as usize;
813 size -= 4;
814
815 let mut description = None;
816 if desc_len > 0 && desc_len < size {
817 let pos = 12 + mime_len;
818
819 if let Ok(desc) = utf8_decode_str(&content[pos..pos + desc_len]) {
820 description = Some(desc.to_owned().into());
821 }
822
823 size -= desc_len;
824 reader.seek(SeekFrom::Current(desc_len as i64))?;
825 }
826
827 let width = reader.read_u32::<BigEndian>()?;
828 let height = reader.read_u32::<BigEndian>()?;
829 let color_depth = reader.read_u32::<BigEndian>()?;
830 let num_colors = reader.read_u32::<BigEndian>()?;
831 let data_len = reader.read_u32::<BigEndian>()? as usize;
832 size -= 20;
833
834 if data_len <= size {
835 let mut data = try_vec![0; data_len];
836
837 if let Ok(()) = reader.read_exact(&mut data) {
838 let mime_type;
839 if mime_type_str.is_empty() {
840 mime_type = None;
841 } else {
842 mime_type = Some(MimeType::from_str(mime_type_str));
843 }
844
845 return Ok((
846 Self {
847 pic_type: PictureType::from_u8(pic_ty as u8),
848 mime_type,
849 description,
850 data: Cow::from(data),
851 },
852 PictureInformation {
853 width,
854 height,
855 color_depth,
856 num_colors,
857 },
858 ));
859 }
860 }
861
862 err!(NotAPicture)
863 }
864
865 pub fn as_ape_bytes(&self) -> Vec<u8> {
871 let mut data: Vec<u8> = Vec::new();
872
873 if let Some(desc) = &self.description {
874 data.extend(desc.as_bytes());
875 }
876
877 data.push(0);
878 data.extend(self.data.iter());
879
880 data
881 }
882
883 pub fn from_ape_bytes(key: &str, bytes: &[u8]) -> Result<Self> {
892 if bytes.is_empty() {
893 err!(NotAPicture);
894 }
895
896 let pic_type = PictureType::from_ape_key(key);
897
898 let reader = &mut &*bytes;
899 let mut pos = 0;
900
901 let mut description = None;
902 let mut desc_text = String::new();
903
904 while let Ok(ch) = reader.read_u8() {
905 pos += 1;
906
907 if ch == b'\0' {
908 break;
909 }
910
911 desc_text.push(char::from(ch));
912 }
913
914 if !desc_text.is_empty() {
915 description = Some(Cow::from(desc_text));
916 }
917
918 let mime_type = {
919 let mut identifier = [0; 8];
920 reader.read_exact(&mut identifier)?;
921
922 Self::mimetype_from_bin(&identifier[..])?
923 };
924
925 let data = Cow::from(bytes[pos..].to_vec());
926
927 Ok(Picture {
928 pic_type,
929 mime_type: Some(mime_type),
930 description,
931 data,
932 })
933 }
934
935 pub(crate) fn mimetype_from_bin(bytes: &[u8]) -> Result<MimeType> {
936 match bytes[..8] {
937 [0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A] => Ok(MimeType::Png),
938 [0xFF, 0xD8, ..] => Ok(MimeType::Jpeg),
939 [b'G', b'I', b'F', 0x38, 0x37 | 0x39, b'a', ..] => Ok(MimeType::Gif),
940 [b'B', b'M', ..] => Ok(MimeType::Bmp),
941 [b'I', b'I', b'*', 0x00, ..] | [b'M', b'M', 0x00, b'*', ..] => Ok(MimeType::Tiff),
942 _ => err!(NotAPicture),
943 }
944 }
945}
946
947impl From<Picture> for Cow<'_, Picture> {
948 fn from(pic: Picture) -> Self {
949 Cow::Owned(pic)
950 }
951}
952
953impl<'a> From<&'a Picture> for Cow<'a, Picture> {
954 fn from(pic: &'a Picture) -> Self {
955 Cow::Borrowed(pic)
956 }
957}