use std::fs::File;
use std::io::{BufWriter, Read, Seek, SeekFrom, Write};
pub fn strip_metadata(inp: &mut File, out: &mut BufWriter<File>) -> Result<(), String> {
let mut webp_sig = [0u8; 4];
inp.read_exact(&mut webp_sig).map_err(|e| e.to_string())?;
if &webp_sig != b"WEBP" {
return Err("Not a valid WebP file".into());
}
out.write_all(b"RIFF").map_err(|e| e.to_string())?;
out.write_all(&[0, 0, 0, 0]).map_err(|e| e.to_string())?;
out.write_all(b"WEBP").map_err(|e| e.to_string())?;
let mut total_written = 4u32;
loop {
let mut chunk_header = [0u8; 8];
match inp.read_exact(&mut chunk_header) {
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => break,
Err(e) => return Err(e.to_string()),
}
let tag = &chunk_header[0..4];
let size = u32::from_le_bytes([chunk_header[4], chunk_header[5], chunk_header[6], chunk_header[7]]);
let padded_size = if size % 2 != 0 { size + 1 } else { size };
if tag == b"EXIF" || tag == b"XMP " {
inp.seek(SeekFrom::Current(padded_size as i64)).map_err(|e| e.to_string())?;
} else if tag == b"VP8X" {
out.write_all(&chunk_header).map_err(|e| e.to_string())?;
let mut vp8x_data = vec![0u8; padded_size as usize];
inp.read_exact(&mut vp8x_data).map_err(|e| e.to_string())?;
vp8x_data[0] &= !0x08;
vp8x_data[0] &= !0x04;
out.write_all(&vp8x_data).map_err(|e| e.to_string())?;
total_written += 8 + padded_size;
} else {
out.write_all(&chunk_header).map_err(|e| e.to_string())?;
let mut remaining = padded_size;
let mut buf = [0u8; 8192];
while remaining > 0 {
let to_read = std::cmp::min(remaining as usize, buf.len());
inp.read_exact(&mut buf[..to_read]).map_err(|e| e.to_string())?;
out.write_all(&buf[..to_read]).map_err(|e| e.to_string())?;
remaining -= to_read as u32;
}
total_written += 8 + padded_size;
}
}
out.flush().map_err(|e| e.to_string())?;
let out_file = out.get_mut();
out_file.seek(SeekFrom::Start(4)).map_err(|e| e.to_string())?;
out_file.write_all(&total_written.to_le_bytes()).map_err(|e| e.to_string())?;
Ok(())
}
pub fn inject_metadata(inp: &mut File, out: &mut BufWriter<File>, exif_chunk: &[u8]) -> Result<(), String> {
let mut webp_sig = [0u8; 4];
inp.read_exact(&mut webp_sig).map_err(|e| e.to_string())?;
if &webp_sig != b"WEBP" {
return Err("Not a valid WebP file".into());
}
out.write_all(b"RIFF").map_err(|e| e.to_string())?;
out.write_all(&[0, 0, 0, 0]).map_err(|e| e.to_string())?;
out.write_all(b"WEBP").map_err(|e| e.to_string())?;
let mut total_written = 4u32;
let mut has_vp8x = false;
let pending_exif = Some(exif_chunk);
loop {
let mut chunk_header = [0u8; 8];
match inp.read_exact(&mut chunk_header) {
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => break,
Err(e) => return Err(e.to_string()),
}
let tag = &chunk_header[0..4];
let size = u32::from_le_bytes([chunk_header[4], chunk_header[5], chunk_header[6], chunk_header[7]]);
let padded_size = if size % 2 != 0 { size + 1 } else { size };
if tag == b"EXIF" {
inp.seek(SeekFrom::Current(padded_size as i64)).map_err(|e| e.to_string())?;
} else if tag == b"VP8X" {
has_vp8x = true;
out.write_all(&chunk_header).map_err(|e| e.to_string())?;
let mut vp8x_data = vec![0u8; padded_size as usize];
inp.read_exact(&mut vp8x_data).map_err(|e| e.to_string())?;
vp8x_data[0] |= 0x08;
out.write_all(&vp8x_data).map_err(|e| e.to_string())?;
total_written += 8 + padded_size;
} else {
out.write_all(&chunk_header).map_err(|e| e.to_string())?;
let mut remaining = padded_size;
let mut buf = [0u8; 8192];
while remaining > 0 {
let to_read = std::cmp::min(remaining as usize, buf.len());
inp.read_exact(&mut buf[..to_read]).map_err(|e| e.to_string())?;
out.write_all(&buf[..to_read]).map_err(|e| e.to_string())?;
remaining -= to_read as u32;
}
total_written += 8 + padded_size;
}
}
if !has_vp8x {
return Err("Injecting EXIF into simple (non-extended) WebP files is currently unsupported. File must contain a VP8X chunk.".into());
}
if let Some(exif_data) = pending_exif {
out.write_all(exif_data).map_err(|e| e.to_string())?;
total_written += exif_data.len() as u32;
if exif_data.len() % 2 != 0 {
out.write_all(&[0]).map_err(|e| e.to_string())?;
total_written += 1;
}
}
out.flush().map_err(|e| e.to_string())?;
let out_file = out.get_mut();
out_file.seek(SeekFrom::Start(4)).map_err(|e| e.to_string())?;
out_file.write_all(&total_written.to_le_bytes()).map_err(|e| e.to_string())?;
Ok(())
}