#[derive(Default)]
pub(super) struct RawColourFix {
pub(super) max_cll: Option<u32>,
pub(super) max_fall: Option<u32>,
pub(super) primary_r_chromaticity_y: Option<f64>,
pub(super) primary_g_chromaticity_y: Option<f64>,
pub(super) primary_b_chromaticity_y: Option<f64>,
}
pub(super) fn scan_mkv_colour_raw(data: &[u8]) -> Option<RawColourFix> {
let mut cursor = 0;
let seg_body: &[u8] = loop {
let (el, after) = next_ebml_element(data, cursor)?;
if el.id == 0x18538067 {
break &data[el.body_start..el.body_start + el.body_len];
}
cursor = after;
};
let tracks = find_ebml_child(seg_body, 0x1654AE6B)?;
let mut cur = 0;
while cur < tracks.len() {
let (el, after) = next_ebml_element(tracks, cur)?;
cur = after;
if el.id != 0xAE {
continue;
}
let entry = &tracks[el.body_start..el.body_start + el.body_len];
let Some(video) = find_ebml_child(entry, 0xE0) else {
continue;
};
let Some(colour) = find_ebml_child(video, 0x55B0) else {
continue;
};
let mut fix = RawColourFix::default();
let mut c = 0;
while c < colour.len() {
let (ce, after_ce) = match next_ebml_element(colour, c) {
Some(v) => v,
None => break,
};
c = after_ce;
let value_bytes = &colour[ce.body_start..ce.body_start + ce.body_len];
match ce.id {
0x55BC => {
fix.max_cll = read_unsigned(value_bytes).and_then(|v| u32::try_from(v).ok());
}
0x55BD => {
fix.max_fall = read_unsigned(value_bytes).and_then(|v| u32::try_from(v).ok());
}
0x55D0 => {
let md = value_bytes;
let mut mc = 0;
while mc < md.len() {
let (mce, after_mce) = match next_ebml_element(md, mc) {
Some(v) => v,
None => break,
};
mc = after_mce;
let mv = &md[mce.body_start..mce.body_start + mce.body_len];
match mce.id {
0x55D2 => fix.primary_r_chromaticity_y = read_float(mv),
0x55D4 => fix.primary_g_chromaticity_y = read_float(mv),
0x55D6 => fix.primary_b_chromaticity_y = read_float(mv),
_ => {}
}
}
}
_ => {}
}
}
if fix.max_cll.is_some()
|| fix.max_fall.is_some()
|| fix.primary_r_chromaticity_y.is_some()
|| fix.primary_g_chromaticity_y.is_some()
|| fix.primary_b_chromaticity_y.is_some()
{
return Some(fix);
}
}
None
}
fn find_ebml_child(buf: &[u8], want: u32) -> Option<&[u8]> {
let mut cur = 0;
while cur < buf.len() {
let (el, after) = next_ebml_element(buf, cur)?;
cur = after;
if el.id == want {
return Some(&buf[el.body_start..el.body_start + el.body_len]);
}
}
None
}
#[derive(Debug)]
struct RawEbmlElement {
id: u32,
body_start: usize,
body_len: usize,
}
fn next_ebml_element(buf: &[u8], off: usize) -> Option<(RawEbmlElement, usize)> {
if off >= buf.len() {
return None;
}
let (id, id_len) = read_id_vint(&buf[off..])?;
let body_off = off + id_len;
if body_off >= buf.len() {
return None;
}
let (size, size_len) = read_size_vint(&buf[body_off..])?;
let body_start = body_off + size_len;
if body_start + size as usize > buf.len() {
return None;
}
let elem = RawEbmlElement {
id,
body_start,
body_len: size as usize,
};
Some((elem, body_start + size as usize))
}
pub(crate) fn read_id_vint(buf: &[u8]) -> Option<(u32, usize)> {
if buf.is_empty() {
return None;
}
let first = buf[0];
let len = if first & 0x80 != 0 {
1
} else if first & 0x40 != 0 {
2
} else if first & 0x20 != 0 {
3
} else if first & 0x10 != 0 {
4
} else {
return None;
};
if buf.len() < len {
return None;
}
let mut id: u32 = 0;
for b in &buf[..len] {
id = (id << 8) | (*b as u32);
}
Some((id, len))
}
pub(crate) fn read_size_vint(buf: &[u8]) -> Option<(u64, usize)> {
if buf.is_empty() {
return None;
}
let first = buf[0];
if first == 0 {
return None;
}
let len = first.leading_zeros() as usize + 1;
if len > 8 || buf.len() < len {
return None;
}
let mask: u8 = if len == 8 { 0 } else { 0xFFu8 >> len };
let mut v: u64 = (first & mask) as u64;
for b in &buf[1..len] {
v = (v << 8) | (*b as u64);
}
Some((v, len))
}
fn read_unsigned(buf: &[u8]) -> Option<u64> {
if buf.len() > 8 {
return None;
}
let mut v: u64 = 0;
for b in buf {
v = (v << 8) | (*b as u64);
}
Some(v)
}
fn read_float(buf: &[u8]) -> Option<f64> {
match buf.len() {
4 => {
let arr: [u8; 4] = buf.try_into().ok()?;
Some(f32::from_be_bytes(arr) as f64)
}
8 => {
let arr: [u8; 8] = buf.try_into().ok()?;
Some(f64::from_be_bytes(arr))
}
_ => None,
}
}