use gamut_core::{Error, Result};
use crate::boxes::BoxReader;
use crate::model::{ColourInformation, IsoBmffImage, Item, NclxColr, Property, PropertyKind};
type ItemAssociations = Vec<(u16, Vec<(u16, bool)>)>;
pub fn read(data: &[u8]) -> Result<IsoBmffImage> {
let mut top = BoxReader::new(data);
let mut ftyp = None;
let mut meta_body = None;
let mut saw_mdat = false;
while let Some(b) = top.next_box()? {
match &b.ty {
b"ftyp" => ftyp = Some(parse_ftyp(b.body)?),
b"meta" => meta_body = Some(b.body),
b"mdat" => saw_mdat = true,
b"moov" | b"trak" => {
return Err(Error::Unsupported(
"ISOBMFF: image sequences (tracks) not supported",
));
}
_ => {} }
}
let (major_brand, minor_version, compatible_brands) =
ftyp.ok_or(Error::InvalidInput("ISOBMFF: missing ftyp"))?;
let meta_body = meta_body.ok_or(Error::InvalidInput("ISOBMFF: missing meta"))?;
if !saw_mdat {
return Err(Error::InvalidInput("ISOBMFF: missing mdat"));
}
let meta = parse_meta(meta_body)?;
let mut items = Vec::with_capacity(meta.infe.len());
for infe in &meta.infe {
let loc = meta
.iloc
.iter()
.find(|e| e.id == infe.id)
.ok_or(Error::InvalidInput("ISOBMFF: iloc missing item"))?;
let start = loc.offset as usize;
let end = start
.checked_add(loc.length as usize)
.ok_or(Error::InvalidInput("ISOBMFF: iloc extent overflow"))?;
let payload = data
.get(start..end)
.ok_or(Error::InvalidInput("ISOBMFF: iloc extent out of bounds"))?
.to_vec();
let assoc = meta
.ipma
.iter()
.find(|(id, _)| *id == infe.id)
.map(|(_, row)| row)
.ok_or(Error::InvalidInput("ISOBMFF: ipma missing item"))?;
let mut properties = Vec::with_capacity(assoc.len());
for &(index, essential) in assoc {
let i = usize::from(index);
if index == 0 || i > meta.ipco.len() {
return Err(Error::InvalidInput(
"ISOBMFF: ipma property index out of range",
));
}
properties.push(Property {
essential,
kind: meta.ipco[i - 1].clone(),
});
}
items.push(Item {
id: infe.id,
item_type: infe.item_type,
name: infe.name.clone(),
properties,
payload,
});
}
Ok(IsoBmffImage {
major_brand,
minor_version,
compatible_brands,
primary_item_id: meta.primary_item_id,
items,
})
}
fn parse_ftyp(body: &[u8]) -> Result<([u8; 4], u32, Vec<[u8; 4]>)> {
let mut r = BoxReader::new(body);
let major = r.fourcc()?;
let minor = r.u32()?;
let mut compatible = Vec::with_capacity(r.remaining() / 4);
while r.remaining() >= 4 {
compatible.push(r.fourcc()?);
}
if r.remaining() != 0 {
return Err(Error::InvalidInput("ISOBMFF: ftyp has trailing bytes"));
}
Ok((major, minor, compatible))
}
struct Meta {
primary_item_id: u16,
iloc: Vec<IlocEntry>,
infe: Vec<InfeEntry>,
ipco: Vec<PropertyKind>,
ipma: ItemAssociations,
}
struct IlocEntry {
id: u16,
offset: u32,
length: u32,
}
struct InfeEntry {
id: u16,
item_type: [u8; 4],
name: String,
}
fn parse_meta(body: &[u8]) -> Result<Meta> {
let mut r = BoxReader::new(body);
full_box_header(&mut r)?;
let mut primary_item_id = None;
let mut iloc = None;
let mut infe = None;
let mut iprp = None;
while let Some(b) = r.next_box()? {
match &b.ty {
b"hdlr" => parse_hdlr(b.body)?,
b"pitm" => primary_item_id = Some(parse_pitm(b.body)?),
b"iloc" => iloc = Some(parse_iloc(b.body)?),
b"iinf" => infe = Some(parse_iinf(b.body)?),
b"iprp" => iprp = Some(parse_iprp(b.body)?),
_ => {} }
}
let (ipco, ipma) = iprp.ok_or(Error::InvalidInput("ISOBMFF: meta missing iprp"))?;
Ok(Meta {
primary_item_id: primary_item_id
.ok_or(Error::InvalidInput("ISOBMFF: meta missing pitm"))?,
iloc: iloc.ok_or(Error::InvalidInput("ISOBMFF: meta missing iloc"))?,
infe: infe.ok_or(Error::InvalidInput("ISOBMFF: meta missing iinf"))?,
ipco,
ipma,
})
}
fn full_box_header(r: &mut BoxReader) -> Result<u8> {
let version = r.u8()?;
r.take(3)?; Ok(version)
}
fn parse_hdlr(body: &[u8]) -> Result<()> {
let mut r = BoxReader::new(body);
full_box_header(&mut r)?;
let _pre_defined = r.u32()?;
let handler = r.fourcc()?;
if &handler != b"pict" {
return Err(Error::Unsupported("ISOBMFF: non-picture handler"));
}
Ok(())
}
fn parse_pitm(body: &[u8]) -> Result<u16> {
let mut r = BoxReader::new(body);
if full_box_header(&mut r)? != 0 {
return Err(Error::Unsupported(
"ISOBMFF: pitm version (only v0 supported)",
));
}
r.u16()
}
fn parse_iloc(body: &[u8]) -> Result<Vec<IlocEntry>> {
let mut r = BoxReader::new(body);
if full_box_header(&mut r)? != 0 {
return Err(Error::Unsupported(
"ISOBMFF: iloc version (only v0 supported)",
));
}
let sizes = r.u8()?;
if sizes != 0x44 {
return Err(Error::Unsupported(
"ISOBMFF: iloc offset_size/length_size != 4",
));
}
let base = r.u8()?;
if base & 0xf0 != 0 {
return Err(Error::Unsupported("ISOBMFF: iloc base_offset_size != 0"));
}
let item_count = r.u16()?;
let mut entries = Vec::new();
for _ in 0..item_count {
let id = r.u16()?;
let _data_reference_index = r.u16()?;
let extent_count = r.u16()?;
if extent_count != 1 {
return Err(Error::Unsupported("ISOBMFF: iloc multiple extents"));
}
let offset = r.u32()?;
let length = r.u32()?;
entries.push(IlocEntry { id, offset, length });
}
Ok(entries)
}
fn parse_iinf(body: &[u8]) -> Result<Vec<InfeEntry>> {
let mut r = BoxReader::new(body);
if full_box_header(&mut r)? != 0 {
return Err(Error::Unsupported(
"ISOBMFF: iinf version (only v0 supported)",
));
}
let entry_count = r.u16()?;
let mut entries = Vec::new();
for _ in 0..entry_count {
let b = r
.next_box()?
.ok_or(Error::InvalidInput("ISOBMFF: iinf truncated"))?;
if &b.ty != b"infe" {
return Err(Error::InvalidInput("ISOBMFF: iinf child is not infe"));
}
entries.push(parse_infe(b.body)?);
}
Ok(entries)
}
fn parse_infe(body: &[u8]) -> Result<InfeEntry> {
let mut r = BoxReader::new(body);
if full_box_header(&mut r)? != 2 {
return Err(Error::Unsupported(
"ISOBMFF: infe version (only v2 supported)",
));
}
let id = r.u16()?;
let _item_protection_index = r.u16()?;
let item_type = r.fourcc()?;
let name = read_c_string(&mut r)?;
Ok(InfeEntry {
id,
item_type,
name,
})
}
fn read_c_string(r: &mut BoxReader) -> Result<String> {
let mut bytes = Vec::new();
while r.remaining() != 0 {
let b = r.u8()?;
if b == 0 {
break;
}
bytes.push(b);
}
String::from_utf8(bytes).map_err(|_| Error::InvalidInput("ISOBMFF: infe name not UTF-8"))
}
fn parse_iprp(body: &[u8]) -> Result<(Vec<PropertyKind>, ItemAssociations)> {
let mut r = BoxReader::new(body);
let mut ipco = None;
let mut ipma = None;
while let Some(b) = r.next_box()? {
match &b.ty {
b"ipco" => ipco = Some(parse_ipco(b.body)?),
b"ipma" => ipma = Some(parse_ipma(b.body)?),
_ => {}
}
}
let ipco = ipco.ok_or(Error::InvalidInput("ISOBMFF: iprp missing ipco"))?;
let ipma = ipma.ok_or(Error::InvalidInput("ISOBMFF: iprp missing ipma"))?;
Ok((ipco, ipma))
}
fn parse_ipco(body: &[u8]) -> Result<Vec<PropertyKind>> {
let mut r = BoxReader::new(body);
let mut props = Vec::new();
while let Some(b) = r.next_box()? {
props.push(parse_property(b.ty, b.body)?);
}
Ok(props)
}
fn parse_property(ty: [u8; 4], body: &[u8]) -> Result<PropertyKind> {
match &ty {
b"ispe" => {
let mut r = BoxReader::new(body);
full_box_header(&mut r)?;
let width = r.u32()?;
let height = r.u32()?;
Ok(PropertyKind::ImageSpatialExtents { width, height })
}
b"pixi" => {
let mut r = BoxReader::new(body);
full_box_header(&mut r)?;
let count = r.u8()?;
let mut bits_per_channel = Vec::new();
for _ in 0..count {
bits_per_channel.push(r.u8()?);
}
Ok(PropertyKind::PixelInformation { bits_per_channel })
}
b"colr" => {
let mut r = BoxReader::new(body);
let colour_type = r.fourcc()?;
if &colour_type == b"nclx" {
let colour_primaries = r.u16()?;
let transfer_characteristics = r.u16()?;
let matrix_coefficients = r.u16()?;
let full_range = (r.u8()? >> 7) & 1 == 1;
Ok(PropertyKind::Colour(ColourInformation::Nclx(NclxColr {
colour_primaries,
transfer_characteristics,
matrix_coefficients,
full_range,
})))
} else {
Ok(PropertyKind::Other {
kind: ty,
data: body.to_vec(),
})
}
}
b"irot" => {
let mut r = BoxReader::new(body);
Ok(PropertyKind::Rotation(r.u8()? & 0x03))
}
b"imir" => {
let mut r = BoxReader::new(body);
Ok(PropertyKind::Mirror(r.u8()? & 0x01))
}
b"av1C" => Ok(PropertyKind::CodecConfiguration {
kind: ty,
data: body.to_vec(),
}),
_ => Ok(PropertyKind::Other {
kind: ty,
data: body.to_vec(),
}),
}
}
fn parse_ipma(body: &[u8]) -> Result<ItemAssociations> {
let mut r = BoxReader::new(body);
let version = r.u8()?;
let _flags_hi = r.take(2)?;
let flags_lo = r.u8()?;
if version != 0 {
return Err(Error::Unsupported(
"ISOBMFF: ipma version (only v0 supported)",
));
}
if flags_lo & 1 == 1 {
return Err(Error::Unsupported(
"ISOBMFF: ipma 16-bit property indices (flags & 1)",
));
}
let entry_count = r.u32()?;
let mut out = Vec::new();
for _ in 0..entry_count {
let item_id = r.u16()?;
let assoc_count = r.u8()?;
let mut row = Vec::new();
for _ in 0..assoc_count {
let byte = r.u8()?;
row.push((u16::from(byte & 0x7f), (byte & 0x80) != 0));
}
out.push((item_id, row));
}
Ok(out)
}