use crate::error::{Error, MuxError, Result};
use crate::ffi::demux::Demux;
use alloc::vec::Vec;
use whereat::*;
pub fn get_icc_profile(webp_data: &[u8]) -> Result<Option<Vec<u8>>> {
get_chunk(webp_data, b"ICCP", &crate::Limits::none())
}
pub fn get_exif(webp_data: &[u8]) -> Result<Option<Vec<u8>>> {
get_chunk(webp_data, b"EXIF", &crate::Limits::none())
}
pub fn get_xmp(webp_data: &[u8]) -> Result<Option<Vec<u8>>> {
get_chunk(webp_data, b"XMP ", &crate::Limits::none())
}
pub fn get_icc_profile_with_limits(
webp_data: &[u8],
limits: &crate::Limits,
) -> Result<Option<Vec<u8>>> {
get_chunk(webp_data, b"ICCP", limits)
}
pub fn get_exif_with_limits(webp_data: &[u8], limits: &crate::Limits) -> Result<Option<Vec<u8>>> {
get_chunk(webp_data, b"EXIF", limits)
}
pub fn get_xmp_with_limits(webp_data: &[u8], limits: &crate::Limits) -> Result<Option<Vec<u8>>> {
get_chunk(webp_data, b"XMP ", limits)
}
unsafe fn create_mux_from_data(webp_data: &[u8], copy_data: bool) -> *mut libwebp_sys::WebPMux {
let data = libwebp_sys::WebPData {
bytes: webp_data.as_ptr(),
size: webp_data.len(),
};
unsafe {
libwebp_sys::WebPMuxCreateInternal(
&data,
copy_data as i32,
libwebp_sys::WEBP_MUX_ABI_VERSION as i32,
)
}
}
pub(crate) const MAX_METADATA_CHUNK_BYTES: usize = 256 * 1024 * 1024;
fn get_chunk(
webp_data: &[u8],
fourcc: &[u8; 4],
limits: &crate::Limits,
) -> Result<Option<Vec<u8>>> {
limits
.check_input_size(webp_data.len() as u64)
.map_err(|e| at!(Error::LimitExceeded(e)))?;
let demux = Demux::new(webp_data)?;
let Some(chunk) = demux.get_chunk(fourcc) else {
return Ok(None);
};
let bytes = chunk.bytes();
if bytes.is_empty() {
return Ok(None);
}
if bytes.len() > MAX_METADATA_CHUNK_BYTES {
return Err(at!(Error::InvalidInput(alloc::format!(
"metadata chunk exceeds internal hard cap: {} bytes (max {})",
bytes.len(),
MAX_METADATA_CHUNK_BYTES
))));
}
limits
.check_metadata_bytes(u32::try_from(bytes.len()).unwrap_or(u32::MAX))
.map_err(|e| at!(Error::LimitExceeded(e)))?;
Ok(Some(bytes.to_vec()))
}
pub fn embed_icc(webp_data: &[u8], icc_profile: &[u8]) -> Result<Vec<u8>> {
embed_chunk(webp_data, b"ICCP", icc_profile)
}
pub fn embed_exif(webp_data: &[u8], exif_data: &[u8]) -> Result<Vec<u8>> {
embed_chunk(webp_data, b"EXIF", exif_data)
}
pub fn embed_xmp(webp_data: &[u8], xmp_data: &[u8]) -> Result<Vec<u8>> {
embed_chunk(webp_data, b"XMP ", xmp_data)
}
fn embed_chunk(webp_data: &[u8], fourcc: &[u8; 4], chunk_data: &[u8]) -> Result<Vec<u8>> {
let mux = unsafe { create_mux_from_data(webp_data, true) };
if mux.is_null() {
return Err(at!(Error::MuxError(MuxError::BadData)));
}
let chunk = libwebp_sys::WebPData {
bytes: chunk_data.as_ptr(),
size: chunk_data.len(),
};
let err = unsafe {
libwebp_sys::WebPMuxSetChunk(
mux,
fourcc.as_ptr() as *const core::ffi::c_char,
&chunk,
1, )
};
if err != libwebp_sys::WebPMuxError::WEBP_MUX_OK {
unsafe { libwebp_sys::WebPMuxDelete(mux) };
return Err(at!(Error::MuxError(MuxError::from(err as i32))));
}
let mut output_data = libwebp_sys::WebPData::default();
let err = unsafe { libwebp_sys::WebPMuxAssemble(mux, &mut output_data) };
if err != libwebp_sys::WebPMuxError::WEBP_MUX_OK {
unsafe { libwebp_sys::WebPMuxDelete(mux) };
return Err(at!(Error::MuxError(MuxError::from(err as i32))));
}
let result = unsafe {
if output_data.bytes.is_null() || output_data.size == 0 {
libwebp_sys::WebPMuxDelete(mux);
return Err(at!(Error::MuxError(MuxError::MemoryError)));
}
let slice = core::slice::from_raw_parts(output_data.bytes, output_data.size);
let vec = slice.to_vec();
libwebp_sys::WebPDataClear(&mut output_data);
libwebp_sys::WebPMuxDelete(mux);
vec
};
Ok(result)
}
pub fn remove_icc(webp_data: &[u8]) -> Result<Vec<u8>> {
remove_chunk(webp_data, b"ICCP")
}
pub fn remove_exif(webp_data: &[u8]) -> Result<Vec<u8>> {
remove_chunk(webp_data, b"EXIF")
}
pub fn remove_xmp(webp_data: &[u8]) -> Result<Vec<u8>> {
remove_chunk(webp_data, b"XMP ")
}
fn remove_chunk(webp_data: &[u8], fourcc: &[u8; 4]) -> Result<Vec<u8>> {
let mux = unsafe { create_mux_from_data(webp_data, true) };
if mux.is_null() {
return Err(at!(Error::MuxError(MuxError::BadData)));
}
let err = unsafe {
libwebp_sys::WebPMuxDeleteChunk(mux, fourcc.as_ptr() as *const core::ffi::c_char)
};
if err != libwebp_sys::WebPMuxError::WEBP_MUX_OK
&& err != libwebp_sys::WebPMuxError::WEBP_MUX_NOT_FOUND
{
unsafe { libwebp_sys::WebPMuxDelete(mux) };
return Err(at!(Error::MuxError(MuxError::from(err as i32))));
}
let mut output_data = libwebp_sys::WebPData::default();
let err = unsafe { libwebp_sys::WebPMuxAssemble(mux, &mut output_data) };
if err != libwebp_sys::WebPMuxError::WEBP_MUX_OK {
unsafe { libwebp_sys::WebPMuxDelete(mux) };
return Err(at!(Error::MuxError(MuxError::from(err as i32))));
}
let result = unsafe {
if output_data.bytes.is_null() || output_data.size == 0 {
libwebp_sys::WebPMuxDelete(mux);
return Err(at!(Error::MuxError(MuxError::MemoryError)));
}
let slice = core::slice::from_raw_parts(output_data.bytes, output_data.size);
let vec = slice.to_vec();
libwebp_sys::WebPDataClear(&mut output_data);
libwebp_sys::WebPMuxDelete(mux);
vec
};
Ok(result)
}
#[cfg(test)]
mod tests {
}