use std::vec::Vec;
use mediaframe::frame::Rotation;
use crate::FfmpegBytes;
use derive_more::IsVariant;
use ffmpeg_next::codec::Parameters;
use crate::demuxer::{
DemuxError, ParametersAlloc, ParametersCopy, ParametersMissing, ParametersTooLarge,
};
#[derive(Clone, Debug, Default)]
pub struct VideoPacketExtra {
stream_index: i32,
byte_pos: Option<i64>,
side_data: Vec<SideDataEntry>,
}
impl VideoPacketExtra {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(stream_index: i32) -> Self {
Self {
stream_index,
byte_pos: None,
side_data: Vec::new(),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn stream_index(&self) -> i32 {
self.stream_index
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn byte_pos(&self) -> Option<i64> {
self.byte_pos
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn side_data(&self) -> &[SideDataEntry] {
self.side_data.as_slice()
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_stream_index(mut self, value: i32) -> Self {
self.stream_index = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_byte_pos(mut self, value: Option<i64>) -> Self {
self.byte_pos = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
self.side_data = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
self.stream_index = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_byte_pos(&mut self, value: Option<i64>) -> &mut Self {
self.byte_pos = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
self.side_data = value;
self
}
}
#[derive(Clone, Debug, Default)]
pub struct VideoFrameExtra {
sample_aspect_ratio: Option<(u32, u32)>,
picture_type: PictureType,
key_frame: bool,
interlaced: bool,
top_field_first: bool,
best_effort_timestamp: Option<i64>,
mastering_display: Option<MasteringDisplay>,
content_light_level: Option<ContentLightLevel>,
smpte_timecode: Vec<u32>,
side_data: Vec<SideDataEntry>,
}
impl VideoFrameExtra {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new() -> Self {
Self {
sample_aspect_ratio: None,
picture_type: PictureType::Unspecified,
key_frame: false,
interlaced: false,
top_field_first: false,
best_effort_timestamp: None,
mastering_display: None,
content_light_level: None,
smpte_timecode: Vec::new(),
side_data: Vec::new(),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn sample_aspect_ratio(&self) -> Option<(u32, u32)> {
self.sample_aspect_ratio
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn picture_type(&self) -> PictureType {
self.picture_type
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn key_frame(&self) -> bool {
self.key_frame
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn interlaced(&self) -> bool {
self.interlaced
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn top_field_first(&self) -> bool {
self.top_field_first
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn best_effort_timestamp(&self) -> Option<i64> {
self.best_effort_timestamp
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn mastering_display(&self) -> Option<MasteringDisplay> {
self.mastering_display
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn content_light_level(&self) -> Option<ContentLightLevel> {
self.content_light_level
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn smpte_timecode(&self) -> &[u32] {
self.smpte_timecode.as_slice()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn side_data(&self) -> &[SideDataEntry] {
self.side_data.as_slice()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_sample_aspect_ratio(mut self, value: Option<(u32, u32)>) -> Self {
self.sample_aspect_ratio = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_picture_type(mut self, value: PictureType) -> Self {
self.picture_type = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_key_frame(mut self, value: bool) -> Self {
self.key_frame = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_interlaced(mut self, value: bool) -> Self {
self.interlaced = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_top_field_first(mut self, value: bool) -> Self {
self.top_field_first = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_best_effort_timestamp(mut self, value: Option<i64>) -> Self {
self.best_effort_timestamp = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_mastering_display(mut self, value: Option<MasteringDisplay>) -> Self {
self.mastering_display = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_content_light_level(mut self, value: Option<ContentLightLevel>) -> Self {
self.content_light_level = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub fn with_smpte_timecode(mut self, value: Vec<u32>) -> Self {
self.smpte_timecode = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
self.side_data = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_sample_aspect_ratio(&mut self, value: Option<(u32, u32)>) -> &mut Self {
self.sample_aspect_ratio = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_picture_type(&mut self, value: PictureType) -> &mut Self {
self.picture_type = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_key_frame(&mut self, value: bool) -> &mut Self {
self.key_frame = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_interlaced(&mut self, value: bool) -> &mut Self {
self.interlaced = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_top_field_first(&mut self, value: bool) -> &mut Self {
self.top_field_first = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_best_effort_timestamp(&mut self, value: Option<i64>) -> &mut Self {
self.best_effort_timestamp = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_mastering_display(&mut self, value: Option<MasteringDisplay>) -> &mut Self {
self.mastering_display = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_content_light_level(&mut self, value: Option<ContentLightLevel>) -> &mut Self {
self.content_light_level = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_smpte_timecode(&mut self, value: Vec<u32>) -> &mut Self {
self.smpte_timecode = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
self.side_data = value;
self
}
}
#[derive(Clone, Debug, Default)]
pub struct AudioPacketExtra {
stream_index: i32,
byte_pos: Option<i64>,
side_data: Vec<SideDataEntry>,
}
impl AudioPacketExtra {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(stream_index: i32) -> Self {
Self {
stream_index,
byte_pos: None,
side_data: Vec::new(),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn stream_index(&self) -> i32 {
self.stream_index
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn byte_pos(&self) -> Option<i64> {
self.byte_pos
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn side_data(&self) -> &[SideDataEntry] {
self.side_data.as_slice()
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_stream_index(mut self, value: i32) -> Self {
self.stream_index = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_byte_pos(mut self, value: Option<i64>) -> Self {
self.byte_pos = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
self.side_data = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
self.stream_index = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_byte_pos(&mut self, value: Option<i64>) -> &mut Self {
self.byte_pos = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
self.side_data = value;
self
}
}
#[derive(Clone, Debug, Default)]
pub struct AudioFrameExtra {
best_effort_timestamp: Option<i64>,
side_data: Vec<SideDataEntry>,
}
impl AudioFrameExtra {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new() -> Self {
Self {
best_effort_timestamp: None,
side_data: Vec::new(),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn best_effort_timestamp(&self) -> Option<i64> {
self.best_effort_timestamp
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn side_data(&self) -> &[SideDataEntry] {
self.side_data.as_slice()
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_best_effort_timestamp(mut self, value: Option<i64>) -> Self {
self.best_effort_timestamp = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
self.side_data = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_best_effort_timestamp(&mut self, value: Option<i64>) -> &mut Self {
self.best_effort_timestamp = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
self.side_data = value;
self
}
}
#[derive(Clone, Debug, Default)]
pub struct SubtitlePacketExtra {
stream_index: i32,
language: Option<[u8; 3]>,
forced: bool,
side_data: Vec<SideDataEntry>,
}
impl SubtitlePacketExtra {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(stream_index: i32) -> Self {
Self {
stream_index,
language: None,
forced: false,
side_data: Vec::new(),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn stream_index(&self) -> i32 {
self.stream_index
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn language(&self) -> Option<[u8; 3]> {
self.language
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn forced(&self) -> bool {
self.forced
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn side_data(&self) -> &[SideDataEntry] {
self.side_data.as_slice()
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_stream_index(mut self, value: i32) -> Self {
self.stream_index = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_language(mut self, value: Option<[u8; 3]>) -> Self {
self.language = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
self.side_data = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
self.side_data = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_forced(mut self, value: bool) -> Self {
self.forced = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
self.stream_index = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_language(&mut self, value: Option<[u8; 3]>) -> &mut Self {
self.language = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_forced(&mut self, value: bool) -> &mut Self {
self.forced = value;
self
}
}
#[derive(Clone, Debug, Default)]
pub struct SubtitleFrameExtra {
start_display_time: u32,
end_display_time: u32,
}
impl SubtitleFrameExtra {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(start_display_time: u32, end_display_time: u32) -> Self {
Self {
start_display_time,
end_display_time,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn start_display_time(&self) -> u32 {
self.start_display_time
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn end_display_time(&self) -> u32 {
self.end_display_time
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_start_display_time(mut self, value: u32) -> Self {
self.start_display_time = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_end_display_time(mut self, value: u32) -> Self {
self.end_display_time = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_start_display_time(&mut self, value: u32) -> &mut Self {
self.start_display_time = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_end_display_time(&mut self, value: u32) -> &mut Self {
self.end_display_time = value;
self
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, IsVariant)]
#[non_exhaustive]
pub enum ImageOrientation {
#[default]
TopLeft,
TopRight,
BottomRight,
BottomLeft,
LeftTop,
RightTop,
RightBottom,
LeftBottom,
Other([i32; 9]),
}
impl ImageOrientation {
const UNIT: i32 = 1 << 16;
const PERSPECTIVE_UNIT: i32 = 1 << 30;
pub const DISPLAY_MATRIX_BYTES: usize = 9 * core::mem::size_of::<i32>();
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn matrix(&self) -> [i32; 9] {
match self {
Self::Other(matrix) => *matrix,
named => {
let [a, b, c, d] = named.linear();
[a, b, 0, c, d, 0, 0, 0, Self::PERSPECTIVE_UNIT]
}
}
}
pub fn from_display_matrix(bytes: &[u8]) -> Option<Self> {
if bytes.len() != Self::DISPLAY_MATRIX_BYTES {
return None;
}
let mut matrix = [0i32; 9];
for (index, word) in matrix.iter_mut().enumerate() {
let mut raw = [0u8; 4];
raw.copy_from_slice(&bytes[index * 4..index * 4 + 4]);
*word = i32::from_ne_bytes(raw);
}
Some(Self::from_matrix(matrix))
}
fn from_matrix(matrix: [i32; 9]) -> Self {
const P: i32 = ImageOrientation::UNIT;
const N: i32 = -ImageOrientation::UNIT;
const W: i32 = ImageOrientation::PERSPECTIVE_UNIT;
match matrix {
[P, 0, 0, 0, P, 0, 0, 0, W] => Self::TopLeft,
[N, 0, 0, 0, P, 0, 0, 0, W] => Self::TopRight,
[N, 0, 0, 0, N, 0, 0, 0, W] => Self::BottomRight,
[P, 0, 0, 0, N, 0, 0, 0, W] => Self::BottomLeft,
[0, P, 0, P, 0, 0, 0, 0, W] => Self::LeftTop,
[0, P, 0, N, 0, 0, 0, 0, W] => Self::RightTop,
[0, N, 0, N, 0, 0, 0, 0, W] => Self::RightBottom,
[0, N, 0, P, 0, 0, 0, 0, W] => Self::LeftBottom,
other => Self::Other(other),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn to_exif_code(&self) -> Option<u16> {
Some(match self {
Self::TopLeft => 1,
Self::TopRight => 2,
Self::BottomRight => 3,
Self::BottomLeft => 4,
Self::LeftTop => 5,
Self::RightTop => 6,
Self::RightBottom => 7,
Self::LeftBottom => 8,
Self::Other(_) => return None,
})
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn from_exif_code(code: u16) -> Option<Self> {
Some(match code {
1 => Self::TopLeft,
2 => Self::TopRight,
3 => Self::BottomRight,
4 => Self::BottomLeft,
5 => Self::LeftTop,
6 => Self::RightTop,
7 => Self::RightBottom,
8 => Self::LeftBottom,
_ => return None,
})
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn is_mirrored(&self) -> bool {
let [a, b, c, d] = self.linear();
(a as i64) * (d as i64) - (b as i64) * (c as i64) < 0
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn rotation(&self) -> Option<Rotation> {
Some(match self {
Self::TopLeft | Self::TopRight => Rotation::D0,
Self::RightTop | Self::LeftTop => Rotation::D90,
Self::BottomRight | Self::BottomLeft => Rotation::D180,
Self::LeftBottom | Self::RightBottom => Rotation::D270,
Self::Other(_) => return None,
})
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn linear(&self) -> [i32; 4] {
const P: i32 = ImageOrientation::UNIT;
const N: i32 = -ImageOrientation::UNIT;
match self {
Self::TopLeft => [P, 0, 0, P],
Self::TopRight => [N, 0, 0, P],
Self::BottomRight => [N, 0, 0, N],
Self::BottomLeft => [P, 0, 0, N],
Self::LeftTop => [0, P, P, 0],
Self::RightTop => [0, P, N, 0],
Self::RightBottom => [0, N, N, 0],
Self::LeftBottom => [0, N, P, 0],
Self::Other(matrix) => [matrix[0], matrix[1], matrix[3], matrix[4]],
}
}
}
#[derive(Clone, Debug, Default)]
pub struct ImageFrameExtra {
orientation: Option<ImageOrientation>,
side_data: Vec<SideDataEntry>,
}
impl ImageFrameExtra {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new() -> Self {
Self {
orientation: None,
side_data: Vec::new(),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn orientation(&self) -> Option<ImageOrientation> {
self.orientation
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn side_data(&self) -> &[SideDataEntry] {
self.side_data.as_slice()
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_orientation(mut self, value: Option<ImageOrientation>) -> Self {
self.orientation = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
self.side_data = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_orientation(&mut self, value: Option<ImageOrientation>) -> &mut Self {
self.orientation = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
self.side_data = value;
self
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, IsVariant)]
#[non_exhaustive]
pub enum PictureType {
#[default]
Unspecified,
I,
P,
B,
S,
Si,
Sp,
Bi,
}
#[derive(Clone, Debug)]
pub struct SideDataEntry {
kind: i32,
data: FfmpegBytes,
}
impl SideDataEntry {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(kind: i32, data: FfmpegBytes) -> Self {
Self { kind, data }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn kind(&self) -> i32 {
self.kind
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn data(&self) -> &[u8] {
self.data.as_slice()
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn data_ref(&self) -> &FfmpegBytes {
&self.data
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_kind(mut self, value: i32) -> Self {
self.kind = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub fn with_data(mut self, value: FfmpegBytes) -> Self {
self.data = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_kind(&mut self, value: i32) -> &mut Self {
self.kind = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_data(&mut self, value: FfmpegBytes) -> &mut Self {
self.data = value;
self
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct MasteringDisplay {
display_primaries: [(u32, u32); 3],
white_point: (u32, u32),
max_luminance: (u32, u32),
min_luminance: (u32, u32),
}
impl MasteringDisplay {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(
display_primaries: [(u32, u32); 3],
white_point: (u32, u32),
max_luminance: (u32, u32),
min_luminance: (u32, u32),
) -> Self {
Self {
display_primaries,
white_point,
max_luminance,
min_luminance,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn display_primaries(&self) -> [(u32, u32); 3] {
self.display_primaries
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn white_point(&self) -> (u32, u32) {
self.white_point
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn max_luminance(&self) -> (u32, u32) {
self.max_luminance
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn min_luminance(&self) -> (u32, u32) {
self.min_luminance
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_display_primaries(mut self, value: [(u32, u32); 3]) -> Self {
self.display_primaries = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_white_point(mut self, value: (u32, u32)) -> Self {
self.white_point = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_max_luminance(mut self, value: (u32, u32)) -> Self {
self.max_luminance = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn with_min_luminance(mut self, value: (u32, u32)) -> Self {
self.min_luminance = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_display_primaries(&mut self, value: [(u32, u32); 3]) -> &mut Self {
self.display_primaries = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_white_point(&mut self, value: (u32, u32)) -> &mut Self {
self.white_point = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_max_luminance(&mut self, value: (u32, u32)) -> &mut Self {
self.max_luminance = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_min_luminance(&mut self, value: (u32, u32)) -> &mut Self {
self.min_luminance = value;
self
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct ContentLightLevel {
max_cll: u32,
max_fall: u32,
}
impl ContentLightLevel {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(max_cll: u32, max_fall: u32) -> Self {
Self { max_cll, max_fall }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn max_cll(&self) -> u32 {
self.max_cll
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn max_fall(&self) -> u32 {
self.max_fall
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_max_cll(mut self, value: u32) -> Self {
self.max_cll = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_max_fall(mut self, value: u32) -> Self {
self.max_fall = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_max_cll(&mut self, value: u32) -> &mut Self {
self.max_cll = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_max_fall(&mut self, value: u32) -> &mut Self {
self.max_fall = value;
self
}
}
#[derive(Clone, Debug, Default)]
pub struct DataPacketExtra {
stream_index: i32,
byte_pos: Option<i64>,
side_data: Vec<SideDataEntry>,
}
impl DataPacketExtra {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(stream_index: i32) -> Self {
Self {
stream_index,
byte_pos: None,
side_data: Vec::new(),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn stream_index(&self) -> i32 {
self.stream_index
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn byte_pos(&self) -> Option<i64> {
self.byte_pos
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn side_data(&self) -> &[SideDataEntry] {
self.side_data.as_slice()
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_stream_index(mut self, value: i32) -> Self {
self.stream_index = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_byte_pos(mut self, value: Option<i64>) -> Self {
self.byte_pos = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
self.side_data = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
self.stream_index = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_byte_pos(&mut self, value: Option<i64>) -> &mut Self {
self.byte_pos = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
self.side_data = value;
self
}
}
#[derive(Clone, Debug, Default)]
pub struct AttachmentPacketExtra {
stream_index: i32,
synthesized: bool,
}
impl AttachmentPacketExtra {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(stream_index: i32) -> Self {
Self {
stream_index,
synthesized: false,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn stream_index(&self) -> i32 {
self.stream_index
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn synthesized(&self) -> bool {
self.synthesized
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_stream_index(mut self, value: i32) -> Self {
self.stream_index = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_synthesized(mut self, value: bool) -> Self {
self.synthesized = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
self.stream_index = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_synthesized(&mut self, value: bool) -> &mut Self {
self.synthesized = value;
self
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct ParameterFootprint {
extradata: usize,
extradata_payload: usize,
coded_side_data: usize,
channel_map: usize,
}
impl ParameterFootprint {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn extradata(&self) -> usize {
self.extradata
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn extradata_payload(&self) -> usize {
self.extradata_payload
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn coded_side_data(&self) -> usize {
self.coded_side_data
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn channel_map(&self) -> usize {
self.channel_map
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn total(&self) -> Option<usize> {
match self.extradata.checked_add(self.coded_side_data) {
Some(sum) => sum.checked_add(self.channel_map),
None => None,
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn total_without_extradata(&self) -> Option<usize> {
self.coded_side_data.checked_add(self.channel_map)
}
}
#[cfg(target_pointer_width = "64")]
const _: () = {
assert!(
core::mem::size_of::<ffmpeg_next::ffi::AVCodecParameters>() == 184,
"AVCodecParameters changed shape — re-census its heap fields against \
`measure_parameters` and `bounded_clone_parameters` before raising this",
);
};
pub(crate) unsafe fn measure_parameters(
par: *const ffmpeg_next::ffi::AVCodecParameters,
) -> Option<ParameterFootprint> {
let extradata_payload = if unsafe { (*par).extradata }.is_null() {
0
} else {
usize::try_from(unsafe { (*par).extradata_size }).ok()?
};
let extradata = if extradata_payload == 0 {
0
} else {
extradata_payload.checked_add(ffmpeg_next::ffi::AV_INPUT_BUFFER_PADDING_SIZE as usize)?
};
let side_data_ptr = unsafe { (*par).coded_side_data };
let side_data_count = unsafe { (*par).nb_coded_side_data };
let coded_side_data = if side_data_ptr.is_null() || side_data_count <= 0 {
0
} else {
let count = usize::try_from(side_data_count).ok()?;
let mut total = count.checked_mul(core::mem::size_of::<ffmpeg_next::ffi::AVPacketSideData>())?;
for index in 0..count {
let size =
unsafe { core::ptr::read_unaligned(core::ptr::addr_of!((*side_data_ptr.add(index)).size)) };
total = total.checked_add(size)?;
}
total
};
let channel_map = {
let order = unsafe {
core::ptr::read_unaligned(core::ptr::addr_of!((*par).ch_layout.order).cast::<i32>())
};
const UNSPEC: i32 = ffmpeg_next::ffi::AVChannelOrder::AV_CHANNEL_ORDER_UNSPEC as i32;
const NATIVE: i32 = ffmpeg_next::ffi::AVChannelOrder::AV_CHANNEL_ORDER_NATIVE as i32;
const CUSTOM: i32 = ffmpeg_next::ffi::AVChannelOrder::AV_CHANNEL_ORDER_CUSTOM as i32;
const AMBISONIC: i32 = ffmpeg_next::ffi::AVChannelOrder::AV_CHANNEL_ORDER_AMBISONIC as i32;
match order {
UNSPEC | NATIVE | AMBISONIC => 0,
CUSTOM => {
let channels = usize::try_from(unsafe { (*par).ch_layout.nb_channels }).ok()?;
channels.checked_mul(core::mem::size_of::<ffmpeg_next::ffi::AVChannelCustom>())?
}
_ => return None,
}
};
Some(ParameterFootprint {
extradata,
extradata_payload,
coded_side_data,
channel_map,
})
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub(crate) enum ExtradataPolicy {
#[default]
Copy,
Omit,
}
fn seat_copy_failed(stream_index: usize) -> DemuxError {
DemuxError::ParametersCopy(ParametersCopy::new(
stream_index,
ffmpeg_next::Error::Other {
errno: libc::ENOMEM,
},
))
}
pub(crate) fn bounded_clone_parameters(
source: &Parameters,
stream_index: usize,
budget: usize,
) -> Result<Parameters, DemuxError> {
bounded_clone_parameters_with(source, stream_index, budget, ExtradataPolicy::Copy)
}
pub(crate) fn bounded_clone_parameters_with(
source: &Parameters,
stream_index: usize,
budget: usize,
extradata_policy: ExtradataPolicy,
) -> Result<Parameters, DemuxError> {
let src = unsafe { source.as_ptr() };
if src.is_null() {
return Err(DemuxError::ParametersMissing(ParametersMissing::new(
stream_index,
)));
}
let footprint = unsafe { measure_parameters(src) }.ok_or_else(|| {
DemuxError::ParametersTooLarge(ParametersTooLarge::new(stream_index, usize::MAX, budget))
})?;
let total = match extradata_policy {
ExtradataPolicy::Copy => footprint.total(),
ExtradataPolicy::Omit => footprint.total_without_extradata(),
}
.ok_or_else(|| {
DemuxError::ParametersTooLarge(ParametersTooLarge::new(stream_index, usize::MAX, budget))
})?;
if total > budget {
return Err(DemuxError::ParametersTooLarge(ParametersTooLarge::new(
stream_index,
total,
budget,
)));
}
let mut out = Parameters::new();
let dst = unsafe { out.as_mut_ptr() };
if dst.is_null() {
return Err(DemuxError::ParametersAlloc(ParametersAlloc::new(
stream_index,
)));
}
unsafe {
core::ptr::copy_nonoverlapping(src, dst, 1);
(*dst).extradata = core::ptr::null_mut();
(*dst).extradata_size = 0;
(*dst).coded_side_data = core::ptr::null_mut();
(*dst).nb_coded_side_data = 0;
(*dst).ch_layout = core::mem::zeroed();
}
if footprint.extradata() > 0 && matches!(extradata_policy, ExtradataPolicy::Copy) {
let padded = footprint.extradata();
unsafe {
let buffer = ffmpeg_next::ffi::av_mallocz(padded) as *mut u8;
if buffer.is_null() {
return Err(seat_copy_failed(stream_index));
}
let payload =
usize::try_from((*src).extradata_size).map_err(|_| seat_copy_failed(stream_index))?;
core::ptr::copy_nonoverlapping((*src).extradata, buffer, payload);
(*dst).extradata = buffer;
(*dst).extradata_size = (*src).extradata_size;
}
}
unsafe {
let count = (*src).nb_coded_side_data;
if count > 0 && !(*src).coded_side_data.is_null() {
let entries = usize::try_from(count)
.ok()
.and_then(|c| c.checked_mul(core::mem::size_of::<ffmpeg_next::ffi::AVPacketSideData>()))
.ok_or_else(|| seat_copy_failed(stream_index))?;
let array = ffmpeg_next::ffi::av_mallocz(entries) as *mut ffmpeg_next::ffi::AVPacketSideData;
if array.is_null() {
return Err(seat_copy_failed(stream_index));
}
(*dst).coded_side_data = array;
(*dst).nb_coded_side_data = count;
for index in 0..count as usize {
let from = (*src).coded_side_data.add(index);
let into = array.add(index);
let kind = core::ptr::read_unaligned(core::ptr::addr_of!((*from).type_).cast::<i32>());
core::ptr::write_unaligned(core::ptr::addr_of_mut!((*into).type_).cast::<i32>(), kind);
let size = core::ptr::read_unaligned(core::ptr::addr_of!((*from).size));
let data = core::ptr::read_unaligned(core::ptr::addr_of!((*from).data));
if size > 0 && !data.is_null() {
let payload = ffmpeg_next::ffi::av_mallocz(size) as *mut u8;
if payload.is_null() {
return Err(seat_copy_failed(stream_index));
}
core::ptr::copy_nonoverlapping(data, payload, size);
core::ptr::write_unaligned(core::ptr::addr_of_mut!((*into).data), payload);
core::ptr::write_unaligned(core::ptr::addr_of_mut!((*into).size), size);
} else {
core::ptr::write_unaligned(core::ptr::addr_of_mut!((*into).size), 0);
}
}
}
}
let rc = unsafe {
ffmpeg_next::ffi::av_channel_layout_copy(
core::ptr::addr_of_mut!((*dst).ch_layout),
core::ptr::addr_of!((*src).ch_layout),
)
};
if rc < 0 {
return Err(DemuxError::ParametersCopy(ParametersCopy::new(
stream_index,
ffmpeg_next::Error::from(rc),
)));
}
Ok(out)
}
pub struct TrackExtra {
stream_index: i32,
disposition: i32,
start_time: Option<i64>,
frame_count: Option<i64>,
parameters: Parameters,
parameter_bytes: usize,
}
impl TrackExtra {
pub fn new(stream_index: i32, parameters: Parameters) -> Result<Self, DemuxError> {
let par = unsafe { parameters.as_ptr() };
if par.is_null() {
return Err(DemuxError::ParametersMissing(ParametersMissing::new(
stream_index.max(0) as usize,
)));
}
let parameter_bytes = unsafe { measure_parameters(par) }
.and_then(|footprint| footprint.total())
.ok_or_else(|| {
DemuxError::ParametersTooLarge(ParametersTooLarge::new(
stream_index.max(0) as usize,
usize::MAX,
usize::MAX,
))
})?;
Ok(Self {
stream_index,
disposition: 0,
start_time: None,
frame_count: None,
parameters,
parameter_bytes,
})
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn parameter_bytes(&self) -> usize {
self.parameter_bytes
}
pub fn try_clone(&self) -> Result<Self, DemuxError> {
Ok(Self {
stream_index: self.stream_index,
disposition: self.disposition,
start_time: self.start_time,
frame_count: self.frame_count,
parameters: self.clone_parameters()?,
parameter_bytes: self.parameter_bytes,
})
}
pub fn clone_parameters(&self) -> Result<Parameters, DemuxError> {
bounded_clone_parameters(
&self.parameters,
self.stream_index.max(0) as usize,
self.parameter_bytes,
)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn stream_index(&self) -> i32 {
self.stream_index
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn disposition(&self) -> i32 {
self.disposition
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn start_time(&self) -> Option<i64> {
self.start_time
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn frame_count(&self) -> Option<i64> {
self.frame_count
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn parameters(&self) -> &Parameters {
&self.parameters
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_disposition(mut self, value: i32) -> Self {
self.disposition = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_start_time(mut self, value: Option<i64>) -> Self {
self.start_time = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[must_use]
pub const fn with_frame_count(mut self, value: Option<i64>) -> Self {
self.frame_count = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_disposition(&mut self, value: i32) -> &mut Self {
self.disposition = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_start_time(&mut self, value: Option<i64>) -> &mut Self {
self.start_time = value;
self
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn set_frame_count(&mut self, value: Option<i64>) -> &mut Self {
self.frame_count = value;
self
}
}
impl std::fmt::Debug for TrackExtra {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TrackExtra")
.field("stream_index", &self.stream_index)
.field("disposition", &format_args!("{:#x}", self.disposition))
.field("start_time", &self.start_time)
.field("frame_count", &self.frame_count)
.field(
"parameters",
&format_args!("{:?}", crate::boundary::media_kind_of(&self.parameters)),
)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_construct() {
let v = VideoPacketExtra::default();
assert_eq!(v.stream_index(), 0);
assert!(v.side_data().is_empty());
let f = VideoFrameExtra::default();
assert_eq!(f.picture_type(), PictureType::Unspecified);
assert!(!f.key_frame());
assert!(f.mastering_display().is_none());
let s = SubtitleFrameExtra::default();
assert_eq!(s.start_display_time(), 0);
assert_eq!(s.end_display_time(), 0);
}
#[test]
fn picture_type_default_is_unspecified() {
assert_eq!(PictureType::default(), PictureType::Unspecified);
}
fn parameters_with(extradata: usize, icc_profile: usize) -> Parameters {
let mut out = Parameters::new();
unsafe {
let par = out.as_mut_ptr();
if extradata > 0 {
let buffer = ffmpeg_next::ffi::av_mallocz(extradata) as *mut u8;
assert!(!buffer.is_null(), "av_mallocz extradata");
(*par).extradata = buffer;
(*par).extradata_size = extradata as i32;
}
if icc_profile > 0 {
let array = ffmpeg_next::ffi::av_mallocz(core::mem::size_of::<
ffmpeg_next::ffi::AVPacketSideData,
>()) as *mut ffmpeg_next::ffi::AVPacketSideData;
assert!(!array.is_null(), "av_mallocz side-data array");
let payload = ffmpeg_next::ffi::av_mallocz(icc_profile) as *mut u8;
assert!(!payload.is_null(), "av_mallocz icc profile");
(*array).data = payload;
(*array).size = icc_profile;
(*array).type_ = ffmpeg_next::ffi::AVPacketSideDataType::AV_PKT_DATA_ICC_PROFILE;
(*par).coded_side_data = array;
(*par).nb_coded_side_data = 1;
}
}
out
}
fn footprint_of(parameters: &Parameters) -> ParameterFootprint {
unsafe { measure_parameters(parameters.as_ptr()) }.expect("measurable")
}
#[test]
fn the_measurement_counts_every_heap_seat_and_allocates_nothing() {
const PAD: usize = ffmpeg_next::ffi::AV_INPUT_BUFFER_PADDING_SIZE as usize;
const DESCRIPTOR: usize = core::mem::size_of::<ffmpeg_next::ffi::AVPacketSideData>();
let parameters = parameters_with(4_096, 64 * 1024);
let footprint = footprint_of(¶meters);
assert_eq!(footprint.extradata(), 4_096 + PAD);
assert_eq!(
footprint.coded_side_data(),
64 * 1024 + DESCRIPTOR,
"the descriptor array is an allocation too",
);
assert_eq!(footprint.channel_map(), 0, "no custom layout here");
assert_eq!(
footprint.total(),
Some(4_096 + PAD + 64 * 1024 + DESCRIPTOR),
);
assert_eq!(
footprint.total_without_extradata(),
Some(64 * 1024 + DESCRIPTOR),
);
assert_eq!(footprint_of(¶meters_with(0, 8)).extradata(), 0);
}
#[test]
fn an_oversized_coded_side_data_entry_is_refused_before_the_clone() {
let parameters = parameters_with(0, 8 * 1024 * 1024);
let declared = footprint_of(¶meters).total().expect("measurable");
match bounded_clone_parameters(¶meters, 3, 64 * 1024) {
Err(DemuxError::ParametersTooLarge(p)) => {
assert_eq!(p.stream_index(), 3);
assert_eq!(p.bytes(), declared);
assert_eq!(p.limit(), 64 * 1024);
}
Err(other) => panic!("expected ParametersTooLarge, got {other:?}"),
Ok(_) => panic!("an 8 MiB ICC profile passed a 64 KiB ceiling"),
}
let cloned =
bounded_clone_parameters(¶meters, 3, declared).expect("at the cap is not over it");
assert_eq!(footprint_of(&cloned), footprint_of(¶meters));
}
#[test]
fn a_legitimate_multi_megabyte_icc_profile_is_admitted_by_default() {
let parameters = parameters_with(1_024, 4 * 1024 * 1024);
let cloned = bounded_clone_parameters(
¶meters,
0,
crate::limits::DEFAULT_MAX_CODEC_PARAMETER_BYTES,
)
.expect("a 4 MiB ICC profile is real media");
assert_eq!(footprint_of(&cloned), footprint_of(¶meters));
}
#[test]
fn the_bounded_clone_keeps_every_field_a_decoder_consumes() {
let parameters = parameters_with(32, 128);
unsafe {
let par = parameters.as_ptr() as *mut ffmpeg_next::ffi::AVCodecParameters;
(*par).codec_id = ffmpeg_next::ffi::AVCodecID::AV_CODEC_ID_H264;
(*par).width = 1920;
(*par).height = 1080;
(*par).bit_rate = 5_000_000;
(*par).sample_rate = 48_000;
core::ptr::write_bytes((*par).extradata, 0xAB, 32);
core::ptr::write_bytes((*(*par).coded_side_data).data, 0xCD, 128);
}
let cloned = bounded_clone_parameters(¶meters, 0, usize::MAX).expect("clone");
unsafe {
let src = parameters.as_ptr();
let dst = cloned.as_ptr();
assert_eq!((*dst).codec_id, (*src).codec_id, "the scalar sweep");
assert_eq!(((*dst).width, (*dst).height), (1920, 1080));
assert_eq!((*dst).bit_rate, 5_000_000);
assert_eq!((*dst).sample_rate, 48_000);
assert_eq!((*dst).extradata_size, 32);
assert_ne!(
(*dst).extradata,
(*src).extradata,
"it is a copy, not an alias"
);
let extradata = core::slice::from_raw_parts((*dst).extradata, 32);
assert!(extradata.iter().all(|&b| b == 0xAB), "SPS/PPS survived");
let padded = core::slice::from_raw_parts(
(*dst).extradata.add(32),
ffmpeg_next::ffi::AV_INPUT_BUFFER_PADDING_SIZE as usize,
);
assert!(padded.iter().all(|&b| b == 0), "the read-past padding");
assert_eq!((*dst).nb_coded_side_data, 1);
let entry = &*(*dst).coded_side_data;
assert_eq!(entry.size, 128);
assert_eq!(
entry.type_,
ffmpeg_next::ffi::AVPacketSideDataType::AV_PKT_DATA_ICC_PROFILE,
);
assert_ne!(entry.data, (*(*src).coded_side_data).data, "a copy");
let payload = core::slice::from_raw_parts(entry.data, 128);
assert!(
payload.iter().all(|&b| b == 0xCD),
"the ICC profile survived"
);
}
}
#[test]
fn side_data_entry_carries_bytes() {
let entry = SideDataEntry::new(12345, FfmpegBytes::copy_from_slice(&[1, 2, 3, 4]));
assert_eq!(entry.kind(), 12345);
assert_eq!(entry.data(), &[1, 2, 3, 4]);
}
#[test]
fn side_data_entry_clone_shares_its_payload() {
let entry = SideDataEntry::new(7, FfmpegBytes::copy_from_slice(&[9u8; 64]));
let cloned = entry.clone();
assert!(
entry.data_ref().ptr_eq(cloned.data_ref()),
"cloning a side-data entry copied its bytes",
);
assert_eq!(cloned.data(), entry.data());
}
const MEASURED: [(u16, ImageOrientation, [i32; 4]); 8] = [
(1, ImageOrientation::TopLeft, [65536, 0, 0, 65536]),
(2, ImageOrientation::TopRight, [-65536, 0, 0, 65536]),
(3, ImageOrientation::BottomRight, [-65536, 0, 0, -65536]),
(4, ImageOrientation::BottomLeft, [65536, 0, 0, -65536]),
(5, ImageOrientation::LeftTop, [0, 65536, 65536, 0]),
(6, ImageOrientation::RightTop, [0, 65536, -65536, 0]),
(7, ImageOrientation::RightBottom, [0, -65536, -65536, 0]),
(8, ImageOrientation::LeftBottom, [0, -65536, 65536, 0]),
];
fn display_matrix(linear: [i32; 4]) -> Vec<u8> {
words_to_bytes([
linear[0],
linear[1],
0,
linear[2],
linear[3],
0,
0,
0,
1 << 30,
])
}
fn words_to_bytes(words: [i32; 9]) -> Vec<u8> {
words.iter().flat_map(|w| w.to_ne_bytes()).collect()
}
#[test]
fn every_measured_display_matrix_reads_back_as_its_exif_tag() {
for (tag, expected, linear) in MEASURED {
let read = ImageOrientation::from_display_matrix(&display_matrix(linear))
.expect("a nine-word matrix is readable");
assert_eq!(read, expected, "tag {tag}");
assert_eq!(read.to_exif_code(), Some(tag));
assert_eq!(ImageOrientation::from_exif_code(tag), Some(read));
assert_eq!(read.linear(), linear, "tag {tag}");
}
}
#[test]
fn the_four_mirrored_tags_are_the_ones_exif_says_they_are() {
for (tag, orientation, _) in MEASURED {
assert_eq!(
orientation.is_mirrored(),
matches!(tag, 2 | 4 | 5 | 7),
"tag {tag}",
);
}
}
#[test]
fn the_quarter_turn_lands_in_the_workspace_rotation_vocabulary() {
use ImageOrientation::*;
assert_eq!(TopLeft.rotation(), Some(Rotation::D0));
assert_eq!(TopRight.rotation(), Some(Rotation::D0));
assert_eq!(RightTop.rotation(), Some(Rotation::D90));
assert_eq!(LeftTop.rotation(), Some(Rotation::D90));
assert_eq!(BottomRight.rotation(), Some(Rotation::D180));
assert_eq!(BottomLeft.rotation(), Some(Rotation::D180));
assert_eq!(LeftBottom.rotation(), Some(Rotation::D270));
assert_eq!(RightBottom.rotation(), Some(Rotation::D270));
assert_eq!(TopLeft.rotation(), TopRight.rotation());
assert_ne!(TopLeft, TopRight);
}
#[test]
fn a_transform_the_vocabulary_cannot_name_is_carried_not_collapsed() {
let odd = [46_341, 46_341, -46_341, 46_341]; let words: [i32; 9] = [odd[0], odd[1], 0, odd[2], odd[3], 0, 0, 0, 1 << 30];
let read =
ImageOrientation::from_display_matrix(&display_matrix(odd)).expect("readable, just unnamed");
assert_eq!(read, ImageOrientation::Other(words));
assert_eq!(read.to_exif_code(), None, "there is no tag to invent");
assert_eq!(read.rotation(), None, "it is not a quarter turn");
assert_eq!(read.linear(), odd, "the linear projection still answers");
assert_eq!(read.matrix(), words, "and nothing was dropped");
assert!(!read.is_mirrored());
assert!(ImageOrientation::Other([65536, 0, 0, 0, -65536, 0, 0, 0, 1 << 30]).is_mirrored());
}
#[test]
fn a_noncanonical_word_keeps_a_matrix_out_of_the_named_variants() {
let named = ImageOrientation::RightTop;
let canonical = named.matrix();
assert_eq!(
ImageOrientation::from_display_matrix(&words_to_bytes(canonical)),
Some(named),
"the canonical matrix must still be named",
);
for index in [2usize, 5, 6, 7, 8] {
let mut forged = canonical;
forged[index] = if index == 8 { 1 << 29 } else { 4_096 };
let read = ImageOrientation::from_display_matrix(&words_to_bytes(forged))
.expect("nine words are readable");
assert_eq!(
read,
ImageOrientation::Other(forged),
"word {index} was collapsed into a named variant",
);
assert_eq!(read.to_exif_code(), None, "word {index}");
assert_eq!(read.matrix(), forged, "word {index} round-trips whole");
assert_eq!(read.linear(), named.linear(), "word {index}");
}
}
#[test]
fn the_escape_round_trips_every_word_losslessly() {
let words: [i32; 9] = [1, -2, 3, -4, 5, -6, i32::MIN, i32::MAX, 0];
let read = ImageOrientation::from_display_matrix(&words_to_bytes(words)).expect("readable");
assert_eq!(read, ImageOrientation::Other(words));
assert_eq!(read.matrix(), words);
let again =
ImageOrientation::from_display_matrix(&words_to_bytes(read.matrix())).expect("readable");
assert_eq!(again, read);
}
#[test]
fn every_named_orientation_reconstructs_its_canonical_matrix() {
for (tag, orientation, linear) in MEASURED {
let matrix = orientation.matrix();
assert_eq!(
[matrix[0], matrix[1], matrix[3], matrix[4]],
linear,
"tag {tag}",
);
assert_eq!(
[matrix[2], matrix[5], matrix[6], matrix[7]],
[0, 0, 0, 0],
"tag {tag}: no translation, no perspective",
);
assert_eq!(matrix[8], 1 << 30, "tag {tag}: unity `w`");
assert_eq!(
ImageOrientation::from_display_matrix(&words_to_bytes(matrix)),
Some(orientation),
"tag {tag}",
);
}
}
#[test]
fn a_malformed_matrix_is_no_orientation_rather_than_a_guessed_one() {
assert_eq!(ImageOrientation::from_display_matrix(&[]), None);
assert_eq!(ImageOrientation::from_display_matrix(&[0u8; 16]), None);
assert_eq!(ImageOrientation::from_display_matrix(&[0u8; 40]), None);
assert_eq!(
ImageOrientation::DISPLAY_MATRIX_BYTES,
36,
"nine int32, per libavutil/display.h",
);
assert!(ImageOrientation::from_display_matrix(&[0u8; 36]).is_some());
}
#[test]
fn an_out_of_range_exif_tag_is_refused_not_clamped() {
for code in [0u16, 9, 255, u16::MAX] {
assert_eq!(ImageOrientation::from_exif_code(code), None, "code {code}");
}
}
#[test]
fn the_orientation_seat_rides_the_image_extras() {
let extra = ImageFrameExtra::default();
assert_eq!(extra.orientation(), None, "absent until a file says");
let carried = ImageFrameExtra::new().with_orientation(Some(ImageOrientation::RightTop));
assert_eq!(carried.orientation(), Some(ImageOrientation::RightTop));
let mut mutated = carried.clone();
mutated.set_orientation(None);
assert_eq!(mutated.orientation(), None);
assert_eq!(carried.orientation(), Some(ImageOrientation::RightTop));
}
#[test]
fn the_image_household_is_one_seat() {
let extra = ImageFrameExtra::default();
assert!(extra.side_data().is_empty());
let carried = ImageFrameExtra::new().with_side_data(vec![SideDataEntry::new(
3,
FfmpegBytes::copy_from_slice(&[1]),
)]);
assert_eq!(carried.side_data().len(), 1);
assert_eq!(carried.side_data()[0].kind(), 3);
let mut mutated = carried.clone();
mutated.set_side_data(Vec::new());
assert!(mutated.side_data().is_empty());
assert_eq!(carried.side_data().len(), 1);
}
#[test]
fn content_light_level_default_is_zero() {
let cll = ContentLightLevel::default();
assert_eq!(cll.max_cll(), 0);
assert_eq!(cll.max_fall(), 0);
}
#[test]
fn builders_chain() {
let v = VideoPacketExtra::new(7)
.with_byte_pos(Some(1234))
.with_side_data(vec![SideDataEntry::new(
1,
FfmpegBytes::copy_from_slice(&[0xAB]),
)]);
assert_eq!(v.stream_index(), 7);
assert_eq!(v.byte_pos(), Some(1234));
assert_eq!(v.side_data().len(), 1);
}
#[test]
fn setters_chain() {
let mut v = VideoPacketExtra::default();
v.set_stream_index(3).set_byte_pos(Some(99));
assert_eq!(v.stream_index(), 3);
assert_eq!(v.byte_pos(), Some(99));
}
}