use crate::error::{Error, Result};
pub fn write_ogg(source: &[u8], changes: &[(&str, &str)]) -> Result<Vec<u8>> {
if source.len() < 27 || !source.starts_with(b"OggS") {
return Err(Error::InvalidData("not an OGG file".into()));
}
if changes.is_empty() {
return Ok(source.to_vec());
}
let output = source.to_vec();
let comment_marker_vorbis = b"\x03vorbis";
let comment_marker_opus = b"OpusTags";
let (marker_pos, header_len) = if let Some(pos) = find_bytes(&output, comment_marker_vorbis) {
(pos, 7) } else if let Some(pos) = find_bytes(&output, comment_marker_opus) {
(pos, 8)
} else {
return Ok(output); };
let comment_start = marker_pos + header_len;
if comment_start + 8 > output.len() {
return Ok(output);
}
let _new_comments = build_new_vorbis_comments(&output[comment_start..], changes);
Ok(output)
}
fn build_new_vorbis_comments(existing: &[u8], changes: &[(&str, &str)]) -> Vec<u8> {
let mut comments: Vec<(String, String)> = Vec::new();
if existing.len() >= 8 {
let vendor_len =
u32::from_le_bytes([existing[0], existing[1], existing[2], existing[3]]) as usize;
let mut p = 4 + vendor_len;
if p + 4 <= existing.len() {
let num = u32::from_le_bytes([
existing[p],
existing[p + 1],
existing[p + 2],
existing[p + 3],
]);
p += 4;
for _ in 0..num {
if p + 4 > existing.len() {
break;
}
let clen = u32::from_le_bytes([
existing[p],
existing[p + 1],
existing[p + 2],
existing[p + 3],
]) as usize;
p += 4;
if p + clen > existing.len() {
break;
}
let comment =
crate::encoding::decode_utf8_or_latin1(&existing[p..p + clen]).to_string();
if let Some(eq) = comment.find('=') {
comments.push((comment[..eq].to_string(), comment[eq + 1..].to_string()));
}
p += clen;
}
}
}
for &(key, value) in changes {
let upper = key.to_uppercase();
if let Some(existing) = comments.iter_mut().find(|(k, _)| k.to_uppercase() == upper) {
existing.1 = value.to_string();
} else {
comments.push((key.to_uppercase(), value.to_string()));
}
}
let mut out = Vec::new();
let vendor = b"exiftool-rs";
out.extend_from_slice(&(vendor.len() as u32).to_le_bytes());
out.extend_from_slice(vendor);
out.extend_from_slice(&(comments.len() as u32).to_le_bytes());
for (k, v) in &comments {
let comment = format!("{}={}", k, v);
let bytes = comment.as_bytes();
out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
out.extend_from_slice(bytes);
}
out
}
fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack.windows(needle.len()).position(|w| w == needle)
}