use crate::error::{Error, Result};
pub fn write_mp4(
source: &[u8],
new_tags: &[(&[u8; 4], &str)],
new_xmp: Option<&[u8]>,
) -> Result<Vec<u8>> {
if source.len() < 8 {
return Err(Error::InvalidData("file too small for MP4".into()));
}
let mut output = Vec::with_capacity(source.len());
let mut pos = 0;
let mut wrote_metadata = false;
while pos + 8 <= source.len() {
let size = u32::from_be_bytes([
source[pos],
source[pos + 1],
source[pos + 2],
source[pos + 3],
]) as usize;
let atom_type = &source[pos + 4..pos + 8];
let actual_size = if size == 0 {
source.len() - pos
} else if size == 1 && pos + 16 <= source.len() {
u64::from_be_bytes([
source[pos + 8],
source[pos + 9],
source[pos + 10],
source[pos + 11],
source[pos + 12],
source[pos + 13],
source[pos + 14],
source[pos + 15],
]) as usize
} else {
size
};
if actual_size < 8 || pos + actual_size > source.len() {
output.extend_from_slice(&source[pos..]);
break;
}
if atom_type == b"moov" {
let moov_data = &source[pos + 8..pos + actual_size];
let new_moov = rewrite_moov(moov_data, new_tags, new_xmp)?;
let new_size = (new_moov.len() + 8) as u32;
output.extend_from_slice(&new_size.to_be_bytes());
output.extend_from_slice(b"moov");
output.extend_from_slice(&new_moov);
wrote_metadata = true;
} else {
output.extend_from_slice(&source[pos..pos + actual_size]);
}
pos += actual_size;
}
if !wrote_metadata && (!new_tags.is_empty() || new_xmp.is_some()) {
let new_moov = create_moov(new_tags, new_xmp);
output.extend_from_slice(&new_moov);
}
Ok(output)
}
fn rewrite_moov(
moov_data: &[u8],
new_tags: &[(&[u8; 4], &str)],
new_xmp: Option<&[u8]>,
) -> Result<Vec<u8>> {
let mut output = Vec::with_capacity(moov_data.len());
let mut pos = 0;
let mut wrote_udta = false;
while pos + 8 <= moov_data.len() {
let size = u32::from_be_bytes([
moov_data[pos],
moov_data[pos + 1],
moov_data[pos + 2],
moov_data[pos + 3],
]) as usize;
let atom_type = &moov_data[pos + 4..pos + 8];
if size < 8 || pos + size > moov_data.len() {
output.extend_from_slice(&moov_data[pos..]);
break;
}
if atom_type == b"udta" {
let udta_content = &moov_data[pos + 8..pos + size];
let new_udta = rewrite_udta(udta_content, new_tags)?;
let new_size = (new_udta.len() + 8) as u32;
output.extend_from_slice(&new_size.to_be_bytes());
output.extend_from_slice(b"udta");
output.extend_from_slice(&new_udta);
wrote_udta = true;
} else {
output.extend_from_slice(&moov_data[pos..pos + size]);
}
pos += size;
}
if !wrote_udta && !new_tags.is_empty() {
let new_udta = create_udta(new_tags);
output.extend_from_slice(&new_udta);
}
if let Some(xmp) = new_xmp {
let uuid_xmp: [u8; 16] = [
0xBE, 0x7A, 0xCF, 0xCB, 0x97, 0xA9, 0x42, 0xE8, 0x9C, 0x71, 0x99, 0x94, 0x91, 0xE3,
0xAF, 0xAC,
];
let uuid_size = (8 + 16 + xmp.len()) as u32;
output.extend_from_slice(&uuid_size.to_be_bytes());
output.extend_from_slice(b"uuid");
output.extend_from_slice(&uuid_xmp);
output.extend_from_slice(xmp);
}
Ok(output)
}
fn rewrite_udta(udta_data: &[u8], new_tags: &[(&[u8; 4], &str)]) -> Result<Vec<u8>> {
let mut output = Vec::new();
let mut pos = 0;
let mut wrote_meta = false;
while pos + 8 <= udta_data.len() {
let size = u32::from_be_bytes([
udta_data[pos],
udta_data[pos + 1],
udta_data[pos + 2],
udta_data[pos + 3],
]) as usize;
let atom_type = &udta_data[pos + 4..pos + 8];
if size < 8 || pos + size > udta_data.len() {
output.extend_from_slice(&udta_data[pos..]);
break;
}
if atom_type == b"meta" {
let meta_content = &udta_data[pos + 8..pos + size];
let new_meta = rebuild_meta(meta_content, new_tags);
output.extend_from_slice(&new_meta);
wrote_meta = true;
} else {
output.extend_from_slice(&udta_data[pos..pos + size]);
}
pos += size;
}
if !wrote_meta && !new_tags.is_empty() {
let meta = create_meta(new_tags);
output.extend_from_slice(&meta);
}
Ok(output)
}
fn rebuild_meta(_meta_data: &[u8], new_tags: &[(&[u8; 4], &str)]) -> Vec<u8> {
let ilst = build_ilst(new_tags);
let mut meta_content = Vec::new();
meta_content.extend_from_slice(&[0, 0, 0, 0]);
let hdlr = build_hdlr();
meta_content.extend_from_slice(&hdlr);
let ilst_size = (ilst.len() + 8) as u32;
meta_content.extend_from_slice(&ilst_size.to_be_bytes());
meta_content.extend_from_slice(b"ilst");
meta_content.extend_from_slice(&ilst);
let meta_size = (meta_content.len() + 8) as u32;
let mut out = Vec::new();
out.extend_from_slice(&meta_size.to_be_bytes());
out.extend_from_slice(b"meta");
out.extend_from_slice(&meta_content);
out
}
fn build_ilst(tags: &[(&[u8; 4], &str)]) -> Vec<u8> {
let mut ilst = Vec::new();
for (key, value) in tags {
let mut data_content = Vec::new();
data_content.extend_from_slice(&[0, 0, 0, 1]); data_content.extend_from_slice(&[0, 0, 0, 0]); data_content.extend_from_slice(value.as_bytes());
let data_size = (data_content.len() + 8) as u32;
let mut data_atom = Vec::new();
data_atom.extend_from_slice(&data_size.to_be_bytes());
data_atom.extend_from_slice(b"data");
data_atom.extend_from_slice(&data_content);
let item_size = (data_atom.len() + 8) as u32;
ilst.extend_from_slice(&item_size.to_be_bytes());
ilst.extend_from_slice(*key);
ilst.extend_from_slice(&data_atom);
}
ilst
}
fn build_hdlr() -> Vec<u8> {
let mut content = Vec::new();
content.extend_from_slice(&[0, 0, 0, 0]); content.extend_from_slice(&[0, 0, 0, 0]); content.extend_from_slice(b"mdir"); content.extend_from_slice(&[0; 12]); content.push(0);
let size = (content.len() + 8) as u32;
let mut out = Vec::new();
out.extend_from_slice(&size.to_be_bytes());
out.extend_from_slice(b"hdlr");
out.extend_from_slice(&content);
out
}
fn create_udta(tags: &[(&[u8; 4], &str)]) -> Vec<u8> {
let meta = create_meta(tags);
let size = (meta.len() + 8) as u32;
let mut out = Vec::new();
out.extend_from_slice(&size.to_be_bytes());
out.extend_from_slice(b"udta");
out.extend_from_slice(&meta);
out
}
fn create_meta(tags: &[(&[u8; 4], &str)]) -> Vec<u8> {
rebuild_meta(&[], tags)
}
fn create_moov(tags: &[(&[u8; 4], &str)], xmp: Option<&[u8]>) -> Vec<u8> {
let mut content = Vec::new();
if !tags.is_empty() {
let udta = create_udta(tags);
content.extend_from_slice(&udta);
}
if let Some(xmp_data) = xmp {
let uuid_xmp: [u8; 16] = [
0xBE, 0x7A, 0xCF, 0xCB, 0x97, 0xA9, 0x42, 0xE8, 0x9C, 0x71, 0x99, 0x94, 0x91, 0xE3,
0xAF, 0xAC,
];
let uuid_size = (8 + 16 + xmp_data.len()) as u32;
content.extend_from_slice(&uuid_size.to_be_bytes());
content.extend_from_slice(b"uuid");
content.extend_from_slice(&uuid_xmp);
content.extend_from_slice(xmp_data);
}
let size = (content.len() + 8) as u32;
let mut out = Vec::new();
out.extend_from_slice(&size.to_be_bytes());
out.extend_from_slice(b"moov");
out.extend_from_slice(&content);
out
}
pub fn tag_to_ilst_key(tag: &str) -> Option<[u8; 4]> {
Some(match tag.to_lowercase().as_str() {
"title" => [0xA9, b'n', b'a', b'm'],
"artist" => [0xA9, b'A', b'R', b'T'],
"album" => [0xA9, b'a', b'l', b'b'],
"year" | "date" => [0xA9, b'd', b'a', b'y'],
"comment" => [0xA9, b'c', b'm', b't'],
"genre" => [0xA9, b'g', b'e', b'n'],
"composer" | "writer" => [0xA9, b'w', b'r', b't'],
"encoder" | "encodedby" => [0xA9, b't', b'o', b'o'],
"grouping" => [0xA9, b'g', b'r', b'p'],
"lyrics" => [0xA9, b'l', b'y', b'r'],
"description" => [0xA9, b'd', b'e', b's'],
"albumartist" => [b'a', b'A', b'R', b'T'],
"copyright" => [b'c', b'p', b'r', b't'],
_ => return None,
})
}