#[derive(Clone, Debug)]
pub struct AttachedPicture {
pub mime_type: String,
pub picture_type: PictureType,
pub description: String,
pub data: Vec<u8>,
}
impl AttachedPicture {
pub fn new(mime_type: impl Into<String>, picture_type: PictureType) -> Self {
Self {
mime_type: mime_type.into(),
picture_type,
description: String::new(),
data: Vec::new(),
}
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
pub fn with_data(mut self, data: Vec<u8>) -> Self {
self.data = data;
self
}
pub fn with_picture_type(mut self, picture_type: PictureType) -> Self {
self.picture_type = picture_type;
self
}
pub fn is_external_link(&self) -> bool {
self.mime_type == "-->"
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
#[repr(u8)]
pub enum PictureType {
Other = 0x00,
FileIcon32x32 = 0x01,
FileIcon = 0x02,
FrontCover = 0x03,
BackCover = 0x04,
LeafletPage = 0x05,
Media = 0x06,
LeadArtist = 0x07,
Artist = 0x08,
Conductor = 0x09,
BandOrchestra = 0x0A,
Composer = 0x0B,
Lyricist = 0x0C,
RecordingLocation = 0x0D,
DuringRecording = 0x0E,
DuringPerformance = 0x0F,
MovieScreenCapture = 0x10,
ABrightColouredFish = 0x11,
Illustration = 0x12,
BandLogo = 0x13,
PublisherLogo = 0x14,
Unknown = 0xFF,
}
impl PictureType {
pub fn from_u8(b: u8) -> Self {
match b {
0x00 => Self::Other,
0x01 => Self::FileIcon32x32,
0x02 => Self::FileIcon,
0x03 => Self::FrontCover,
0x04 => Self::BackCover,
0x05 => Self::LeafletPage,
0x06 => Self::Media,
0x07 => Self::LeadArtist,
0x08 => Self::Artist,
0x09 => Self::Conductor,
0x0A => Self::BandOrchestra,
0x0B => Self::Composer,
0x0C => Self::Lyricist,
0x0D => Self::RecordingLocation,
0x0E => Self::DuringRecording,
0x0F => Self::DuringPerformance,
0x10 => Self::MovieScreenCapture,
0x11 => Self::ABrightColouredFish,
0x12 => Self::Illustration,
0x13 => Self::BandLogo,
0x14 => Self::PublisherLogo,
_ => Self::Unknown,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
pub fn is_known(self) -> bool {
!matches!(self, Self::Unknown)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn picture_type_known_values() {
assert_eq!(PictureType::from_u8(0x03), PictureType::FrontCover);
assert_eq!(PictureType::from_u8(0x07), PictureType::LeadArtist);
assert_eq!(PictureType::from_u8(0x14), PictureType::PublisherLogo);
}
#[test]
fn picture_type_unknown_collapses() {
assert_eq!(PictureType::from_u8(0x15), PictureType::Unknown);
assert_eq!(PictureType::from_u8(0xAB), PictureType::Unknown);
}
#[test]
fn to_u8_inverts_from_u8_on_every_assigned_code() {
for b in 0x00u8..=0x14 {
assert_eq!(PictureType::from_u8(b).to_u8(), b);
}
}
#[test]
fn to_u8_unknown_emits_sentinel_byte() {
assert_eq!(PictureType::Unknown.to_u8(), 0xFF);
let collapsed = PictureType::from_u8(0xAB);
assert_eq!(collapsed, PictureType::Unknown);
assert_eq!(collapsed.to_u8(), 0xFF);
assert_ne!(collapsed.to_u8(), 0xAB);
}
#[test]
fn is_known_separates_assigned_from_sentinel() {
assert!(PictureType::Other.is_known());
assert!(PictureType::FrontCover.is_known());
assert!(PictureType::PublisherLogo.is_known());
assert!(!PictureType::Unknown.is_known());
}
#[test]
fn attached_picture_new_defaults_then_builders_fill() {
let p = AttachedPicture::new("image/png", PictureType::FrontCover);
assert_eq!(p.mime_type, "image/png");
assert_eq!(p.picture_type, PictureType::FrontCover);
assert!(p.description.is_empty());
assert!(p.data.is_empty());
assert!(!p.is_external_link());
let filled = p
.with_description("Album art")
.with_data(vec![0x89, b'P', b'N', b'G']);
assert_eq!(filled.description, "Album art");
assert_eq!(filled.data, vec![0x89, b'P', b'N', b'G']);
}
#[test]
fn attached_picture_with_picture_type_replaces() {
let p = AttachedPicture::new("image/jpeg", PictureType::Other)
.with_picture_type(PictureType::BackCover);
assert_eq!(p.picture_type, PictureType::BackCover);
}
#[test]
fn attached_picture_external_link_detected_by_sentinel_mime() {
let p = AttachedPicture::new("-->", PictureType::FrontCover)
.with_data(b"https://example.invalid/cover.jpg".to_vec());
assert!(p.is_external_link());
let inline = AttachedPicture::new("image/jpeg", PictureType::FrontCover);
assert!(!inline.is_external_link());
}
}