Files
pile/crates/pile-flac/src/blocks/picture.rs
rm-dr bf1241e0a5
All checks were successful
CI / Typos (push) Successful in 20s
CI / Clippy (push) Successful in 2m3s
CI / Build and test (push) Successful in 3m31s
pile-audio refactor
2026-02-21 19:19:41 -08:00

214 lines
5.2 KiB
Rust

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<u8>,
}
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<Self, FlacDecodeError> {
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(());
}
}