use oxideav_prores::frame::{
parse_frame_header, write_frame, write_frame_with_alpha, ChromaFormat,
};
fn build_frame_header(
bitstream_version: u8,
chroma_code: u8,
interlace_mode: u8,
alpha_channel_type: u8,
) -> Vec<u8> {
let mut h = Vec::with_capacity(20);
h.extend_from_slice(&20u16.to_be_bytes()); h.push(0); h.push(bitstream_version);
h.extend_from_slice(b"Lavc"); h.extend_from_slice(&64u16.to_be_bytes()); h.extend_from_slice(&48u16.to_be_bytes()); h.push(((chroma_code & 0x3) << 6) | ((interlace_mode & 0x3) << 2));
h.push(0); h.push(0); h.push(0); h.push(0); h.push(alpha_channel_type & 0x0F); h.push(0); h.push(0); debug_assert_eq!(h.len(), 20);
h
}
#[test]
fn rejects_bitstream_version_above_one() {
for v in [2u8, 5, 255] {
let h = build_frame_header(v, 2, 0, 0);
let err = parse_frame_header(&h).expect_err("must reject");
assert!(
format!("{err:?}").contains("bitstream_version"),
"error for v={v} mentions bitstream_version: {err:?}"
);
}
}
#[test]
fn accepts_bitstream_version_zero_with_422_no_alpha() {
let h = build_frame_header(0, 2, 0, 0);
let (fh, _) = parse_frame_header(&h).expect("must accept");
assert_eq!(fh.bitstream_version, 0);
assert_eq!(fh.chroma_format, ChromaFormat::Y422);
assert_eq!(fh.alpha_channel_type, 0);
}
#[test]
fn rejects_version_zero_with_444_chroma() {
let h = build_frame_header(0, 3, 0, 0);
let err = parse_frame_header(&h).expect_err("must reject");
let msg = format!("{err:?}");
assert!(
msg.contains("bitstream_version 0") && msg.contains("chroma_format"),
"error mentions both v0 + chroma_format: {msg}"
);
}
#[test]
fn rejects_version_zero_with_alpha_8bit() {
let h = build_frame_header(0, 2, 0, 1);
let err = parse_frame_header(&h).expect_err("must reject");
let msg = format!("{err:?}");
assert!(
msg.contains("bitstream_version 0") && msg.contains("alpha_channel_type"),
"error mentions both v0 + alpha_channel_type: {msg}"
);
}
#[test]
fn rejects_version_zero_with_alpha_16bit() {
let h = build_frame_header(0, 2, 0, 2);
let err = parse_frame_header(&h).expect_err("must reject");
let msg = format!("{err:?}");
assert!(
msg.contains("bitstream_version 0") && msg.contains("alpha_channel_type"),
"error mentions both v0 + alpha_channel_type: {msg}"
);
}
#[test]
fn accepts_version_one_with_444_chroma() {
let h = build_frame_header(1, 3, 0, 0);
let (fh, _) = parse_frame_header(&h).expect("must accept");
assert_eq!(fh.bitstream_version, 1);
assert_eq!(fh.chroma_format, ChromaFormat::Y444);
assert_eq!(fh.alpha_channel_type, 0);
}
#[test]
fn accepts_version_one_with_alpha_on_422() {
let h = build_frame_header(1, 2, 0, 1);
let (fh, _) = parse_frame_header(&h).expect("must accept");
assert_eq!(fh.bitstream_version, 1);
assert_eq!(fh.chroma_format, ChromaFormat::Y422);
assert_eq!(fh.alpha_channel_type, 1);
}
#[test]
fn rejects_interlace_mode_three_reserved() {
let h = build_frame_header(1, 2, 3, 0);
let err = parse_frame_header(&h).expect_err("must reject");
let msg = format!("{err:?}");
assert!(
msg.contains("interlace_mode 3"),
"error mentions reserved interlace_mode 3: {msg}"
);
}
#[test]
fn accepts_interlace_mode_zero_one_two() {
for im in [0u8, 1, 2] {
let h = build_frame_header(1, 2, im, 0);
let (fh, _) = parse_frame_header(&h).expect("must accept");
assert_eq!(fh.interlace_mode, im, "interlace_mode {im}");
}
}
#[test]
fn rejects_luma_qmat_entry_below_two() {
let mut h = build_frame_header(1, 2, 0, 0);
h[0..2].copy_from_slice(&84u16.to_be_bytes());
h[19] = 0b10; let mut bad = [4u8; 64];
bad[17] = 1; h.extend_from_slice(&bad);
let err = parse_frame_header(&h).expect_err("must reject");
let msg = format!("{err:?}");
assert!(
msg.contains("luma_quantization_matrix") && msg.contains("2..=63"),
"error mentions luma qmat range: {msg}"
);
}
#[test]
fn rejects_luma_qmat_entry_above_63() {
let mut h = build_frame_header(1, 2, 0, 0);
h[0..2].copy_from_slice(&84u16.to_be_bytes());
h[19] = 0b10;
let mut bad = [4u8; 64];
bad[0] = 64; h.extend_from_slice(&bad);
let err = parse_frame_header(&h).expect_err("must reject");
let msg = format!("{err:?}");
assert!(
msg.contains("luma_quantization_matrix") && msg.contains("2..=63"),
"error mentions luma qmat range: {msg}"
);
}
#[test]
fn rejects_chroma_qmat_entry_out_of_range() {
let mut h = build_frame_header(1, 3, 0, 0);
h[0..2].copy_from_slice(&148u16.to_be_bytes()); h[19] = 0b11;
let luma = [4u8; 64];
h.extend_from_slice(&luma);
let mut chroma = [4u8; 64];
chroma[42] = 0;
h.extend_from_slice(&chroma);
let err = parse_frame_header(&h).expect_err("must reject");
let msg = format!("{err:?}");
assert!(
msg.contains("chroma_quantization_matrix") && msg.contains("2..=63"),
"error mentions chroma qmat range: {msg}"
);
}
#[test]
fn accepts_qmats_with_spec_endpoints() {
let mut h = build_frame_header(1, 2, 0, 0);
h[0..2].copy_from_slice(&84u16.to_be_bytes());
h[19] = 0b10;
let mut q = [4u8; 64];
q[0] = 2;
q[63] = 63;
h.extend_from_slice(&q);
let (fh, _) = parse_frame_header(&h).expect("must accept");
assert_eq!(fh.luma_qmat[0], 2);
assert_eq!(fh.luma_qmat[63], 63);
assert_eq!(fh.chroma_qmat, fh.luma_qmat);
}
#[test]
fn encoder_round_trips_v0_compatibility() {
let luma = [4u8; 64];
let chroma = [4u8; 64];
let mut buf = Vec::new();
write_frame(
&mut buf,
28,
128,
128,
ChromaFormat::Y422,
0,
&luma,
&chroma,
false,
false,
);
let (fh, _) = parse_frame_header(&buf[8..]).expect("must accept own output");
assert_eq!(
fh.bitstream_version, 0,
"encoder must pick v0 for 4:2:2 no-alpha"
);
}
#[test]
fn encoder_round_trips_v1_compatibility() {
let luma = [4u8; 64];
let chroma = [4u8; 64];
let mut buf444 = Vec::new();
write_frame(
&mut buf444,
28,
64,
64,
ChromaFormat::Y444,
0,
&luma,
&chroma,
false,
false,
);
let (fh, _) = parse_frame_header(&buf444[8..]).unwrap();
assert_eq!(fh.bitstream_version, 1, "4:4:4 forces v1");
let mut buf_alpha = Vec::new();
write_frame_with_alpha(
&mut buf_alpha,
28,
64,
64,
ChromaFormat::Y422,
0,
&luma,
&chroma,
false,
false,
1, );
let (fh, _) = parse_frame_header(&buf_alpha[8..]).unwrap();
assert_eq!(fh.bitstream_version, 1, "alpha forces v1");
}
#[test]
fn rejects_frame_size_below_eight_bytes() {
use oxideav_prores::frame::parse_frame;
for fs in 0u32..=7 {
let mut buf = Vec::with_capacity(32);
buf.extend_from_slice(&fs.to_be_bytes());
buf.extend_from_slice(b"icpf");
buf.extend_from_slice(&[0u8; 24]);
let err = parse_frame(&buf).expect_err("must reject fs < 8");
let msg = format!("{err:?}");
assert!(
msg.contains("frame_size") || msg.contains("8-byte"),
"fs={fs}: error mentions frame_size or 8-byte prefix: {msg}"
);
}
}