use mime::Mime; use std::{ fmt::Debug, io::{Cursor, Read}, }; use super::{FlacMetablockDecode, FlacMetablockEncode, FlacMetablockHeader, FlacMetablockType}; use crate::{FlacDecodeError, FlacEncodeError, PictureType}; /// A picture metablock in a flac file pub struct FlacPictureBlock { /// The type of this picture pub picture_type: PictureType, /// The format of this picture pub mime: Mime, /// The description of this picture pub description: String, /// The width of this picture, in px pub width: u32, /// The height of this picture, in px pub height: u32, /// The bit depth of this picture pub bit_depth: u32, /// The color count of this picture (if indexed) pub color_count: u32, /// The image data pub img_data: Vec, } impl Debug for FlacPictureBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("FlacPicture") .field("type", &self.picture_type) .field("mime", &self.mime) .field("img_data.len()", &self.img_data.len()) .finish() } } impl FlacMetablockDecode for FlacPictureBlock { fn decode(data: &[u8]) -> Result { let mut d = Cursor::new(data); // This is re-used whenever we need to read four bytes let mut block = [0u8; 4]; #[expect(clippy::map_err_ignore)] d.read_exact(&mut block) .map_err(|_| FlacDecodeError::MalformedBlock)?; let picture_type = u32::from_be_bytes(block); let picture_type = PictureType::from_idx(picture_type) .ok_or(FlacDecodeError::InvalidPictureType(picture_type))?; // Image format let mime = { d.read_exact(&mut block) .map_err(|_err| FlacDecodeError::MalformedBlock)?; #[expect(clippy::expect_used)] let mime_length = u32::from_be_bytes(block) .try_into() .expect("mime length does not fit into usize"); let mut mime = vec![0u8; mime_length]; d.read_exact(&mut mime) .map_err(|_err| FlacDecodeError::MalformedBlock)?; String::from_utf8(mime) .ok() .and_then(|x| x.parse().ok()) .unwrap_or(mime::APPLICATION_OCTET_STREAM) }; // Image description let description = { d.read_exact(&mut block) .map_err(|_err| FlacDecodeError::MalformedBlock)?; #[expect(clippy::expect_used)] let desc_length = u32::from_be_bytes(block) .try_into() .expect("description length does not fit into usize"); let mut desc = vec![0u8; desc_length]; d.read_exact(&mut desc) .map_err(|_err| FlacDecodeError::MalformedBlock)?; String::from_utf8(desc)? }; // Image width #[expect(clippy::map_err_ignore)] d.read_exact(&mut block) .map_err(|_| FlacDecodeError::MalformedBlock)?; let width = u32::from_be_bytes(block); // Image height #[expect(clippy::map_err_ignore)] d.read_exact(&mut block) .map_err(|_| FlacDecodeError::MalformedBlock)?; let height = u32::from_be_bytes(block); // Image bit depth #[expect(clippy::map_err_ignore)] d.read_exact(&mut block) .map_err(|_| FlacDecodeError::MalformedBlock)?; let depth = u32::from_be_bytes(block); // Color count for indexed images #[expect(clippy::map_err_ignore)] d.read_exact(&mut block) .map_err(|_| FlacDecodeError::MalformedBlock)?; let color_count = u32::from_be_bytes(block); // Image data length let img_data = { d.read_exact(&mut block) .map_err(|_err| FlacDecodeError::MalformedBlock)?; #[expect(clippy::expect_used)] let data_length = u32::from_be_bytes(block) .try_into() .expect("image data length does not fit into usize"); let mut img_data = vec![0u8; data_length]; d.read_exact(&mut img_data) .map_err(|_err| FlacDecodeError::MalformedBlock)?; img_data }; Ok(Self { picture_type, mime, description, width, height, bit_depth: depth, color_count, img_data, }) } } impl FlacMetablockEncode for FlacPictureBlock { #[expect(clippy::expect_used)] fn get_len(&self) -> u32 { (4 + (4 + self.mime.to_string().len()) + (4 + self.description.len()) + 4 + 4 + 4 + 4 + (4 + self.img_data.len())) .try_into() .expect("picture block size does not fit into u32") } fn encode( &self, is_last: bool, with_header: bool, target: &mut impl std::io::Write, ) -> Result<(), FlacEncodeError> { if with_header { let header = FlacMetablockHeader { block_type: FlacMetablockType::Picture, length: self.get_len(), is_last, }; header.encode(target)?; } target.write_all(&self.picture_type.to_idx().to_be_bytes())?; #[expect(clippy::expect_used)] { let mime = self.mime.to_string(); target.write_all( &u32::try_from(mime.len()) .expect("mime length does not fit into u32") .to_be_bytes(), )?; target.write_all(self.mime.to_string().as_bytes())?; drop(mime); target.write_all( &u32::try_from(self.description.len()) .expect("description length does not fit into u32") .to_be_bytes(), )?; target.write_all(self.description.as_bytes())?; target.write_all(&self.width.to_be_bytes())?; target.write_all(&self.height.to_be_bytes())?; target.write_all(&self.bit_depth.to_be_bytes())?; target.write_all(&self.color_count.to_be_bytes())?; target.write_all( &u32::try_from(self.img_data.len()) .expect("image data length does not fit into u32") .to_be_bytes(), )?; } target.write_all(&self.img_data)?; return Ok(()); } }