use crate::blob::{decode_blobs, encode_blobs, BlobKind, MetadataBlob};
use crate::layer::LayerIdentity;
use crate::signal::ColorSignal;
use crate::subtitle::SubtitleCue;
use crate::vector::VectorFrame;
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Frame {
Audio(AudioFrame),
Video(VideoFrame),
Subtitle(SubtitleCue),
Vector(VectorFrame),
}
impl Frame {
pub fn pts(&self) -> Option<i64> {
match self {
Self::Audio(a) => a.pts,
Self::Video(v) => v.pts,
Self::Subtitle(s) => Some(s.start_us),
Self::Vector(v) => v.pts,
}
}
}
#[derive(Clone, Debug)]
pub struct AudioFrame {
pub samples: u32,
pub pts: Option<i64>,
pub data: Vec<Vec<u8>>,
}
#[derive(Clone, Debug)]
pub struct VideoFrame {
pub pts: Option<i64>,
pub planes: Vec<VideoPlane>,
}
const SIGNIFICANT_BITS_STRIDE: usize = usize::MAX;
const COLOR_SIGNAL_STRIDE: usize = usize::MAX - 1;
const LAYER_IDENTITY_STRIDE: usize = usize::MAX - 2;
const PALETTE_ALPHA_STRIDE: usize = usize::MAX - 3;
const BLOBS_STRIDE: usize = usize::MAX - 4;
const SIDE_CHANNEL_STRIDE_FLOOR: usize = isize::MAX as usize + 1;
impl VideoFrame {
fn is_side_channel_entry(plane: &VideoPlane) -> bool {
(plane.stride == 0 || plane.stride >= SIDE_CHANNEL_STRIDE_FLOOR) && !plane.data.is_empty()
}
fn side_channel_run_start(&self) -> usize {
let mut start = self.planes.len();
while start > 0 && Self::is_side_channel_entry(&self.planes[start - 1]) {
start -= 1;
}
start
}
fn side_channel_index(&self, stride_tag: usize) -> Option<usize> {
let start = self.side_channel_run_start();
self.planes[start..]
.iter()
.rposition(|p| p.stride == stride_tag)
.map(|i| start + i)
}
fn remove_side_channel(&mut self, stride_tag: usize) -> Option<Vec<u8>> {
let reported = self
.side_channel_index(stride_tag)
.map(|i| self.planes.remove(i).data);
while let Some(i) = self.side_channel_index(stride_tag) {
self.planes.remove(i);
}
reported
}
pub fn palette(&self) -> Option<&[u8]> {
self.side_channel_index(0)
.map(|i| self.planes[i].data.as_slice())
}
pub fn palette_rgb(&self, index: u8) -> Option<[u8; 3]> {
let pal = self.palette()?;
let at = usize::from(index) * 3;
let entry = pal.get(at..at + 3)?;
Some([entry[0], entry[1], entry[2]])
}
pub fn set_palette(&mut self, rgb: Vec<u8>) {
self.remove_side_channel(0);
if !rgb.is_empty() {
self.planes.push(VideoPlane {
stride: 0,
data: rgb,
});
}
}
pub fn with_palette(mut self, rgb: Vec<u8>) -> Self {
self.set_palette(rgb);
self
}
pub fn take_palette(&mut self) -> Option<Vec<u8>> {
self.remove_side_channel(0)
}
fn palette_entry_count(&self) -> usize {
self.palette().map_or(0, |p| p.len() / 3)
}
pub fn palette_alpha(&self) -> Option<&[u8]> {
let i = self.side_channel_index(PALETTE_ALPHA_STRIDE)?;
let alpha = self.planes[i].data.as_slice();
let entries = self.palette_entry_count();
(entries > 0 && alpha.len() <= entries).then_some(alpha)
}
pub fn palette_rgba(&self, index: u8) -> Option<[u8; 4]> {
let [r, g, b] = self.palette_rgb(index)?;
let a = self
.palette_alpha()
.and_then(|a| a.get(usize::from(index)).copied())
.unwrap_or(u8::MAX);
Some([r, g, b, a])
}
pub fn set_palette_alpha(&mut self, alpha: Vec<u8>) {
self.remove_side_channel(PALETTE_ALPHA_STRIDE);
if !alpha.is_empty() {
self.planes.push(VideoPlane {
stride: PALETTE_ALPHA_STRIDE,
data: alpha,
});
}
}
pub fn with_palette_alpha(mut self, alpha: Vec<u8>) -> Self {
self.set_palette_alpha(alpha);
self
}
pub fn take_palette_alpha(&mut self) -> Option<Vec<u8>> {
let valid = self.palette_alpha().is_some();
self.remove_side_channel(PALETTE_ALPHA_STRIDE)
.filter(|_| valid)
}
pub fn set_palette_rgba(&mut self, rgba: &[[u8; 4]]) {
let rgb = rgba.iter().flat_map(|e| [e[0], e[1], e[2]]).collect();
let alpha = rgba.iter().map(|e| e[3]).collect();
self.set_palette(rgb);
self.set_palette_alpha(alpha);
}
pub fn with_palette_rgba(mut self, rgba: &[[u8; 4]]) -> Self {
self.set_palette_rgba(rgba);
self
}
pub fn significant_bits(&self) -> Option<&[u8]> {
self.side_channel_index(SIGNIFICANT_BITS_STRIDE)
.map(|i| self.planes[i].data.as_slice())
}
pub fn plane_significant_bits(&self, plane: usize) -> Option<u8> {
self.significant_bits()?.get(plane).copied()
}
pub fn set_significant_bits(&mut self, bits: Vec<u8>) {
self.remove_side_channel(SIGNIFICANT_BITS_STRIDE);
if !bits.is_empty() {
self.planes.push(VideoPlane {
stride: SIGNIFICANT_BITS_STRIDE,
data: bits,
});
}
}
pub fn with_significant_bits(mut self, bits: Vec<u8>) -> Self {
self.set_significant_bits(bits);
self
}
pub fn take_significant_bits(&mut self) -> Option<Vec<u8>> {
self.remove_side_channel(SIGNIFICANT_BITS_STRIDE)
}
pub fn color_signal(&self) -> Option<ColorSignal> {
self.side_channel_index(COLOR_SIGNAL_STRIDE)
.and_then(|i| ColorSignal::from_bytes(&self.planes[i].data))
}
pub fn set_color_signal(&mut self, signal: ColorSignal) {
self.remove_side_channel(COLOR_SIGNAL_STRIDE);
self.planes.push(VideoPlane {
stride: COLOR_SIGNAL_STRIDE,
data: signal.to_bytes().to_vec(),
});
}
pub fn with_color_signal(mut self, signal: ColorSignal) -> Self {
self.set_color_signal(signal);
self
}
pub fn take_color_signal(&mut self) -> Option<ColorSignal> {
self.remove_side_channel(COLOR_SIGNAL_STRIDE)
.and_then(|d| ColorSignal::from_bytes(&d))
}
pub fn layer(&self) -> Option<LayerIdentity> {
self.side_channel_index(LAYER_IDENTITY_STRIDE)
.and_then(|i| LayerIdentity::from_bytes(&self.planes[i].data))
}
pub fn layer_or_base(&self) -> LayerIdentity {
self.layer().unwrap_or_default()
}
pub fn set_layer(&mut self, layer: LayerIdentity) {
self.remove_side_channel(LAYER_IDENTITY_STRIDE);
self.planes.push(VideoPlane {
stride: LAYER_IDENTITY_STRIDE,
data: layer.to_bytes().to_vec(),
});
}
pub fn with_layer(mut self, layer: LayerIdentity) -> Self {
self.set_layer(layer);
self
}
pub fn take_layer(&mut self) -> Option<LayerIdentity> {
self.remove_side_channel(LAYER_IDENTITY_STRIDE)
.and_then(|d| LayerIdentity::from_bytes(&d))
}
pub fn blobs(&self) -> Vec<MetadataBlob> {
self.side_channel_index(BLOBS_STRIDE)
.and_then(|i| decode_blobs(&self.planes[i].data))
.unwrap_or_default()
}
pub fn blob(&self, kind: &BlobKind) -> Option<MetadataBlob> {
self.blobs().into_iter().find(|b| b.is(kind))
}
pub fn set_blobs(&mut self, blobs: Vec<MetadataBlob>) {
self.remove_side_channel(BLOBS_STRIDE);
if !blobs.is_empty() {
self.planes.push(VideoPlane {
stride: BLOBS_STRIDE,
data: encode_blobs(&blobs),
});
}
}
pub fn push_blob(&mut self, blob: MetadataBlob) {
let mut blobs = self.blobs();
blobs.push(blob);
self.set_blobs(blobs);
}
pub fn with_blob(mut self, blob: MetadataBlob) -> Self {
self.push_blob(blob);
self
}
pub fn with_blobs(mut self, blobs: Vec<MetadataBlob>) -> Self {
self.set_blobs(blobs);
self
}
pub fn take_blobs(&mut self) -> Vec<MetadataBlob> {
self.remove_side_channel(BLOBS_STRIDE)
.and_then(|d| decode_blobs(&d))
.unwrap_or_default()
}
pub fn image_planes(&self) -> &[VideoPlane] {
&self.planes[..self.image_plane_count()]
}
pub fn image_plane_count(&self) -> usize {
self.side_channel_run_start()
}
}
#[derive(Clone, Debug)]
pub struct VideoPlane {
pub stride: usize,
pub data: Vec<u8>,
}
#[cfg(test)]
mod tests {
use super::*;
fn gray_frame() -> VideoFrame {
VideoFrame {
pts: Some(7),
planes: vec![VideoPlane {
stride: 4,
data: vec![0u8; 8],
}],
}
}
fn full_palette() -> Vec<u8> {
(0u16..256)
.flat_map(|i| {
let i = i as u8;
[i, !i, i ^ 0x55]
})
.collect()
}
#[test]
fn frame_without_palette_reports_none_and_full_image_planes() {
let f = gray_frame();
assert_eq!(f.palette(), None);
assert_eq!(f.palette_rgb(0), None);
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.image_planes().len(), 1);
assert_eq!(f.image_planes()[0].stride, 4);
}
#[test]
fn set_palette_round_trips_and_keeps_image_planes_intact() {
let mut f = gray_frame();
let pal = full_palette();
f.set_palette(pal.clone());
assert_eq!(f.palette(), Some(pal.as_slice()));
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.image_planes()[0].data.len(), 8);
assert_eq!(f.planes.len(), 2);
assert_eq!(f.planes[1].stride, 0);
assert_eq!(f.palette_rgb(0), Some([0x00, 0xFF, 0x55]));
assert_eq!(f.palette_rgb(0xAB), Some([0xAB, 0x54, 0xFE]));
assert_eq!(f.palette_rgb(255), Some([0xFF, 0x00, 0xAA]));
}
#[test]
fn set_palette_replaces_existing_table() {
let mut f = gray_frame();
f.set_palette(vec![1, 2, 3]);
f.set_palette(vec![9, 8, 7, 6, 5, 4]);
assert_eq!(f.planes.len(), 2);
assert_eq!(f.palette(), Some(&[9, 8, 7, 6, 5, 4][..]));
assert_eq!(f.palette_rgb(1), Some([6, 5, 4]));
}
#[test]
fn short_palette_covers_only_its_entries() {
let f = gray_frame().with_palette(vec![10, 20, 30, 40, 50, 60]);
assert_eq!(f.palette_rgb(0), Some([10, 20, 30]));
assert_eq!(f.palette_rgb(1), Some([40, 50, 60]));
assert_eq!(f.palette_rgb(2), None);
assert_eq!(f.palette_rgb(255), None);
}
#[test]
fn empty_palette_clears_and_take_palette_detaches() {
let mut f = gray_frame();
f.set_palette(vec![1, 2, 3]);
assert!(f.palette().is_some());
f.set_palette(Vec::new());
assert_eq!(f.palette(), None);
assert_eq!(f.planes.len(), 1);
f.set_palette(vec![4, 5, 6]);
assert_eq!(f.take_palette(), Some(vec![4, 5, 6]));
assert_eq!(f.palette(), None);
assert_eq!(f.take_palette(), None);
assert_eq!(f.planes.len(), 1);
}
#[test]
fn zero_stride_empty_plane_is_not_mistaken_for_a_palette() {
let f = VideoFrame {
pts: None,
planes: vec![
VideoPlane {
stride: 4,
data: vec![0u8; 8],
},
VideoPlane {
stride: 0,
data: Vec::new(),
},
],
};
assert_eq!(f.palette(), None);
assert_eq!(f.image_plane_count(), 2);
}
#[test]
fn palette_on_frame_without_image_planes() {
let f = VideoFrame {
pts: None,
planes: Vec::new(),
}
.with_palette(vec![1, 2, 3]);
assert_eq!(f.palette(), Some(&[1, 2, 3][..]));
assert_eq!(f.image_plane_count(), 0);
assert!(f.image_planes().is_empty());
}
#[test]
fn frame_without_significant_bits_reports_none() {
let f = gray_frame();
assert_eq!(f.significant_bits(), None);
assert_eq!(f.plane_significant_bits(0), None);
assert_eq!(f.image_plane_count(), 1);
}
#[test]
fn set_significant_bits_round_trips_and_keeps_image_planes_intact() {
let mut f = VideoFrame {
pts: Some(3),
planes: vec![
VideoPlane {
stride: 8,
data: vec![0u8; 16],
},
VideoPlane {
stride: 8,
data: vec![0u8; 16],
},
VideoPlane {
stride: 8,
data: vec![0u8; 16],
},
],
};
f.set_significant_bits(vec![12, 10, 10]);
assert_eq!(f.significant_bits(), Some(&[12, 10, 10][..]));
assert_eq!(f.plane_significant_bits(0), Some(12));
assert_eq!(f.plane_significant_bits(1), Some(10));
assert_eq!(f.plane_significant_bits(2), Some(10));
assert_eq!(f.plane_significant_bits(3), None);
assert_eq!(f.image_plane_count(), 3);
assert_eq!(f.image_planes().len(), 3);
assert_eq!(f.planes.len(), 4);
assert_eq!(f.planes[3].stride, usize::MAX);
}
#[test]
fn set_significant_bits_replaces_and_empty_clears_and_take_detaches() {
let mut f = gray_frame();
f.set_significant_bits(vec![7]);
f.set_significant_bits(vec![6]);
assert_eq!(f.planes.len(), 2);
assert_eq!(f.significant_bits(), Some(&[6][..]));
f.set_significant_bits(Vec::new());
assert_eq!(f.significant_bits(), None);
assert_eq!(f.planes.len(), 1);
f.set_significant_bits(vec![5]);
assert_eq!(f.take_significant_bits(), Some(vec![5]));
assert_eq!(f.significant_bits(), None);
assert_eq!(f.take_significant_bits(), None);
assert_eq!(f.planes.len(), 1);
}
#[test]
fn palette_and_significant_bits_compose_in_either_order() {
let mut f = gray_frame()
.with_palette(vec![1, 2, 3])
.with_significant_bits(vec![8]);
assert_eq!(f.palette(), Some(&[1, 2, 3][..]));
assert_eq!(f.significant_bits(), Some(&[8][..]));
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.planes.len(), 3);
f.set_palette(vec![9, 8, 7]);
assert_eq!(f.palette(), Some(&[9, 8, 7][..]));
assert_eq!(f.significant_bits(), Some(&[8][..]));
f.set_significant_bits(vec![7]);
assert_eq!(f.palette(), Some(&[9, 8, 7][..]));
assert_eq!(f.significant_bits(), Some(&[7][..]));
assert_eq!(f.image_plane_count(), 1);
let g = gray_frame()
.with_significant_bits(vec![4])
.with_palette(full_palette());
assert_eq!(g.significant_bits(), Some(&[4][..]));
assert_eq!(g.palette_rgb(0), Some([0x00, 0xFF, 0x55]));
assert_eq!(g.image_plane_count(), 1);
let mut h = g;
assert_eq!(h.take_significant_bits(), Some(vec![4]));
assert_eq!(h.significant_bits(), None);
assert_eq!(h.palette().map(<[u8]>::len), Some(768));
assert_eq!(h.take_palette().map(|p| p.len()), Some(768));
assert_eq!(h.planes.len(), 1);
assert_eq!(h.image_plane_count(), 1);
}
#[test]
fn max_stride_empty_plane_is_not_mistaken_for_significant_bits() {
let f = VideoFrame {
pts: None,
planes: vec![
VideoPlane {
stride: 4,
data: vec![0u8; 8],
},
VideoPlane {
stride: usize::MAX,
data: Vec::new(),
},
],
};
assert_eq!(f.significant_bits(), None);
assert_eq!(f.image_plane_count(), 2);
}
#[test]
fn significant_bits_on_frame_without_image_planes() {
let f = VideoFrame {
pts: None,
planes: Vec::new(),
}
.with_significant_bits(vec![12, 10, 10]);
assert_eq!(f.significant_bits(), Some(&[12, 10, 10][..]));
assert_eq!(f.image_plane_count(), 0);
assert!(f.image_planes().is_empty());
}
#[test]
fn side_channels_survive_clone_and_frame_wrapping() {
let f = gray_frame()
.with_palette(vec![1, 2, 3])
.with_significant_bits(vec![6]);
let cloned = f.clone();
assert_eq!(cloned.palette(), f.palette());
assert_eq!(cloned.significant_bits(), f.significant_bits());
let wrapped = Frame::Video(cloned);
assert_eq!(wrapped.pts(), Some(7));
if let Frame::Video(v) = wrapped {
assert_eq!(v.palette(), Some(&[1, 2, 3][..]));
assert_eq!(v.significant_bits(), Some(&[6][..]));
} else {
unreachable!("wrapped as Video above");
}
}
#[test]
fn frame_without_color_signal_or_layer_reports_none() {
let f = gray_frame();
assert_eq!(f.color_signal(), None);
assert_eq!(f.layer(), None);
assert_eq!(f.layer_or_base(), LayerIdentity::base());
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.planes.len(), 1);
}
#[test]
fn set_color_signal_round_trips_and_keeps_image_planes_intact() {
let mut f = gray_frame();
let sig = ColorSignal::from_code_points(9, 16, 9, true);
f.set_color_signal(sig);
assert_eq!(f.color_signal(), Some(sig));
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.image_planes()[0].data.len(), 8);
assert_eq!(f.planes.len(), 2);
assert_eq!(f.planes[1].stride, usize::MAX - 1);
assert_eq!(f.planes[1].data, sig.to_bytes());
f.set_color_signal(ColorSignal::srgb());
assert_eq!(f.planes.len(), 2);
assert_eq!(f.color_signal(), Some(ColorSignal::srgb()));
assert_eq!(f.take_color_signal(), Some(ColorSignal::srgb()));
assert_eq!(f.color_signal(), None);
assert_eq!(f.take_color_signal(), None);
assert_eq!(f.planes.len(), 1);
}
#[test]
fn set_layer_round_trips_and_keeps_image_planes_intact() {
let mut f = gray_frame();
let id = LayerIdentity::new(1).with_view_id(1).with_access_unit(9);
f.set_layer(id);
assert_eq!(f.layer(), Some(id));
assert_eq!(f.layer_or_base(), id);
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.planes.len(), 2);
assert_eq!(f.planes[1].stride, usize::MAX - 2);
f.set_layer(LayerIdentity::new(2));
assert_eq!(f.planes.len(), 2);
assert_eq!(f.layer(), Some(LayerIdentity::new(2)));
assert_eq!(f.take_layer(), Some(LayerIdentity::new(2)));
assert_eq!(f.layer(), None);
assert_eq!(f.take_layer(), None);
assert_eq!(f.planes.len(), 1);
}
#[test]
fn all_four_side_channels_compose_in_any_order() {
let sig = ColorSignal::bt709_limited();
let id = LayerIdentity::new(1).with_view_id(1);
let mut f = gray_frame()
.with_layer(id)
.with_palette(vec![1, 2, 3])
.with_color_signal(sig)
.with_significant_bits(vec![8]);
assert_eq!(f.planes.len(), 5);
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.palette(), Some(&[1, 2, 3][..]));
assert_eq!(f.significant_bits(), Some(&[8][..]));
assert_eq!(f.color_signal(), Some(sig));
assert_eq!(f.layer(), Some(id));
f.set_color_signal(ColorSignal::srgb());
assert_eq!(f.planes.len(), 5);
assert_eq!(f.palette(), Some(&[1, 2, 3][..]));
assert_eq!(f.significant_bits(), Some(&[8][..]));
assert_eq!(f.layer(), Some(id));
assert_eq!(f.color_signal(), Some(ColorSignal::srgb()));
assert_eq!(f.take_layer(), Some(id));
assert_eq!(f.take_palette(), Some(vec![1, 2, 3]));
assert_eq!(f.color_signal(), Some(ColorSignal::srgb()));
assert_eq!(f.significant_bits(), Some(&[8][..]));
assert_eq!(f.take_color_signal(), Some(ColorSignal::srgb()));
assert_eq!(f.take_significant_bits(), Some(vec![8]));
assert_eq!(f.planes.len(), 1);
assert_eq!(f.image_plane_count(), 1);
}
#[test]
fn huge_stride_empty_plane_is_not_a_side_channel() {
let f = VideoFrame {
pts: None,
planes: vec![
VideoPlane {
stride: 4,
data: vec![0u8; 8],
},
VideoPlane {
stride: usize::MAX - 1,
data: Vec::new(),
},
VideoPlane {
stride: usize::MAX - 2,
data: Vec::new(),
},
],
};
assert_eq!(f.color_signal(), None);
assert_eq!(f.layer(), None);
assert_eq!(f.image_plane_count(), 3);
}
#[test]
fn largest_real_stride_is_still_an_image_plane() {
let f = VideoFrame {
pts: None,
planes: vec![VideoPlane {
stride: isize::MAX as usize,
data: vec![0u8; 1],
}],
};
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.color_signal(), None);
assert_eq!(f.layer(), None);
}
#[test]
fn malformed_short_records_read_as_none() {
let f = VideoFrame {
pts: None,
planes: vec![
VideoPlane {
stride: 4,
data: vec![0u8; 8],
},
VideoPlane {
stride: usize::MAX - 1,
data: vec![1, 2],
},
VideoPlane {
stride: usize::MAX - 2,
data: vec![0; 5],
},
],
};
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.color_signal(), None);
assert_eq!(f.layer(), None);
}
#[test]
fn color_signal_and_layer_survive_clone_and_frame_wrapping() {
let sig = ColorSignal::from_code_points(1, 13, 0, true);
let id = LayerIdentity::new(1).with_view_id(1).with_access_unit(3);
let f = gray_frame().with_color_signal(sig).with_layer(id);
let cloned = f.clone();
assert_eq!(cloned.color_signal(), Some(sig));
assert_eq!(cloned.layer(), Some(id));
let wrapped = Frame::Video(cloned);
assert_eq!(wrapped.pts(), Some(7));
if let Frame::Video(v) = wrapped {
assert_eq!(v.color_signal(), Some(sig));
assert_eq!(v.layer(), Some(id));
} else {
unreachable!("wrapped as Video above");
}
}
#[test]
fn frame_without_palette_alpha_reads_opaque() {
let f = gray_frame().with_palette(vec![1, 2, 3, 4, 5, 6]);
assert_eq!(f.palette_alpha(), None);
assert_eq!(f.palette_rgba(0), Some([1, 2, 3, 255]));
assert_eq!(f.palette_rgba(1), Some([4, 5, 6, 255]));
assert_eq!(f.palette_rgba(2), None);
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.planes.len(), 2);
let g = gray_frame();
assert_eq!(g.palette_alpha(), None);
assert_eq!(g.palette_rgba(0), None);
}
#[test]
fn set_palette_alpha_round_trips_and_keeps_image_planes_intact() {
let mut f = gray_frame().with_palette(full_palette());
f.set_palette_alpha(vec![255, 255, 0]);
assert_eq!(f.palette_alpha(), Some(&[255, 255, 0][..]));
assert_eq!(f.palette_rgba(0), Some([0x00, 0xFF, 0x55, 255]));
assert_eq!(f.palette_rgba(2), Some([0x02, 0xFD, 0x57, 0]));
assert_eq!(f.palette_rgba(3), Some([0x03, 0xFC, 0x56, 255]));
assert_eq!(f.palette_rgba(255), Some([0xFF, 0x00, 0xAA, 255]));
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.image_planes()[0].data.len(), 8);
assert_eq!(f.planes.len(), 3);
assert_eq!(f.planes[2].stride, usize::MAX - 3);
assert_eq!(f.palette().map(<[u8]>::len), Some(768));
f.set_palette_alpha(vec![128]);
assert_eq!(f.planes.len(), 3);
assert_eq!(f.palette_alpha(), Some(&[128][..]));
assert_eq!(f.palette_rgba(0), Some([0x00, 0xFF, 0x55, 128]));
f.set_palette_alpha(Vec::new());
assert_eq!(f.palette_alpha(), None);
assert_eq!(f.planes.len(), 2);
assert_eq!(f.palette().map(<[u8]>::len), Some(768));
f.set_palette_alpha(vec![0, 255]);
assert_eq!(f.take_palette_alpha(), Some(vec![0, 255]));
assert_eq!(f.palette_alpha(), None);
assert_eq!(f.take_palette_alpha(), None);
assert_eq!(f.planes.len(), 2);
}
#[test]
fn palette_alpha_longer_than_palette_is_malformed_and_reads_none() {
let mut f = gray_frame()
.with_palette(vec![1, 2, 3, 4, 5, 6])
.with_palette_alpha(vec![0, 0, 0]);
assert_eq!(f.palette_alpha(), None);
assert_eq!(f.palette_rgba(0), Some([1, 2, 3, 255]));
assert_eq!(f.palette_rgba(1), Some([4, 5, 6, 255]));
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.planes.len(), 3);
f.set_palette_alpha(vec![0, 7]);
assert_eq!(f.palette_alpha(), Some(&[0, 7][..]));
assert_eq!(f.palette_rgba(1), Some([4, 5, 6, 7]));
f.set_palette_alpha(vec![0, 0, 0]);
assert_eq!(f.palette_alpha(), None);
f.set_palette(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
assert_eq!(f.palette_alpha(), Some(&[0, 0, 0][..]));
f.set_palette(vec![1, 2, 3]);
assert_eq!(f.palette_alpha(), None);
assert_eq!(f.take_palette_alpha(), None);
assert_eq!(f.planes.len(), 2);
assert_eq!(f.palette(), Some(&[1, 2, 3][..]));
}
#[test]
fn palette_alpha_without_palette_reads_none_in_either_order() {
let mut f = gray_frame().with_palette_alpha(vec![0]);
assert_eq!(f.palette_alpha(), None);
assert_eq!(f.palette_rgba(0), None);
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.planes.len(), 2);
f.set_palette(vec![9, 9, 9]);
assert_eq!(f.palette_alpha(), Some(&[0][..]));
assert_eq!(f.palette_rgba(0), Some([9, 9, 9, 0]));
assert_eq!(f.take_palette(), Some(vec![9, 9, 9]));
assert_eq!(f.palette_alpha(), None);
assert_eq!(f.planes.len(), 2);
assert_eq!(f.image_plane_count(), 1);
f.set_palette(vec![1, 1, 1]);
assert_eq!(f.palette_alpha(), Some(&[0][..]));
}
#[test]
fn set_palette_rgba_writes_both_records() {
let table = [[10, 20, 30, 0], [40, 50, 60, 255], [70, 80, 90, 128]];
let mut f = gray_frame().with_palette_rgba(&table);
assert_eq!(f.palette(), Some(&[10, 20, 30, 40, 50, 60, 70, 80, 90][..]));
assert_eq!(f.palette_alpha(), Some(&[0, 255, 128][..]));
for (i, e) in table.iter().enumerate() {
assert_eq!(f.palette_rgba(i as u8), Some(*e));
}
assert_eq!(f.palette_rgba(3), None);
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.planes.len(), 3);
f.set_palette_rgba(&[[1, 2, 3, 4]]);
assert_eq!(f.planes.len(), 3);
assert_eq!(f.palette_rgba(0), Some([1, 2, 3, 4]));
assert_eq!(f.palette_rgba(1), None);
f.set_palette_rgba(&[]);
assert_eq!(f.palette(), None);
assert_eq!(f.palette_alpha(), None);
assert_eq!(f.planes.len(), 1);
}
#[test]
fn all_five_side_channels_compose_in_any_order() {
let sig = ColorSignal::bt709_limited();
let id = LayerIdentity::new(1).with_view_id(1);
let mut f = gray_frame()
.with_palette_alpha(vec![0])
.with_layer(id)
.with_palette(vec![1, 2, 3, 4, 5, 6])
.with_color_signal(sig)
.with_significant_bits(vec![8]);
assert_eq!(f.planes.len(), 6);
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.palette(), Some(&[1, 2, 3, 4, 5, 6][..]));
assert_eq!(f.palette_alpha(), Some(&[0][..]));
assert_eq!(f.palette_rgba(0), Some([1, 2, 3, 0]));
assert_eq!(f.palette_rgba(1), Some([4, 5, 6, 255]));
assert_eq!(f.significant_bits(), Some(&[8][..]));
assert_eq!(f.color_signal(), Some(sig));
assert_eq!(f.layer(), Some(id));
f.set_palette_alpha(vec![255, 0]);
f.set_color_signal(ColorSignal::srgb());
f.set_palette(vec![7, 8, 9, 10, 11, 12]);
assert_eq!(f.planes.len(), 6);
assert_eq!(f.palette_rgba(1), Some([10, 11, 12, 0]));
assert_eq!(f.significant_bits(), Some(&[8][..]));
assert_eq!(f.layer(), Some(id));
assert_eq!(f.color_signal(), Some(ColorSignal::srgb()));
assert_eq!(f.take_layer(), Some(id));
assert_eq!(f.take_palette_alpha(), Some(vec![255, 0]));
assert_eq!(f.palette_rgba(1), Some([10, 11, 12, 255]));
assert_eq!(f.take_palette(), Some(vec![7, 8, 9, 10, 11, 12]));
assert_eq!(f.take_color_signal(), Some(ColorSignal::srgb()));
assert_eq!(f.take_significant_bits(), Some(vec![8]));
assert_eq!(f.planes.len(), 1);
assert_eq!(f.image_plane_count(), 1);
let g = gray_frame()
.with_significant_bits(vec![8])
.with_color_signal(sig)
.with_palette(vec![1, 2, 3])
.with_layer(id)
.with_palette_alpha(vec![9]);
assert_eq!(g.planes.len(), 6);
assert_eq!(g.image_plane_count(), 1);
assert_eq!(g.palette_rgba(0), Some([1, 2, 3, 9]));
assert_eq!(g.significant_bits(), Some(&[8][..]));
assert_eq!(g.color_signal(), Some(sig));
assert_eq!(g.layer(), Some(id));
}
#[test]
fn palette_alpha_tag_with_empty_data_is_not_a_record() {
let f = VideoFrame {
pts: None,
planes: vec![
VideoPlane {
stride: 4,
data: vec![0u8; 8],
},
VideoPlane {
stride: usize::MAX - 3,
data: Vec::new(),
},
],
};
assert_eq!(f.palette_alpha(), None);
assert_eq!(f.image_plane_count(), 2);
}
#[test]
fn palette_alpha_survives_clone_and_frame_wrapping() {
let f = gray_frame().with_palette_rgba(&[[1, 2, 3, 0], [4, 5, 6, 255]]);
let cloned = f.clone();
assert_eq!(cloned.palette_alpha(), f.palette_alpha());
let wrapped = Frame::Video(cloned);
assert_eq!(wrapped.pts(), Some(7));
if let Frame::Video(v) = wrapped {
assert_eq!(v.palette_rgba(0), Some([1, 2, 3, 0]));
assert_eq!(v.palette_rgba(1), Some([4, 5, 6, 255]));
} else {
unreachable!("wrapped as Video above");
}
}
#[test]
fn frame_without_blobs_reads_empty() {
let f = gray_frame();
assert!(f.blobs().is_empty());
assert_eq!(f.blob(&BlobKind::ICC), None);
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.planes.len(), 1);
}
#[test]
fn set_blobs_round_trips_and_keeps_image_planes_intact() {
let icc = MetadataBlob::new(BlobKind::ICC, vec![1, 2, 3, 4]);
let exif = MetadataBlob::new(BlobKind::EXIF, b"II*\0".to_vec());
let mut f = gray_frame();
f.set_blobs(vec![icc.clone(), exif.clone()]);
assert_eq!(f.blobs(), vec![icc.clone(), exif.clone()]);
assert_eq!(f.blob(&BlobKind::ICC), Some(icc.clone()));
assert_eq!(f.blob(&BlobKind::EXIF), Some(exif.clone()));
assert_eq!(f.blob(&BlobKind::XMP), None);
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.image_planes()[0].data.len(), 8);
assert_eq!(f.planes.len(), 2);
assert_eq!(f.planes[1].stride, usize::MAX - 4);
assert_eq!(f.planes[1].data, encode_blobs(&[icc.clone(), exif.clone()]));
f.set_blobs(vec![exif.clone()]);
assert_eq!(f.planes.len(), 2);
assert_eq!(f.blobs(), vec![exif.clone()]);
f.set_blobs(Vec::new());
assert!(f.blobs().is_empty());
assert_eq!(f.planes.len(), 1);
f.set_blobs(vec![icc.clone()]);
assert_eq!(f.take_blobs(), vec![icc]);
assert!(f.blobs().is_empty());
assert!(f.take_blobs().is_empty());
assert_eq!(f.planes.len(), 1);
}
#[test]
fn push_blob_appends_in_order_and_with_blob_chains() {
let a = MetadataBlob::new(BlobKind::COVER_ART, vec![1]).with_mime("image/jpeg");
let b = MetadataBlob::new(BlobKind::COVER_ART, vec![2]).with_mime("image/png");
let c = MetadataBlob::new(BlobKind::custom("exr-attributes"), vec![3]);
let mut f = gray_frame().with_blob(a.clone());
f.push_blob(b.clone());
f.push_blob(c.clone());
assert_eq!(f.blobs(), vec![a.clone(), b.clone(), c.clone()]);
assert_eq!(f.blob(&BlobKind::COVER_ART), Some(a));
assert_eq!(f.blob(&BlobKind::custom("exr-attributes")), Some(c));
assert_eq!(f.planes.len(), 2);
assert_eq!(f.image_plane_count(), 1);
}
#[test]
fn malformed_blobs_record_reads_empty_and_is_replaced_by_push() {
let mut f = VideoFrame {
pts: None,
planes: vec![
VideoPlane {
stride: 4,
data: vec![0u8; 8],
},
VideoPlane {
stride: usize::MAX - 4,
data: vec![9, 9, 9],
},
],
};
assert_eq!(f.image_plane_count(), 1);
assert!(f.blobs().is_empty());
assert_eq!(f.blob(&BlobKind::ICC), None);
let icc = MetadataBlob::new(BlobKind::ICC, vec![7]);
f.push_blob(icc.clone());
assert_eq!(f.planes.len(), 2);
assert_eq!(f.blobs(), vec![icc]);
f.planes[1].data = vec![9, 9, 9];
assert!(f.take_blobs().is_empty());
assert_eq!(f.planes.len(), 1);
}
#[test]
fn blobs_tag_with_empty_data_is_not_a_record() {
let f = VideoFrame {
pts: None,
planes: vec![
VideoPlane {
stride: 4,
data: vec![0u8; 8],
},
VideoPlane {
stride: usize::MAX - 4,
data: Vec::new(),
},
],
};
assert!(f.blobs().is_empty());
assert_eq!(f.image_plane_count(), 2);
}
#[test]
fn all_six_side_channels_compose_in_any_order() {
let sig = ColorSignal::bt709_limited();
let id = LayerIdentity::new(1).with_view_id(1);
let icc = MetadataBlob::new(BlobKind::ICC, vec![1, 2, 3]);
let xmp = MetadataBlob::new(BlobKind::XMP, b"<x/>".to_vec());
let mut f = gray_frame()
.with_blob(icc.clone())
.with_palette_alpha(vec![0])
.with_layer(id)
.with_palette(vec![1, 2, 3, 4, 5, 6])
.with_color_signal(sig)
.with_significant_bits(vec![8]);
assert_eq!(f.planes.len(), 7);
assert_eq!(f.image_plane_count(), 1);
assert_eq!(f.blobs(), vec![icc.clone()]);
assert_eq!(f.palette_rgba(0), Some([1, 2, 3, 0]));
assert_eq!(f.significant_bits(), Some(&[8][..]));
assert_eq!(f.color_signal(), Some(sig));
assert_eq!(f.layer(), Some(id));
f.push_blob(xmp.clone());
f.set_color_signal(ColorSignal::srgb());
assert_eq!(f.planes.len(), 7);
assert_eq!(f.blobs(), vec![icc.clone(), xmp.clone()]);
assert_eq!(f.palette_rgba(1), Some([4, 5, 6, 255]));
assert_eq!(f.layer(), Some(id));
assert_eq!(f.color_signal(), Some(ColorSignal::srgb()));
assert_eq!(f.take_palette(), Some(vec![1, 2, 3, 4, 5, 6]));
assert_eq!(f.blobs(), vec![icc.clone(), xmp.clone()]);
assert_eq!(f.take_blobs(), vec![icc, xmp]);
assert_eq!(f.take_layer(), Some(id));
assert_eq!(f.take_palette_alpha(), None); assert_eq!(f.take_color_signal(), Some(ColorSignal::srgb()));
assert_eq!(f.take_significant_bits(), Some(vec![8]));
assert_eq!(f.planes.len(), 1);
assert_eq!(f.image_plane_count(), 1);
}
#[test]
fn blobs_survive_clone_and_frame_wrapping() {
let exif = MetadataBlob::new(BlobKind::EXIF, b"MM\0*".to_vec());
let f = gray_frame().with_blob(exif.clone());
let cloned = f.clone();
assert_eq!(cloned.blobs(), f.blobs());
let wrapped = Frame::Video(cloned);
assert_eq!(wrapped.pts(), Some(7));
if let Frame::Video(v) = wrapped {
assert_eq!(v.blob(&BlobKind::EXIF), Some(exif));
} else {
unreachable!("wrapped as Video above");
}
}
#[test]
fn palette_survives_clone_and_frame_wrapping() {
let f = gray_frame().with_palette(full_palette());
let cloned = f.clone();
assert_eq!(cloned.palette(), f.palette());
let wrapped = Frame::Video(cloned);
assert_eq!(wrapped.pts(), Some(7));
if let Frame::Video(v) = wrapped {
assert_eq!(v.palette().map(<[u8]>::len), Some(768));
} else {
unreachable!("wrapped as Video above");
}
}
}