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;
impl VideoFrame {
fn is_side_channel_entry(plane: &VideoPlane) -> bool {
(plane.stride == 0 || plane.stride == SIGNIFICANT_BITS_STRIDE) && !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)
}
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 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 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");
}
}
}