use std::fmt::Display;
use std::fs::OpenOptions;
use std::io::{Cursor, Read, Seek};
use std::path::Path;
use base64::prelude::{BASE64_STANDARD, Engine as _};
use crate::Result;
#[allow(dead_code)] #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum PictureType {
#[default]
Other = 0,
FileIcon,
OtherIcon,
CoverFront,
CoverBack,
LeafletPage,
Media,
LeadArtist,
Artist,
Conductor,
BandOrchestra,
Composter,
Lyricist,
RecordingLocation,
DuringRecording,
DuringPerformance,
MovieCapture,
BrightColouredFish,
Illustration,
BandLogo,
PublisherLogo,
}
impl PictureType {
pub fn from_u32(num: u32) -> std::result::Result<Self, PictureError> {
if num > 20 {
Err(PictureError::InvalidPictureType)
} else {
Ok(unsafe { std::mem::transmute::<u32, Self>(num) })
}
}
}
#[derive(Debug, Clone)]
pub enum PictureError {
InvalidPictureType,
MimeTooLong,
DescriptionTooLong,
DataTooLong,
Base64DecodeError(base64::DecodeError),
NoMimeType,
}
impl Display for PictureError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::InvalidPictureType => "Invalid picture type",
Self::MimeTooLong => "MIME type is too long (more than u32::MAX bytes long!)",
Self::DescriptionTooLong => "Description is too long (more than u32::MAX bytes long!)",
Self::DataTooLong => "Picture data is too long (more than u32::MAX bytes long!)",
Self::Base64DecodeError(_) => "Failed to decode base64 data",
Self::NoMimeType => "Failed to sniff mime type from file",
})
}
}
impl std::error::Error for PictureError {}
impl From<base64::DecodeError> for PictureError {
fn from(value: base64::DecodeError) -> Self {
Self::Base64DecodeError(value)
}
}
#[allow(dead_code)]
#[derive(Default, Clone, Debug)]
pub struct Picture {
pub picture_type: PictureType,
pub mime_type: String,
pub description: String,
pub data: Vec<u8>,
}
impl Picture {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn from_bytes(data: &[u8]) -> Result<Self> {
let mut cursor = Cursor::new(data);
let mut buffer = [0; 4];
cursor.read_exact(&mut buffer)?;
let picture_type = PictureType::from_u32(u32::from_be_bytes(buffer))?;
let mut buffer = [0; 4];
cursor.read_exact(&mut buffer)?;
let mime_length: usize = u32::from_be_bytes(buffer).try_into()?;
let mut buffer = vec![0; mime_length];
cursor.read_exact(&mut buffer)?;
let mime_type = String::from_utf8(buffer)?;
let mut buffer = [0; 4];
cursor.read_exact(&mut buffer)?;
let desc_length: usize = u32::from_be_bytes(buffer).try_into()?;
let mut buffer = vec![0; desc_length];
cursor.read_exact(&mut buffer)?;
let description = String::from_utf8(buffer)?;
cursor.seek_relative(16)?;
let mut buffer = [0; 4];
cursor.read_exact(&mut buffer)?;
let data_length: usize = u32::from_be_bytes(buffer).try_into()?;
let mut data = vec![0; data_length];
cursor.read_exact(&mut data)?;
Ok(Self {
picture_type,
mime_type,
description,
data,
})
}
pub fn to_bytes(&self) -> std::result::Result<Vec<u8>, PictureError> {
let mut output = vec![];
output.extend_from_slice(&(self.picture_type as u32).to_be_bytes());
let mime_length: u32 = self
.mime_type
.len()
.try_into()
.map_err(|_| PictureError::MimeTooLong)?;
output.extend_from_slice(&mime_length.to_be_bytes());
output.extend_from_slice(self.mime_type.as_bytes());
let desc_length: u32 = self
.description
.len()
.try_into()
.map_err(|_| PictureError::DescriptionTooLong)?;
output.extend_from_slice(&desc_length.to_be_bytes());
output.extend_from_slice(self.description.as_bytes());
let zero = [0; 16];
output.extend_from_slice(&zero);
let data_len: u32 = self
.data
.len()
.try_into()
.map_err(|_| PictureError::DataTooLong)?;
output.extend_from_slice(&data_len.to_be_bytes());
output.extend_from_slice(&self.data);
Ok(output)
}
pub fn to_base64(&self) -> Result<String> {
let bytes = self.to_bytes()?;
let encoded = BASE64_STANDARD.encode(bytes);
Ok(encoded)
}
pub fn from_base64(data: &str) -> Result<Self> {
let bytes = BASE64_STANDARD.decode(data).map_err(PictureError::from)?;
let pic = Self::from_bytes(&bytes)?;
Ok(pic)
}
pub fn read_from<R: Read>(mut f_in: R, mime_type: Option<String>) -> Result<Self> {
let mut output = vec![];
f_in.read_to_end(&mut output)?;
let mime_type = match mime_type {
Some(s) => s,
None => infer::get(&output)
.ok_or(PictureError::NoMimeType)?
.mime_type()
.into(),
};
let mut pic = Self::new();
pic.mime_type = mime_type;
pic.data = output;
Ok(pic)
}
pub fn read_from_path<P: AsRef<Path>>(path: P, mime_type: Option<String>) -> Result<Self> {
let file = OpenOptions::new().read(true).open(path)?;
Self::read_from(file, mime_type)
}
}