use crate::{
boxes::{BoxRef, NodeKind},
parser::read_box_header,
registry::{BoxValue, Registry, default_registry},
util::{hex_dump, read_slice},
};
use byteorder::{BigEndian, ReadBytesExt};
use serde::Serialize;
use std::collections::HashMap;
use std::io::{Read, Seek, SeekFrom};
#[derive(Serialize)]
pub struct Box {
pub offset: u64,
pub size: u64,
pub header_size: u64,
pub payload_offset: Option<u64>,
pub payload_size: Option<u64>,
pub typ: String,
pub uuid: Option<String>,
pub version: Option<u8>,
pub flags: Option<u32>,
pub kind: String,
pub full_name: String,
pub decoded: Option<String>,
pub structured_data: Option<crate::registry::StructuredData>,
pub children: Option<Vec<Box>>,
}
pub fn get_boxes<R: Read + Seek>(r: &mut R, size: u64, decode: bool) -> anyhow::Result<Vec<Box>> {
let mut boxes = Vec::new();
while r.stream_position()? < size {
let h = read_box_header(r)?;
let box_end = if h.size == 0 { size } else { h.start + h.size };
let kind = if crate::known_boxes::KnownBox::from(h.typ).is_container() {
r.seek(SeekFrom::Start(h.start + h.header_size))?;
NodeKind::Container(crate::parser::parse_children(r, box_end)?)
} else if crate::known_boxes::KnownBox::from(h.typ).is_full_box() {
r.seek(SeekFrom::Start(h.start + h.header_size))?;
let version = r.read_u8()?;
let mut fl = [0u8; 3];
r.read_exact(&mut fl)?;
let flags = ((fl[0] as u32) << 16) | ((fl[1] as u32) << 8) | (fl[2] as u32);
let data_offset = r.stream_position()?;
let data_len = box_end.saturating_sub(data_offset);
NodeKind::FullBox {
version,
flags,
data_offset,
data_len,
}
} else {
let data_offset = h.start + h.header_size;
let data_len = box_end.saturating_sub(data_offset);
if &h.typ.0 == b"uuid" {
NodeKind::Unknown {
data_offset,
data_len,
}
} else {
NodeKind::Leaf {
data_offset,
data_len,
}
}
};
r.seek(SeekFrom::Start(box_end))?;
boxes.push(BoxRef { hdr: h, kind });
}
let reg = default_registry();
let json_boxes = boxes
.iter()
.map(|b| build_box(r, b, decode, ®))
.collect();
Ok(json_boxes)
}
fn payload_region(b: &BoxRef) -> Option<(crate::boxes::BoxKey, u64, u64)> {
let key = if &b.hdr.typ.0 == b"uuid" {
crate::boxes::BoxKey::Uuid(b.hdr.uuid.unwrap())
} else {
crate::boxes::BoxKey::FourCC(b.hdr.typ)
};
match &b.kind {
NodeKind::FullBox {
data_offset,
data_len,
..
} => Some((key, *data_offset, *data_len)),
NodeKind::Leaf { .. } | NodeKind::Unknown { .. } => {
let hdr = &b.hdr;
if hdr.size == 0 {
return None;
}
let off = hdr.start + hdr.header_size;
let len = hdr.size.saturating_sub(hdr.header_size);
if len == 0 {
return None;
}
Some((key, off, len))
}
NodeKind::Container(_) => None,
}
}
fn payload_geometry(b: &BoxRef) -> Option<(u64, u64)> {
match &b.kind {
NodeKind::FullBox {
data_offset,
data_len,
..
} => Some((*data_offset, *data_len)),
NodeKind::Leaf { .. } | NodeKind::Unknown { .. } => {
let hdr = &b.hdr;
if hdr.size == 0 {
return None;
}
let off = hdr.start + hdr.header_size;
let len = hdr.size.saturating_sub(hdr.header_size);
if len == 0 {
return None;
}
Some((off, len))
}
NodeKind::Container(_) => None,
}
}
fn decode_value<R: Read + Seek>(
r: &mut R,
b: &BoxRef,
reg: &Registry,
) -> (Option<String>, Option<crate::registry::StructuredData>) {
let (key, off, len) = match payload_region(b) {
Some(region) => region,
None => return (None, None),
};
if len == 0 {
return (None, None);
}
if r.seek(SeekFrom::Start(off)).is_err() {
return (None, None);
}
let mut limited = r.take(len);
let (version, flags) = match &b.kind {
crate::boxes::NodeKind::FullBox { version, flags, .. } => (Some(*version), Some(*flags)),
_ => (None, None),
};
if let Some(res) = reg.decode(&key, &mut limited, &b.hdr, version, flags) {
match res {
Ok(BoxValue::Text(s)) => (Some(s), None),
Ok(BoxValue::Bytes(bytes)) => (Some(format!("{} bytes", bytes.len())), None),
Ok(BoxValue::Structured(data)) => {
let debug_str = format!("structured: {:?}", data);
(Some(debug_str), Some(data))
}
Err(e) => (Some(format!("[decode error: {}]", e)), None),
}
} else {
(None, None)
}
}
fn build_box<R: Read + Seek>(r: &mut R, b: &BoxRef, decode: bool, reg: &Registry) -> Box {
let hdr = &b.hdr;
let uuid_str = hdr
.uuid
.map(|u| u.iter().map(|b| format!("{:02x}", b)).collect::<String>());
let kb = crate::known_boxes::KnownBox::from(hdr.typ);
let full_name = kb.full_name().to_string();
let header_size = hdr.header_size;
let (payload_offset, payload_size) = payload_geometry(b)
.map(|(off, len)| (Some(off), Some(len)))
.unwrap_or((None, None));
let (version, flags, kind_str, children) = match &b.kind {
NodeKind::FullBox { version, flags, .. } => {
(Some(*version), Some(*flags), "full".to_string(), None)
}
NodeKind::Leaf { .. } => (None, None, "leaf".to_string(), None),
NodeKind::Unknown { .. } => (None, None, "unknown".to_string(), None),
NodeKind::Container(kids) => {
let child_nodes = kids.iter().map(|c| build_box(r, c, decode, reg)).collect();
(None, None, "container".to_string(), Some(child_nodes))
}
};
let (decoded, structured_data) = if decode {
decode_value(r, b, reg)
} else {
(None, None)
};
Box {
offset: hdr.start,
size: hdr.size,
header_size,
payload_offset,
payload_size,
typ: hdr.typ.to_string(),
uuid: uuid_str,
version,
flags,
kind: kind_str,
full_name,
decoded,
structured_data,
children,
}
}
#[derive(Serialize)]
pub struct HexDump {
pub offset: u64,
pub length: u64,
pub hex: String,
}
pub fn hex_range<R: Read + Seek>(
r: &mut R,
size: u64,
offset: u64,
max_len: u64,
) -> anyhow::Result<HexDump> {
use std::cmp::min;
let available = size.saturating_sub(offset);
let to_read = min(available, max_len);
if to_read == 0 {
return Ok(HexDump {
offset,
length: 0,
hex: String::new(),
});
}
let data = read_slice(r, offset, to_read)?;
let hex_str = hex_dump(&data, offset);
Ok(HexDump {
offset,
length: to_read, hex: hex_str,
})
}
pub fn get_itunes_tags<R: Read + Seek>(
r: &mut R,
size: u64,
) -> anyhow::Result<HashMap<String, String>> {
let moov = match find_box_in_range(r, 0, size, b"moov")? {
Some(h) => h,
None => return Ok(HashMap::new()),
};
let moov_content = moov.start + moov.header_size;
let moov_end = moov.start + moov.size;
let udta = match find_box_in_range(r, moov_content, moov_end, b"udta")? {
Some(h) => h,
None => return Ok(HashMap::new()),
};
let udta_content = udta.start + udta.header_size;
let udta_end = udta.start + udta.size;
let meta = match find_box_in_range(r, udta_content, udta_end, b"meta")? {
Some(h) => h,
None => return Ok(HashMap::new()),
};
let meta_end = meta.start + meta.size;
let meta_content = meta.start + meta.header_size + 4;
let ilst = match find_box_in_range(r, meta_content, meta_end, b"ilst")? {
Some(h) => h,
None => return Ok(HashMap::new()),
};
let ilst_content = ilst.start + ilst.header_size;
let ilst_end = ilst.start + ilst.size;
let mut tags = HashMap::new();
r.seek(SeekFrom::Start(ilst_content))?;
while r.stream_position()? + 8 <= ilst_end {
let tag_hdr = read_box_header(r)?;
let tag_end = if tag_hdr.size == 0 {
ilst_end
} else {
tag_hdr.start + tag_hdr.size
};
if let Some(key) = fourcc_to_tag_key(&tag_hdr.typ.0) {
let tag_content = tag_hdr.start + tag_hdr.header_size;
if let Some(value) = extract_ilst_data_value(r, tag_content, tag_end)? {
tags.insert(key.to_string(), value);
}
}
r.seek(SeekFrom::Start(tag_end))?;
}
Ok(tags)
}
fn find_box_in_range<R: Read + Seek>(
r: &mut R,
start: u64,
end: u64,
target: &[u8; 4],
) -> anyhow::Result<Option<crate::boxes::BoxHeader>> {
r.seek(SeekFrom::Start(start))?;
while r.stream_position()? + 8 <= end {
let h = read_box_header(r)?;
let box_end = if h.size == 0 { end } else { h.start + h.size };
if &h.typ.0 == target {
return Ok(Some(h));
}
r.seek(SeekFrom::Start(box_end))?;
}
Ok(None)
}
fn extract_ilst_data_value<R: Read + Seek>(
r: &mut R,
start: u64,
end: u64,
) -> anyhow::Result<Option<String>> {
r.seek(SeekFrom::Start(start))?;
while r.stream_position()? + 8 <= end {
let h = read_box_header(r)?;
let box_end = if h.size == 0 { end } else { h.start + h.size };
if &h.typ.0 == b"data" {
let _version = r.read_u8()?;
let mut fl = [0u8; 3];
r.read_exact(&mut fl)?;
let type_code = ((fl[0] as u32) << 16) | ((fl[1] as u32) << 8) | (fl[2] as u32);
r.read_u32::<BigEndian>()?;
let data_start = r.stream_position()?;
let data_len = box_end.saturating_sub(data_start) as usize;
if type_code == 1 && data_len > 0 {
let mut buf = vec![0u8; data_len];
r.read_exact(&mut buf)?;
let s = String::from_utf8_lossy(&buf).trim().to_string();
if !s.is_empty() {
return Ok(Some(s));
}
}
return Ok(None);
}
r.seek(SeekFrom::Start(box_end))?;
}
Ok(None)
}
fn fourcc_to_tag_key(fourcc: &[u8; 4]) -> Option<&'static str> {
match fourcc {
b"\xa9nam" => Some("title"),
b"\xa9ART" => Some("artist"),
b"\xa9alb" => Some("album"),
b"\xa9day" => Some("year"),
b"\xa9gen" => Some("genre"),
b"\xa9cmt" => Some("comment"),
b"\xa9des" => Some("description"),
b"desc" => Some("description"),
b"cprt" => Some("copyright"),
b"aART" => Some("album_artist"),
_ => None,
}
}