use crate::error::{Container, Error, Result};
use crate::mkv::attachments::MAX_ATTACHMENT_FIELD;
use crate::mkv::ebml_write::{ElementBuf, push_id, push_size};
use crate::mkv::ids;
use crate::mkv::mux::bytes::{derive_uid, invalid};
use crate::mkv::mux::options::DocType;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AttachmentIn<'a> {
pub name: &'a str,
pub mime: &'a str,
pub description: Option<&'a str>,
pub uid: Option<u64>,
pub data: &'a [u8],
}
#[derive(Debug)]
pub(crate) struct AttachedFile {
pub uid: u64,
head: Vec<u8>,
data: Vec<u8>,
tail: Vec<u8>,
}
impl AttachedFile {
pub fn new(file: &AttachmentIn<'_>, doc_type: DocType, taken: &[u64]) -> Result<Self> {
if doc_type == DocType::WebM {
return Err(Error::Unsupported {
container: Container::WebM,
what: format!("attachment `{}`: WebM has no attachments", file.name),
use_instead: Container::Matroska,
});
}
let description = file.description.unwrap_or_default();
for (field, value) in [
("name", file.name),
("mime", file.mime),
("description", description),
] {
if value.len() as u64 > MAX_ATTACHMENT_FIELD {
return Err(invalid(format!(
"`AttachmentIn::{field}` holds {} bytes, above the limit of \
{MAX_ATTACHMENT_FIELD}. Use a shorter text",
value.len()
)));
}
}
let uid = match file.uid {
Some(0) => {
return Err(invalid(format!(
"attachment `{}` has `FileUID` 0. Use a non-zero UID, or `None`",
file.name
)));
}
Some(uid) if taken.contains(&uid) => {
return Err(invalid(format!(
"attachment `{}` repeats `FileUID` {uid}. Give each attachment its own UID",
file.name
)));
}
Some(uid) => uid,
None => derive_uid(file.name.as_bytes(), taken.len() as u64, |uid| {
taken.contains(&uid)
}),
};
let mut fields = ElementBuf::new();
if let Some(description) = file.description {
fields.text(ids::FILE_DESCRIPTION, description)?;
}
fields.text(ids::FILE_NAME, file.name)?;
fields.text(ids::FILE_MEDIA_TYPE, file.mime)?;
let mut data_head = Vec::with_capacity(11);
push_id(&mut data_head, ids::FILE_DATA);
push_size(&mut data_head, ids::FILE_DATA, file.data.len() as u64)?;
let mut tail = ElementBuf::new();
tail.uint(ids::FILE_UID, uid)?;
let total = fields.as_bytes().len() as u64
+ data_head.len() as u64
+ file.data.len() as u64
+ tail.as_bytes().len() as u64;
let mut head = Vec::with_capacity(fields.as_bytes().len() + data_head.len() + 12);
push_id(&mut head, ids::ATTACHED_FILE);
push_size(&mut head, ids::ATTACHED_FILE, total)?;
head.extend_from_slice(fields.as_bytes());
head.extend_from_slice(&data_head);
Ok(Self {
uid,
head,
data: file.data.to_vec(),
tail: tail.into_bytes(),
})
}
pub fn parts(&self) -> [&[u8]; 3] {
[
self.head.as_slice(),
self.data.as_slice(),
self.tail.as_slice(),
]
}
pub fn len(&self) -> u64 {
self.parts().iter().map(|part| part.len() as u64).sum()
}
}
pub(crate) fn attachments_header(files: &[AttachedFile]) -> Result<Vec<u8>> {
let total: u64 = files.iter().map(AttachedFile::len).sum();
let mut out = Vec::with_capacity(12);
push_id(&mut out, ids::ATTACHMENTS);
push_size(&mut out, ids::ATTACHMENTS, total)?;
Ok(out)
}