1use std::vec::Vec;
24
25use mediaframe::frame::Rotation;
26
27use crate::FfmpegBytes;
28
29use derive_more::IsVariant;
30use ffmpeg_next::codec::Parameters;
31
32use crate::{
33 demuxer::{DemuxError, ParametersAlloc, ParametersCopy, ParametersMissing, ParametersTooLarge},
34 ticket::CodecTicket,
35};
36
37#[derive(Clone, Debug, Default)]
39pub struct VideoPacketExtra {
40 stream_index: i32,
41 byte_pos: Option<i64>,
42 side_data: Vec<SideDataEntry>,
43}
44
45impl VideoPacketExtra {
46 #[cfg_attr(not(tarpaulin), inline(always))]
49 pub const fn new(stream_index: i32) -> Self {
50 Self {
51 stream_index,
52 byte_pos: None,
53 side_data: Vec::new(),
54 }
55 }
56
57 #[cfg_attr(not(tarpaulin), inline(always))]
59 pub const fn stream_index(&self) -> i32 {
60 self.stream_index
61 }
62
63 #[cfg_attr(not(tarpaulin), inline(always))]
66 pub const fn byte_pos(&self) -> Option<i64> {
67 self.byte_pos
68 }
69
70 #[cfg_attr(not(tarpaulin), inline(always))]
72 pub fn side_data(&self) -> &[SideDataEntry] {
73 self.side_data.as_slice()
74 }
75
76 #[cfg_attr(not(tarpaulin), inline(always))]
78 #[must_use]
79 pub const fn with_stream_index(mut self, value: i32) -> Self {
80 self.stream_index = value;
81 self
82 }
83 #[cfg_attr(not(tarpaulin), inline(always))]
85 #[must_use]
86 pub const fn with_byte_pos(mut self, value: Option<i64>) -> Self {
87 self.byte_pos = value;
88 self
89 }
90 #[cfg_attr(not(tarpaulin), inline(always))]
92 #[must_use]
93 pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
94 self.side_data = value;
95 self
96 }
97
98 #[cfg_attr(not(tarpaulin), inline(always))]
100 pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
101 self.stream_index = value;
102 self
103 }
104 #[cfg_attr(not(tarpaulin), inline(always))]
106 pub const fn set_byte_pos(&mut self, value: Option<i64>) -> &mut Self {
107 self.byte_pos = value;
108 self
109 }
110 #[cfg_attr(not(tarpaulin), inline(always))]
112 pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
113 self.side_data = value;
114 self
115 }
116}
117
118#[derive(Clone, Debug, Default)]
121pub struct VideoFrameExtra {
122 sample_aspect_ratio: Option<(u32, u32)>,
123 picture_type: PictureType,
124 key_frame: bool,
125 interlaced: bool,
126 top_field_first: bool,
127 best_effort_timestamp: Option<i64>,
128 mastering_display: Option<MasteringDisplay>,
129 content_light_level: Option<ContentLightLevel>,
130 smpte_timecode: Vec<u32>,
131 side_data: Vec<SideDataEntry>,
132}
133
134impl VideoFrameExtra {
135 #[cfg_attr(not(tarpaulin), inline(always))]
137 pub const fn new() -> Self {
138 Self {
139 sample_aspect_ratio: None,
140 picture_type: PictureType::Unspecified,
141 key_frame: false,
142 interlaced: false,
143 top_field_first: false,
144 best_effort_timestamp: None,
145 mastering_display: None,
146 content_light_level: None,
147 smpte_timecode: Vec::new(),
148 side_data: Vec::new(),
149 }
150 }
151
152 #[cfg_attr(not(tarpaulin), inline(always))]
155 pub const fn sample_aspect_ratio(&self) -> Option<(u32, u32)> {
156 self.sample_aspect_ratio
157 }
158 #[cfg_attr(not(tarpaulin), inline(always))]
160 pub const fn picture_type(&self) -> PictureType {
161 self.picture_type
162 }
163 #[cfg_attr(not(tarpaulin), inline(always))]
165 pub const fn key_frame(&self) -> bool {
166 self.key_frame
167 }
168 #[cfg_attr(not(tarpaulin), inline(always))]
170 pub const fn interlaced(&self) -> bool {
171 self.interlaced
172 }
173 #[cfg_attr(not(tarpaulin), inline(always))]
175 pub const fn top_field_first(&self) -> bool {
176 self.top_field_first
177 }
178 #[cfg_attr(not(tarpaulin), inline(always))]
180 pub const fn best_effort_timestamp(&self) -> Option<i64> {
181 self.best_effort_timestamp
182 }
183 #[cfg_attr(not(tarpaulin), inline(always))]
185 pub const fn mastering_display(&self) -> Option<MasteringDisplay> {
186 self.mastering_display
187 }
188 #[cfg_attr(not(tarpaulin), inline(always))]
190 pub const fn content_light_level(&self) -> Option<ContentLightLevel> {
191 self.content_light_level
192 }
193 #[cfg_attr(not(tarpaulin), inline(always))]
195 pub fn smpte_timecode(&self) -> &[u32] {
196 self.smpte_timecode.as_slice()
197 }
198 #[cfg_attr(not(tarpaulin), inline(always))]
200 pub fn side_data(&self) -> &[SideDataEntry] {
201 self.side_data.as_slice()
202 }
203
204 #[cfg_attr(not(tarpaulin), inline(always))]
206 pub const fn with_sample_aspect_ratio(mut self, value: Option<(u32, u32)>) -> Self {
207 self.sample_aspect_ratio = value;
208 self
209 }
210 #[cfg_attr(not(tarpaulin), inline(always))]
212 #[must_use]
213 pub const fn with_picture_type(mut self, value: PictureType) -> Self {
214 self.picture_type = value;
215 self
216 }
217 #[cfg_attr(not(tarpaulin), inline(always))]
219 #[must_use]
220 pub const fn with_key_frame(mut self, value: bool) -> Self {
221 self.key_frame = value;
222 self
223 }
224 #[cfg_attr(not(tarpaulin), inline(always))]
226 #[must_use]
227 pub const fn with_interlaced(mut self, value: bool) -> Self {
228 self.interlaced = value;
229 self
230 }
231 #[cfg_attr(not(tarpaulin), inline(always))]
233 #[must_use]
234 pub const fn with_top_field_first(mut self, value: bool) -> Self {
235 self.top_field_first = value;
236 self
237 }
238 #[cfg_attr(not(tarpaulin), inline(always))]
240 #[must_use]
241 pub const fn with_best_effort_timestamp(mut self, value: Option<i64>) -> Self {
242 self.best_effort_timestamp = value;
243 self
244 }
245 #[cfg_attr(not(tarpaulin), inline(always))]
247 #[must_use]
248 pub const fn with_mastering_display(mut self, value: Option<MasteringDisplay>) -> Self {
249 self.mastering_display = value;
250 self
251 }
252 #[cfg_attr(not(tarpaulin), inline(always))]
254 #[must_use]
255 pub const fn with_content_light_level(mut self, value: Option<ContentLightLevel>) -> Self {
256 self.content_light_level = value;
257 self
258 }
259 #[cfg_attr(not(tarpaulin), inline(always))]
261 #[must_use]
262 pub fn with_smpte_timecode(mut self, value: Vec<u32>) -> Self {
263 self.smpte_timecode = value;
264 self
265 }
266 #[cfg_attr(not(tarpaulin), inline(always))]
268 #[must_use]
269 pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
270 self.side_data = value;
271 self
272 }
273
274 #[cfg_attr(not(tarpaulin), inline(always))]
276 pub const fn set_sample_aspect_ratio(&mut self, value: Option<(u32, u32)>) -> &mut Self {
277 self.sample_aspect_ratio = value;
278 self
279 }
280 #[cfg_attr(not(tarpaulin), inline(always))]
282 pub const fn set_picture_type(&mut self, value: PictureType) -> &mut Self {
283 self.picture_type = value;
284 self
285 }
286 #[cfg_attr(not(tarpaulin), inline(always))]
288 pub const fn set_key_frame(&mut self, value: bool) -> &mut Self {
289 self.key_frame = value;
290 self
291 }
292 #[cfg_attr(not(tarpaulin), inline(always))]
294 pub const fn set_interlaced(&mut self, value: bool) -> &mut Self {
295 self.interlaced = value;
296 self
297 }
298 #[cfg_attr(not(tarpaulin), inline(always))]
300 pub const fn set_top_field_first(&mut self, value: bool) -> &mut Self {
301 self.top_field_first = value;
302 self
303 }
304 #[cfg_attr(not(tarpaulin), inline(always))]
306 pub const fn set_best_effort_timestamp(&mut self, value: Option<i64>) -> &mut Self {
307 self.best_effort_timestamp = value;
308 self
309 }
310 #[cfg_attr(not(tarpaulin), inline(always))]
312 pub const fn set_mastering_display(&mut self, value: Option<MasteringDisplay>) -> &mut Self {
313 self.mastering_display = value;
314 self
315 }
316 #[cfg_attr(not(tarpaulin), inline(always))]
318 pub const fn set_content_light_level(&mut self, value: Option<ContentLightLevel>) -> &mut Self {
319 self.content_light_level = value;
320 self
321 }
322 #[cfg_attr(not(tarpaulin), inline(always))]
324 pub fn set_smpte_timecode(&mut self, value: Vec<u32>) -> &mut Self {
325 self.smpte_timecode = value;
326 self
327 }
328 #[cfg_attr(not(tarpaulin), inline(always))]
330 pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
331 self.side_data = value;
332 self
333 }
334}
335
336#[derive(Clone, Debug, Default)]
338pub struct AudioPacketExtra {
339 stream_index: i32,
340 byte_pos: Option<i64>,
341 side_data: Vec<SideDataEntry>,
342}
343
344impl AudioPacketExtra {
345 #[cfg_attr(not(tarpaulin), inline(always))]
347 pub const fn new(stream_index: i32) -> Self {
348 Self {
349 stream_index,
350 byte_pos: None,
351 side_data: Vec::new(),
352 }
353 }
354
355 #[cfg_attr(not(tarpaulin), inline(always))]
357 pub const fn stream_index(&self) -> i32 {
358 self.stream_index
359 }
360 #[cfg_attr(not(tarpaulin), inline(always))]
362 pub const fn byte_pos(&self) -> Option<i64> {
363 self.byte_pos
364 }
365 #[cfg_attr(not(tarpaulin), inline(always))]
367 pub fn side_data(&self) -> &[SideDataEntry] {
368 self.side_data.as_slice()
369 }
370
371 #[cfg_attr(not(tarpaulin), inline(always))]
373 #[must_use]
374 pub const fn with_stream_index(mut self, value: i32) -> Self {
375 self.stream_index = value;
376 self
377 }
378 #[cfg_attr(not(tarpaulin), inline(always))]
380 #[must_use]
381 pub const fn with_byte_pos(mut self, value: Option<i64>) -> Self {
382 self.byte_pos = value;
383 self
384 }
385 #[cfg_attr(not(tarpaulin), inline(always))]
387 #[must_use]
388 pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
389 self.side_data = value;
390 self
391 }
392
393 #[cfg_attr(not(tarpaulin), inline(always))]
395 pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
396 self.stream_index = value;
397 self
398 }
399 #[cfg_attr(not(tarpaulin), inline(always))]
401 pub const fn set_byte_pos(&mut self, value: Option<i64>) -> &mut Self {
402 self.byte_pos = value;
403 self
404 }
405 #[cfg_attr(not(tarpaulin), inline(always))]
407 pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
408 self.side_data = value;
409 self
410 }
411}
412
413#[derive(Clone, Debug, Default)]
415pub struct AudioFrameExtra {
416 best_effort_timestamp: Option<i64>,
417 side_data: Vec<SideDataEntry>,
418}
419
420impl AudioFrameExtra {
421 #[cfg_attr(not(tarpaulin), inline(always))]
423 pub const fn new() -> Self {
424 Self {
425 best_effort_timestamp: None,
426 side_data: Vec::new(),
427 }
428 }
429
430 #[cfg_attr(not(tarpaulin), inline(always))]
432 pub const fn best_effort_timestamp(&self) -> Option<i64> {
433 self.best_effort_timestamp
434 }
435 #[cfg_attr(not(tarpaulin), inline(always))]
437 pub fn side_data(&self) -> &[SideDataEntry] {
438 self.side_data.as_slice()
439 }
440
441 #[cfg_attr(not(tarpaulin), inline(always))]
443 #[must_use]
444 pub const fn with_best_effort_timestamp(mut self, value: Option<i64>) -> Self {
445 self.best_effort_timestamp = value;
446 self
447 }
448 #[cfg_attr(not(tarpaulin), inline(always))]
450 #[must_use]
451 pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
452 self.side_data = value;
453 self
454 }
455
456 #[cfg_attr(not(tarpaulin), inline(always))]
458 pub const fn set_best_effort_timestamp(&mut self, value: Option<i64>) -> &mut Self {
459 self.best_effort_timestamp = value;
460 self
461 }
462 #[cfg_attr(not(tarpaulin), inline(always))]
464 pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
465 self.side_data = value;
466 self
467 }
468}
469
470#[derive(Clone, Debug, Default)]
472pub struct SubtitlePacketExtra {
473 stream_index: i32,
474 language: Option<[u8; 3]>,
475 forced: bool,
476 side_data: Vec<SideDataEntry>,
477}
478
479impl SubtitlePacketExtra {
480 #[cfg_attr(not(tarpaulin), inline(always))]
483 pub const fn new(stream_index: i32) -> Self {
484 Self {
485 stream_index,
486 language: None,
487 forced: false,
488 side_data: Vec::new(),
489 }
490 }
491
492 #[cfg_attr(not(tarpaulin), inline(always))]
494 pub const fn stream_index(&self) -> i32 {
495 self.stream_index
496 }
497 #[cfg_attr(not(tarpaulin), inline(always))]
499 pub const fn language(&self) -> Option<[u8; 3]> {
500 self.language
501 }
502 #[cfg_attr(not(tarpaulin), inline(always))]
504 pub const fn forced(&self) -> bool {
505 self.forced
506 }
507 #[cfg_attr(not(tarpaulin), inline(always))]
514 pub fn side_data(&self) -> &[SideDataEntry] {
515 self.side_data.as_slice()
516 }
517
518 #[cfg_attr(not(tarpaulin), inline(always))]
520 #[must_use]
521 pub const fn with_stream_index(mut self, value: i32) -> Self {
522 self.stream_index = value;
523 self
524 }
525 #[cfg_attr(not(tarpaulin), inline(always))]
527 #[must_use]
528 pub const fn with_language(mut self, value: Option<[u8; 3]>) -> Self {
529 self.language = value;
530 self
531 }
532 #[cfg_attr(not(tarpaulin), inline(always))]
534 #[must_use]
535 pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
536 self.side_data = value;
537 self
538 }
539 #[cfg_attr(not(tarpaulin), inline(always))]
541 pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
542 self.side_data = value;
543 self
544 }
545 #[cfg_attr(not(tarpaulin), inline(always))]
547 #[must_use]
548 pub const fn with_forced(mut self, value: bool) -> Self {
549 self.forced = value;
550 self
551 }
552
553 #[cfg_attr(not(tarpaulin), inline(always))]
555 pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
556 self.stream_index = value;
557 self
558 }
559 #[cfg_attr(not(tarpaulin), inline(always))]
561 pub const fn set_language(&mut self, value: Option<[u8; 3]>) -> &mut Self {
562 self.language = value;
563 self
564 }
565 #[cfg_attr(not(tarpaulin), inline(always))]
567 pub const fn set_forced(&mut self, value: bool) -> &mut Self {
568 self.forced = value;
569 self
570 }
571}
572
573#[derive(Clone, Debug, Default)]
575pub struct SubtitleFrameExtra {
576 start_display_time: u32,
577 end_display_time: u32,
578}
579
580impl SubtitleFrameExtra {
581 #[cfg_attr(not(tarpaulin), inline(always))]
583 pub const fn new(start_display_time: u32, end_display_time: u32) -> Self {
584 Self {
585 start_display_time,
586 end_display_time,
587 }
588 }
589
590 #[cfg_attr(not(tarpaulin), inline(always))]
592 pub const fn start_display_time(&self) -> u32 {
593 self.start_display_time
594 }
595 #[cfg_attr(not(tarpaulin), inline(always))]
597 pub const fn end_display_time(&self) -> u32 {
598 self.end_display_time
599 }
600
601 #[cfg_attr(not(tarpaulin), inline(always))]
603 #[must_use]
604 pub const fn with_start_display_time(mut self, value: u32) -> Self {
605 self.start_display_time = value;
606 self
607 }
608 #[cfg_attr(not(tarpaulin), inline(always))]
610 #[must_use]
611 pub const fn with_end_display_time(mut self, value: u32) -> Self {
612 self.end_display_time = value;
613 self
614 }
615
616 #[cfg_attr(not(tarpaulin), inline(always))]
618 pub const fn set_start_display_time(&mut self, value: u32) -> &mut Self {
619 self.start_display_time = value;
620 self
621 }
622 #[cfg_attr(not(tarpaulin), inline(always))]
624 pub const fn set_end_display_time(&mut self, value: u32) -> &mut Self {
625 self.end_display_time = value;
626 self
627 }
628}
629
630#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, IsVariant)]
672#[non_exhaustive]
673pub enum ImageOrientation {
674 #[default]
677 TopLeft,
678 TopRight,
680 BottomRight,
682 BottomLeft,
684 LeftTop,
686 RightTop,
688 RightBottom,
691 LeftBottom,
693 Other([i32; 9]),
710}
711
712impl ImageOrientation {
713 const UNIT: i32 = 1 << 16;
716
717 const PERSPECTIVE_UNIT: i32 = 1 << 30;
721
722 pub const DISPLAY_MATRIX_BYTES: usize = 9 * core::mem::size_of::<i32>();
725
726 #[cfg_attr(not(tarpaulin), inline(always))]
743 pub const fn matrix(&self) -> [i32; 9] {
744 match self {
745 Self::Other(matrix) => *matrix,
746 named => {
747 let [a, b, c, d] = named.linear();
748 [a, b, 0, c, d, 0, 0, 0, Self::PERSPECTIVE_UNIT]
749 }
750 }
751 }
752
753 pub fn from_display_matrix(bytes: &[u8]) -> Option<Self> {
764 if bytes.len() != Self::DISPLAY_MATRIX_BYTES {
765 return None;
766 }
767 let mut matrix = [0i32; 9];
768 for (index, word) in matrix.iter_mut().enumerate() {
769 let mut raw = [0u8; 4];
770 raw.copy_from_slice(&bytes[index * 4..index * 4 + 4]);
771 *word = i32::from_ne_bytes(raw);
772 }
773 Some(Self::from_matrix(matrix))
774 }
775
776 fn from_matrix(matrix: [i32; 9]) -> Self {
786 const P: i32 = ImageOrientation::UNIT;
787 const N: i32 = -ImageOrientation::UNIT;
788 const W: i32 = ImageOrientation::PERSPECTIVE_UNIT;
789 match matrix {
790 [P, 0, 0, 0, P, 0, 0, 0, W] => Self::TopLeft,
791 [N, 0, 0, 0, P, 0, 0, 0, W] => Self::TopRight,
792 [N, 0, 0, 0, N, 0, 0, 0, W] => Self::BottomRight,
793 [P, 0, 0, 0, N, 0, 0, 0, W] => Self::BottomLeft,
794 [0, P, 0, P, 0, 0, 0, 0, W] => Self::LeftTop,
795 [0, P, 0, N, 0, 0, 0, 0, W] => Self::RightTop,
796 [0, N, 0, N, 0, 0, 0, 0, W] => Self::RightBottom,
797 [0, N, 0, P, 0, 0, 0, 0, W] => Self::LeftBottom,
798 other => Self::Other(other),
799 }
800 }
801
802 #[cfg_attr(not(tarpaulin), inline(always))]
807 pub const fn to_exif_code(&self) -> Option<u16> {
808 Some(match self {
809 Self::TopLeft => 1,
810 Self::TopRight => 2,
811 Self::BottomRight => 3,
812 Self::BottomLeft => 4,
813 Self::LeftTop => 5,
814 Self::RightTop => 6,
815 Self::RightBottom => 7,
816 Self::LeftBottom => 8,
817 Self::Other(_) => return None,
818 })
819 }
820
821 #[cfg_attr(not(tarpaulin), inline(always))]
825 pub const fn from_exif_code(code: u16) -> Option<Self> {
826 Some(match code {
827 1 => Self::TopLeft,
828 2 => Self::TopRight,
829 3 => Self::BottomRight,
830 4 => Self::BottomLeft,
831 5 => Self::LeftTop,
832 6 => Self::RightTop,
833 7 => Self::RightBottom,
834 8 => Self::LeftBottom,
835 _ => return None,
836 })
837 }
838
839 #[cfg_attr(not(tarpaulin), inline(always))]
845 pub const fn is_mirrored(&self) -> bool {
846 let [a, b, c, d] = self.linear();
847 (a as i64) * (d as i64) - (b as i64) * (c as i64) < 0
851 }
852
853 #[cfg_attr(not(tarpaulin), inline(always))]
866 pub const fn rotation(&self) -> Option<Rotation> {
867 Some(match self {
868 Self::TopLeft | Self::TopRight => Rotation::D0,
869 Self::RightTop | Self::LeftTop => Rotation::D90,
870 Self::BottomRight | Self::BottomLeft => Rotation::D180,
871 Self::LeftBottom | Self::RightBottom => Rotation::D270,
872 Self::Other(_) => return None,
873 })
874 }
875
876 #[cfg_attr(not(tarpaulin), inline(always))]
886 pub const fn linear(&self) -> [i32; 4] {
887 const P: i32 = ImageOrientation::UNIT;
888 const N: i32 = -ImageOrientation::UNIT;
889 match self {
890 Self::TopLeft => [P, 0, 0, P],
891 Self::TopRight => [N, 0, 0, P],
892 Self::BottomRight => [N, 0, 0, N],
893 Self::BottomLeft => [P, 0, 0, N],
894 Self::LeftTop => [0, P, P, 0],
895 Self::RightTop => [0, P, N, 0],
896 Self::RightBottom => [0, N, N, 0],
897 Self::LeftBottom => [0, N, P, 0],
898 Self::Other(matrix) => [matrix[0], matrix[1], matrix[3], matrix[4]],
899 }
900 }
901}
902
903#[derive(Clone, Debug, Default)]
926pub struct ImageFrameExtra {
927 orientation: Option<ImageOrientation>,
928 side_data: Vec<SideDataEntry>,
929}
930
931impl ImageFrameExtra {
932 #[cfg_attr(not(tarpaulin), inline(always))]
935 pub const fn new() -> Self {
936 Self {
937 orientation: None,
938 side_data: Vec::new(),
939 }
940 }
941
942 #[cfg_attr(not(tarpaulin), inline(always))]
953 pub const fn orientation(&self) -> Option<ImageOrientation> {
954 self.orientation
955 }
956
957 #[cfg_attr(not(tarpaulin), inline(always))]
960 pub fn side_data(&self) -> &[SideDataEntry] {
961 self.side_data.as_slice()
962 }
963
964 #[cfg_attr(not(tarpaulin), inline(always))]
966 #[must_use]
967 pub const fn with_orientation(mut self, value: Option<ImageOrientation>) -> Self {
968 self.orientation = value;
969 self
970 }
971
972 #[cfg_attr(not(tarpaulin), inline(always))]
974 #[must_use]
975 pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
976 self.side_data = value;
977 self
978 }
979
980 #[cfg_attr(not(tarpaulin), inline(always))]
982 pub const fn set_orientation(&mut self, value: Option<ImageOrientation>) -> &mut Self {
983 self.orientation = value;
984 self
985 }
986
987 #[cfg_attr(not(tarpaulin), inline(always))]
989 pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
990 self.side_data = value;
991 self
992 }
993}
994
995#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, IsVariant)]
997#[non_exhaustive]
998pub enum PictureType {
999 #[default]
1001 Unspecified,
1002 I,
1004 P,
1006 B,
1008 S,
1010 Si,
1012 Sp,
1014 Bi,
1016}
1017
1018#[derive(Clone, Debug)]
1026pub struct SideDataEntry {
1027 kind: i32,
1028 data: FfmpegBytes,
1029}
1030
1031impl SideDataEntry {
1032 #[cfg_attr(not(tarpaulin), inline(always))]
1034 pub const fn new(kind: i32, data: FfmpegBytes) -> Self {
1035 Self { kind, data }
1036 }
1037
1038 #[cfg_attr(not(tarpaulin), inline(always))]
1040 pub const fn kind(&self) -> i32 {
1041 self.kind
1042 }
1043 #[cfg_attr(not(tarpaulin), inline(always))]
1045 pub fn data(&self) -> &[u8] {
1046 self.data.as_slice()
1047 }
1048 #[cfg_attr(not(tarpaulin), inline(always))]
1051 pub const fn data_ref(&self) -> &FfmpegBytes {
1052 &self.data
1053 }
1054
1055 #[cfg_attr(not(tarpaulin), inline(always))]
1057 #[must_use]
1058 pub const fn with_kind(mut self, value: i32) -> Self {
1059 self.kind = value;
1060 self
1061 }
1062 #[cfg_attr(not(tarpaulin), inline(always))]
1064 #[must_use]
1065 pub fn with_data(mut self, value: FfmpegBytes) -> Self {
1066 self.data = value;
1067 self
1068 }
1069
1070 #[cfg_attr(not(tarpaulin), inline(always))]
1072 pub const fn set_kind(&mut self, value: i32) -> &mut Self {
1073 self.kind = value;
1074 self
1075 }
1076 #[cfg_attr(not(tarpaulin), inline(always))]
1078 pub fn set_data(&mut self, value: FfmpegBytes) -> &mut Self {
1079 self.data = value;
1080 self
1081 }
1082}
1083
1084#[derive(Copy, Clone, Debug, PartialEq)]
1086pub struct MasteringDisplay {
1087 display_primaries: [(u32, u32); 3],
1088 white_point: (u32, u32),
1089 max_luminance: (u32, u32),
1090 min_luminance: (u32, u32),
1091}
1092
1093impl MasteringDisplay {
1094 #[cfg_attr(not(tarpaulin), inline(always))]
1096 pub const fn new(
1097 display_primaries: [(u32, u32); 3],
1098 white_point: (u32, u32),
1099 max_luminance: (u32, u32),
1100 min_luminance: (u32, u32),
1101 ) -> Self {
1102 Self {
1103 display_primaries,
1104 white_point,
1105 max_luminance,
1106 min_luminance,
1107 }
1108 }
1109
1110 #[cfg_attr(not(tarpaulin), inline(always))]
1125 pub const fn display_primaries(&self) -> [(u32, u32); 3] {
1126 self.display_primaries
1127 }
1128 #[cfg_attr(not(tarpaulin), inline(always))]
1131 pub const fn white_point(&self) -> (u32, u32) {
1132 self.white_point
1133 }
1134 #[cfg_attr(not(tarpaulin), inline(always))]
1136 pub const fn max_luminance(&self) -> (u32, u32) {
1137 self.max_luminance
1138 }
1139 #[cfg_attr(not(tarpaulin), inline(always))]
1141 pub const fn min_luminance(&self) -> (u32, u32) {
1142 self.min_luminance
1143 }
1144
1145 #[cfg_attr(not(tarpaulin), inline(always))]
1147 pub const fn with_display_primaries(mut self, value: [(u32, u32); 3]) -> Self {
1148 self.display_primaries = value;
1149 self
1150 }
1151 #[cfg_attr(not(tarpaulin), inline(always))]
1153 pub const fn with_white_point(mut self, value: (u32, u32)) -> Self {
1154 self.white_point = value;
1155 self
1156 }
1157 #[cfg_attr(not(tarpaulin), inline(always))]
1159 pub const fn with_max_luminance(mut self, value: (u32, u32)) -> Self {
1160 self.max_luminance = value;
1161 self
1162 }
1163 #[cfg_attr(not(tarpaulin), inline(always))]
1165 pub const fn with_min_luminance(mut self, value: (u32, u32)) -> Self {
1166 self.min_luminance = value;
1167 self
1168 }
1169
1170 #[cfg_attr(not(tarpaulin), inline(always))]
1172 pub const fn set_display_primaries(&mut self, value: [(u32, u32); 3]) -> &mut Self {
1173 self.display_primaries = value;
1174 self
1175 }
1176 #[cfg_attr(not(tarpaulin), inline(always))]
1178 pub const fn set_white_point(&mut self, value: (u32, u32)) -> &mut Self {
1179 self.white_point = value;
1180 self
1181 }
1182 #[cfg_attr(not(tarpaulin), inline(always))]
1184 pub const fn set_max_luminance(&mut self, value: (u32, u32)) -> &mut Self {
1185 self.max_luminance = value;
1186 self
1187 }
1188 #[cfg_attr(not(tarpaulin), inline(always))]
1190 pub const fn set_min_luminance(&mut self, value: (u32, u32)) -> &mut Self {
1191 self.min_luminance = value;
1192 self
1193 }
1194}
1195
1196#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
1198pub struct ContentLightLevel {
1199 max_cll: u32,
1200 max_fall: u32,
1201}
1202
1203impl ContentLightLevel {
1204 #[cfg_attr(not(tarpaulin), inline(always))]
1206 pub const fn new(max_cll: u32, max_fall: u32) -> Self {
1207 Self { max_cll, max_fall }
1208 }
1209
1210 #[cfg_attr(not(tarpaulin), inline(always))]
1212 pub const fn max_cll(&self) -> u32 {
1213 self.max_cll
1214 }
1215 #[cfg_attr(not(tarpaulin), inline(always))]
1217 pub const fn max_fall(&self) -> u32 {
1218 self.max_fall
1219 }
1220
1221 #[cfg_attr(not(tarpaulin), inline(always))]
1223 #[must_use]
1224 pub const fn with_max_cll(mut self, value: u32) -> Self {
1225 self.max_cll = value;
1226 self
1227 }
1228 #[cfg_attr(not(tarpaulin), inline(always))]
1230 #[must_use]
1231 pub const fn with_max_fall(mut self, value: u32) -> Self {
1232 self.max_fall = value;
1233 self
1234 }
1235
1236 #[cfg_attr(not(tarpaulin), inline(always))]
1238 pub const fn set_max_cll(&mut self, value: u32) -> &mut Self {
1239 self.max_cll = value;
1240 self
1241 }
1242 #[cfg_attr(not(tarpaulin), inline(always))]
1244 pub const fn set_max_fall(&mut self, value: u32) -> &mut Self {
1245 self.max_fall = value;
1246 self
1247 }
1248}
1249
1250#[derive(Clone, Debug, Default)]
1262pub struct DataPacketExtra {
1263 stream_index: i32,
1264 byte_pos: Option<i64>,
1265 side_data: Vec<SideDataEntry>,
1266}
1267
1268impl DataPacketExtra {
1269 #[cfg_attr(not(tarpaulin), inline(always))]
1272 pub const fn new(stream_index: i32) -> Self {
1273 Self {
1274 stream_index,
1275 byte_pos: None,
1276 side_data: Vec::new(),
1277 }
1278 }
1279
1280 #[cfg_attr(not(tarpaulin), inline(always))]
1282 pub const fn stream_index(&self) -> i32 {
1283 self.stream_index
1284 }
1285 #[cfg_attr(not(tarpaulin), inline(always))]
1288 pub const fn byte_pos(&self) -> Option<i64> {
1289 self.byte_pos
1290 }
1291 #[cfg_attr(not(tarpaulin), inline(always))]
1293 pub fn side_data(&self) -> &[SideDataEntry] {
1294 self.side_data.as_slice()
1295 }
1296
1297 #[cfg_attr(not(tarpaulin), inline(always))]
1299 #[must_use]
1300 pub const fn with_stream_index(mut self, value: i32) -> Self {
1301 self.stream_index = value;
1302 self
1303 }
1304 #[cfg_attr(not(tarpaulin), inline(always))]
1306 #[must_use]
1307 pub const fn with_byte_pos(mut self, value: Option<i64>) -> Self {
1308 self.byte_pos = value;
1309 self
1310 }
1311 #[cfg_attr(not(tarpaulin), inline(always))]
1313 #[must_use]
1314 pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
1315 self.side_data = value;
1316 self
1317 }
1318
1319 #[cfg_attr(not(tarpaulin), inline(always))]
1321 pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
1322 self.stream_index = value;
1323 self
1324 }
1325 #[cfg_attr(not(tarpaulin), inline(always))]
1327 pub const fn set_byte_pos(&mut self, value: Option<i64>) -> &mut Self {
1328 self.byte_pos = value;
1329 self
1330 }
1331 #[cfg_attr(not(tarpaulin), inline(always))]
1333 pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
1334 self.side_data = value;
1335 self
1336 }
1337}
1338
1339#[derive(Clone, Debug, Default)]
1349pub struct AttachmentPacketExtra {
1350 stream_index: i32,
1351 synthesized: bool,
1352}
1353
1354impl AttachmentPacketExtra {
1355 #[cfg_attr(not(tarpaulin), inline(always))]
1358 pub const fn new(stream_index: i32) -> Self {
1359 Self {
1360 stream_index,
1361 synthesized: false,
1362 }
1363 }
1364
1365 #[cfg_attr(not(tarpaulin), inline(always))]
1367 pub const fn stream_index(&self) -> i32 {
1368 self.stream_index
1369 }
1370 #[cfg_attr(not(tarpaulin), inline(always))]
1373 pub const fn synthesized(&self) -> bool {
1374 self.synthesized
1375 }
1376
1377 #[cfg_attr(not(tarpaulin), inline(always))]
1379 #[must_use]
1380 pub const fn with_stream_index(mut self, value: i32) -> Self {
1381 self.stream_index = value;
1382 self
1383 }
1384 #[cfg_attr(not(tarpaulin), inline(always))]
1386 #[must_use]
1387 pub const fn with_synthesized(mut self, value: bool) -> Self {
1388 self.synthesized = value;
1389 self
1390 }
1391
1392 #[cfg_attr(not(tarpaulin), inline(always))]
1394 pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
1395 self.stream_index = value;
1396 self
1397 }
1398 #[cfg_attr(not(tarpaulin), inline(always))]
1400 pub const fn set_synthesized(&mut self, value: bool) -> &mut Self {
1401 self.synthesized = value;
1402 self
1403 }
1404}
1405
1406#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
1412pub struct ParameterFootprint {
1413 extradata: usize,
1414 extradata_payload: usize,
1415 coded_side_data: usize,
1416 channel_map: usize,
1417}
1418
1419impl ParameterFootprint {
1420 #[cfg_attr(not(tarpaulin), inline(always))]
1429 pub const fn extradata(&self) -> usize {
1430 self.extradata
1431 }
1432
1433 #[cfg_attr(not(tarpaulin), inline(always))]
1444 pub const fn extradata_payload(&self) -> usize {
1445 self.extradata_payload
1446 }
1447 #[cfg_attr(not(tarpaulin), inline(always))]
1450 pub const fn coded_side_data(&self) -> usize {
1451 self.coded_side_data
1452 }
1453 #[cfg_attr(not(tarpaulin), inline(always))]
1456 pub const fn channel_map(&self) -> usize {
1457 self.channel_map
1458 }
1459 #[cfg_attr(not(tarpaulin), inline(always))]
1464 pub const fn total(&self) -> Option<usize> {
1465 match self.extradata.checked_add(self.coded_side_data) {
1466 Some(sum) => sum.checked_add(self.channel_map),
1467 None => None,
1468 }
1469 }
1470
1471 #[cfg_attr(not(tarpaulin), inline(always))]
1479 pub const fn total_without_extradata(&self) -> Option<usize> {
1480 self.coded_side_data.checked_add(self.channel_map)
1481 }
1482}
1483
1484#[cfg(target_pointer_width = "64")]
1504const _: () = {
1505 assert!(
1506 core::mem::size_of::<ffmpeg_next::ffi::AVCodecParameters>() == 184,
1507 "AVCodecParameters changed shape — re-census its fields against \
1508 `measure_parameters`, `bounded_clone_parameters` and `ticket::CodecTicket` \
1509 before raising this",
1510 );
1511};
1512
1513pub(crate) unsafe fn measure_parameters(
1525 par: *const ffmpeg_next::ffi::AVCodecParameters,
1526) -> Option<ParameterFootprint> {
1527 let extradata_payload = if unsafe { (*par).extradata }.is_null() {
1530 0
1531 } else {
1532 usize::try_from(unsafe { (*par).extradata_size }).ok()?
1533 };
1534 let extradata = if extradata_payload == 0 {
1538 0
1539 } else {
1540 extradata_payload.checked_add(ffmpeg_next::ffi::AV_INPUT_BUFFER_PADDING_SIZE as usize)?
1541 };
1542
1543 let side_data_ptr = unsafe { (*par).coded_side_data };
1544 let side_data_count = unsafe { (*par).nb_coded_side_data };
1545 let coded_side_data = if side_data_ptr.is_null() || side_data_count <= 0 {
1546 0
1547 } else {
1548 let count = usize::try_from(side_data_count).ok()?;
1549 let mut total = count.checked_mul(core::mem::size_of::<ffmpeg_next::ffi::AVPacketSideData>())?;
1550 for index in 0..count {
1551 let size =
1566 unsafe { core::ptr::read_unaligned(core::ptr::addr_of!((*side_data_ptr.add(index)).size)) };
1567 total = total.checked_add(size)?;
1568 }
1569 total
1570 };
1571
1572 let channel_map = {
1573 let order = unsafe {
1584 core::ptr::read_unaligned(core::ptr::addr_of!((*par).ch_layout.order).cast::<i32>())
1585 };
1586 const UNSPEC: i32 = ffmpeg_next::ffi::AVChannelOrder::AV_CHANNEL_ORDER_UNSPEC as i32;
1587 const NATIVE: i32 = ffmpeg_next::ffi::AVChannelOrder::AV_CHANNEL_ORDER_NATIVE as i32;
1588 const CUSTOM: i32 = ffmpeg_next::ffi::AVChannelOrder::AV_CHANNEL_ORDER_CUSTOM as i32;
1589 const AMBISONIC: i32 = ffmpeg_next::ffi::AVChannelOrder::AV_CHANNEL_ORDER_AMBISONIC as i32;
1590 match order {
1591 UNSPEC | NATIVE | AMBISONIC => 0,
1594 CUSTOM => {
1596 let channels = usize::try_from(unsafe { (*par).ch_layout.nb_channels }).ok()?;
1597 channels.checked_mul(core::mem::size_of::<ffmpeg_next::ffi::AVChannelCustom>())?
1598 }
1599 _ => return None,
1603 }
1604 };
1605
1606 Some(ParameterFootprint {
1607 extradata,
1608 extradata_payload,
1609 coded_side_data,
1610 channel_map,
1611 })
1612}
1613
1614#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
1631pub(crate) enum ExtradataPolicy {
1632 #[default]
1634 Copy,
1635 Omit,
1637}
1638
1639fn seat_copy_failed(stream_index: usize) -> DemuxError {
1647 DemuxError::ParametersCopy(ParametersCopy::new(
1648 stream_index,
1649 ffmpeg_next::Error::Other {
1650 errno: libc::ENOMEM,
1651 },
1652 ))
1653}
1654
1655pub(crate) fn bounded_clone_parameters(
1700 source: &Parameters,
1701 stream_index: usize,
1702 budget: usize,
1703) -> Result<Parameters, DemuxError> {
1704 bounded_clone_parameters_with(source, stream_index, budget, ExtradataPolicy::Copy)
1705}
1706
1707pub(crate) fn bounded_clone_parameters_with(
1709 source: &Parameters,
1710 stream_index: usize,
1711 budget: usize,
1712 extradata_policy: ExtradataPolicy,
1713) -> Result<Parameters, DemuxError> {
1714 let src = unsafe { source.as_ptr() };
1720 if src.is_null() {
1721 return Err(DemuxError::ParametersMissing(ParametersMissing::new(
1722 stream_index,
1723 )));
1724 }
1725
1726 let footprint = unsafe { measure_parameters(src) }.ok_or_else(|| {
1729 DemuxError::ParametersTooLarge(ParametersTooLarge::new(stream_index, usize::MAX, budget))
1730 })?;
1731 let total = match extradata_policy {
1736 ExtradataPolicy::Copy => footprint.total(),
1737 ExtradataPolicy::Omit => footprint.total_without_extradata(),
1738 }
1739 .ok_or_else(|| {
1740 DemuxError::ParametersTooLarge(ParametersTooLarge::new(stream_index, usize::MAX, budget))
1741 })?;
1742 if total > budget {
1743 return Err(DemuxError::ParametersTooLarge(ParametersTooLarge::new(
1744 stream_index,
1745 total,
1746 budget,
1747 )));
1748 }
1749
1750 let mut out = Parameters::new();
1751 let dst = unsafe { out.as_mut_ptr() };
1754 if dst.is_null() {
1755 return Err(DemuxError::ParametersAlloc(ParametersAlloc::new(
1756 stream_index,
1757 )));
1758 }
1759
1760 unsafe {
1766 core::ptr::copy_nonoverlapping(src, dst, 1);
1767 (*dst).extradata = core::ptr::null_mut();
1768 (*dst).extradata_size = 0;
1769 (*dst).coded_side_data = core::ptr::null_mut();
1770 (*dst).nb_coded_side_data = 0;
1771 (*dst).ch_layout = core::mem::zeroed();
1776 }
1777
1778 if footprint.extradata() > 0 && matches!(extradata_policy, ExtradataPolicy::Copy) {
1783 let padded = footprint.extradata();
1784 unsafe {
1788 let buffer = ffmpeg_next::ffi::av_mallocz(padded) as *mut u8;
1789 if buffer.is_null() {
1790 return Err(seat_copy_failed(stream_index));
1791 }
1792 let payload =
1793 usize::try_from((*src).extradata_size).map_err(|_| seat_copy_failed(stream_index))?;
1794 core::ptr::copy_nonoverlapping((*src).extradata, buffer, payload);
1795 (*dst).extradata = buffer;
1796 (*dst).extradata_size = (*src).extradata_size;
1797 }
1798 }
1799
1800 unsafe {
1806 let count = (*src).nb_coded_side_data;
1807 if count > 0 && !(*src).coded_side_data.is_null() {
1808 let entries = usize::try_from(count)
1809 .ok()
1810 .and_then(|c| c.checked_mul(core::mem::size_of::<ffmpeg_next::ffi::AVPacketSideData>()))
1811 .ok_or_else(|| seat_copy_failed(stream_index))?;
1812 let array = ffmpeg_next::ffi::av_mallocz(entries) as *mut ffmpeg_next::ffi::AVPacketSideData;
1813 if array.is_null() {
1814 return Err(seat_copy_failed(stream_index));
1815 }
1816 (*dst).coded_side_data = array;
1821 (*dst).nb_coded_side_data = count;
1822 for index in 0..count as usize {
1823 let from = (*src).coded_side_data.add(index);
1828 let into = array.add(index);
1829 let kind = core::ptr::read_unaligned(core::ptr::addr_of!((*from).type_).cast::<i32>());
1834 core::ptr::write_unaligned(core::ptr::addr_of_mut!((*into).type_).cast::<i32>(), kind);
1835
1836 let size = core::ptr::read_unaligned(core::ptr::addr_of!((*from).size));
1837 let data = core::ptr::read_unaligned(core::ptr::addr_of!((*from).data));
1838 if size > 0 && !data.is_null() {
1839 let payload = ffmpeg_next::ffi::av_mallocz(size) as *mut u8;
1840 if payload.is_null() {
1841 return Err(seat_copy_failed(stream_index));
1842 }
1843 core::ptr::copy_nonoverlapping(data, payload, size);
1844 core::ptr::write_unaligned(core::ptr::addr_of_mut!((*into).data), payload);
1845 core::ptr::write_unaligned(core::ptr::addr_of_mut!((*into).size), size);
1846 } else {
1847 core::ptr::write_unaligned(core::ptr::addr_of_mut!((*into).size), 0);
1848 }
1849 }
1850 }
1851 }
1852
1853 let rc = unsafe {
1861 ffmpeg_next::ffi::av_channel_layout_copy(
1862 core::ptr::addr_of_mut!((*dst).ch_layout),
1863 core::ptr::addr_of!((*src).ch_layout),
1864 )
1865 };
1866 if rc < 0 {
1867 return Err(DemuxError::ParametersCopy(ParametersCopy::new(
1868 stream_index,
1869 ffmpeg_next::Error::from(rc),
1870 )));
1871 }
1872
1873 Ok(out)
1874}
1875
1876#[derive(Clone)]
1945pub struct TrackExtra {
1946 stream_index: i32,
1947 disposition: i32,
1948 start_time: Option<i64>,
1949 frame_count: Option<i64>,
1950 ticket: CodecTicket,
1951}
1952
1953impl TrackExtra {
1954 #[cfg_attr(not(tarpaulin), inline(always))]
1972 pub fn new(stream_index: i32, ticket: CodecTicket) -> Self {
1973 Self {
1974 stream_index,
1975 disposition: 0,
1976 start_time: None,
1977 frame_count: None,
1978 ticket,
1979 }
1980 }
1981
1982 #[cfg_attr(not(tarpaulin), inline(always))]
1994 pub const fn parameter_bytes(&self) -> usize {
1995 self.ticket.footprint_bytes()
1996 }
1997
1998 #[cfg_attr(not(tarpaulin), inline(always))]
2010 pub fn clone_parameters(&self) -> Result<Parameters, DemuxError> {
2011 self.ticket.rebuild()
2012 }
2013
2014 #[cfg_attr(not(tarpaulin), inline(always))]
2016 pub const fn stream_index(&self) -> i32 {
2017 self.stream_index
2018 }
2019 #[cfg_attr(not(tarpaulin), inline(always))]
2021 pub const fn disposition(&self) -> i32 {
2022 self.disposition
2023 }
2024 #[cfg_attr(not(tarpaulin), inline(always))]
2027 pub const fn start_time(&self) -> Option<i64> {
2028 self.start_time
2029 }
2030 #[cfg_attr(not(tarpaulin), inline(always))]
2032 pub const fn frame_count(&self) -> Option<i64> {
2033 self.frame_count
2034 }
2035 #[cfg_attr(not(tarpaulin), inline(always))]
2039 pub const fn ticket(&self) -> &CodecTicket {
2040 &self.ticket
2041 }
2042
2043 #[cfg_attr(not(tarpaulin), inline(always))]
2045 #[must_use]
2046 pub const fn with_disposition(mut self, value: i32) -> Self {
2047 self.disposition = value;
2048 self
2049 }
2050 #[cfg_attr(not(tarpaulin), inline(always))]
2052 #[must_use]
2053 pub const fn with_start_time(mut self, value: Option<i64>) -> Self {
2054 self.start_time = value;
2055 self
2056 }
2057 #[cfg_attr(not(tarpaulin), inline(always))]
2059 #[must_use]
2060 pub const fn with_frame_count(mut self, value: Option<i64>) -> Self {
2061 self.frame_count = value;
2062 self
2063 }
2064
2065 #[cfg_attr(not(tarpaulin), inline(always))]
2067 pub const fn set_disposition(&mut self, value: i32) -> &mut Self {
2068 self.disposition = value;
2069 self
2070 }
2071 #[cfg_attr(not(tarpaulin), inline(always))]
2073 pub const fn set_start_time(&mut self, value: Option<i64>) -> &mut Self {
2074 self.start_time = value;
2075 self
2076 }
2077 #[cfg_attr(not(tarpaulin), inline(always))]
2079 pub const fn set_frame_count(&mut self, value: Option<i64>) -> &mut Self {
2080 self.frame_count = value;
2081 self
2082 }
2083}
2084
2085impl std::fmt::Debug for TrackExtra {
2086 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2090 f.debug_struct("TrackExtra")
2091 .field("stream_index", &self.stream_index)
2092 .field("disposition", &format_args!("{:#x}", self.disposition))
2093 .field("start_time", &self.start_time)
2094 .field("frame_count", &self.frame_count)
2095 .field("ticket", &self.ticket)
2096 .finish()
2097 }
2098}
2099
2100#[cfg(test)]
2101mod tests {
2102 use super::*;
2103
2104 #[test]
2105 fn defaults_construct() {
2106 let v = VideoPacketExtra::default();
2107 assert_eq!(v.stream_index(), 0);
2108 assert!(v.side_data().is_empty());
2109
2110 let f = VideoFrameExtra::default();
2111 assert_eq!(f.picture_type(), PictureType::Unspecified);
2112 assert!(!f.key_frame());
2113 assert!(f.mastering_display().is_none());
2114
2115 let s = SubtitleFrameExtra::default();
2116 assert_eq!(s.start_display_time(), 0);
2117 assert_eq!(s.end_display_time(), 0);
2118 }
2119
2120 #[test]
2121 fn picture_type_default_is_unspecified() {
2122 assert_eq!(PictureType::default(), PictureType::Unspecified);
2123 }
2124
2125 fn parameters_with(extradata: usize, icc_profile: usize) -> Parameters {
2135 let mut out = Parameters::new();
2136 unsafe {
2140 let par = out.as_mut_ptr();
2141 if extradata > 0 {
2142 let buffer = ffmpeg_next::ffi::av_mallocz(extradata) as *mut u8;
2143 assert!(!buffer.is_null(), "av_mallocz extradata");
2144 (*par).extradata = buffer;
2145 (*par).extradata_size = extradata as i32;
2146 }
2147 if icc_profile > 0 {
2148 let array = ffmpeg_next::ffi::av_mallocz(core::mem::size_of::<
2149 ffmpeg_next::ffi::AVPacketSideData,
2150 >()) as *mut ffmpeg_next::ffi::AVPacketSideData;
2151 assert!(!array.is_null(), "av_mallocz side-data array");
2152 let payload = ffmpeg_next::ffi::av_mallocz(icc_profile) as *mut u8;
2153 assert!(!payload.is_null(), "av_mallocz icc profile");
2154 (*array).data = payload;
2155 (*array).size = icc_profile;
2156 (*array).type_ = ffmpeg_next::ffi::AVPacketSideDataType::AV_PKT_DATA_ICC_PROFILE;
2157 (*par).coded_side_data = array;
2158 (*par).nb_coded_side_data = 1;
2159 }
2160 }
2161 out
2162 }
2163
2164 fn footprint_of(parameters: &Parameters) -> ParameterFootprint {
2165 unsafe { measure_parameters(parameters.as_ptr()) }.expect("measurable")
2167 }
2168
2169 #[test]
2170 fn the_measurement_counts_every_heap_seat_and_allocates_nothing() {
2171 const PAD: usize = ffmpeg_next::ffi::AV_INPUT_BUFFER_PADDING_SIZE as usize;
2176 const DESCRIPTOR: usize = core::mem::size_of::<ffmpeg_next::ffi::AVPacketSideData>();
2177 let parameters = parameters_with(4_096, 64 * 1024);
2178 let footprint = footprint_of(¶meters);
2179 assert_eq!(footprint.extradata(), 4_096 + PAD);
2183 assert_eq!(
2184 footprint.coded_side_data(),
2185 64 * 1024 + DESCRIPTOR,
2186 "the descriptor array is an allocation too",
2187 );
2188 assert_eq!(footprint.channel_map(), 0, "no custom layout here");
2189 assert_eq!(
2190 footprint.total(),
2191 Some(4_096 + PAD + 64 * 1024 + DESCRIPTOR),
2192 );
2193 assert_eq!(
2197 footprint.total_without_extradata(),
2198 Some(64 * 1024 + DESCRIPTOR),
2199 );
2200 assert_eq!(footprint_of(¶meters_with(0, 8)).extradata(), 0);
2202 }
2203
2204 #[test]
2205 fn an_oversized_coded_side_data_entry_is_refused_before_the_clone() {
2206 let parameters = parameters_with(0, 8 * 1024 * 1024);
2210 let declared = footprint_of(¶meters).total().expect("measurable");
2211
2212 match bounded_clone_parameters(¶meters, 3, 64 * 1024) {
2213 Err(DemuxError::ParametersTooLarge(p)) => {
2214 assert_eq!(p.stream_index(), 3);
2215 assert_eq!(p.bytes(), declared);
2216 assert_eq!(p.limit(), 64 * 1024);
2217 }
2218 Err(other) => panic!("expected ParametersTooLarge, got {other:?}"),
2219 Ok(_) => panic!("an 8 MiB ICC profile passed a 64 KiB ceiling"),
2220 }
2221
2222 let cloned =
2224 bounded_clone_parameters(¶meters, 3, declared).expect("at the cap is not over it");
2225 assert_eq!(footprint_of(&cloned), footprint_of(¶meters));
2226 }
2227
2228 #[test]
2229 fn a_legitimate_multi_megabyte_icc_profile_is_admitted_by_default() {
2230 let parameters = parameters_with(1_024, 4 * 1024 * 1024);
2234 let cloned = bounded_clone_parameters(
2235 ¶meters,
2236 0,
2237 crate::limits::DEFAULT_MAX_CODEC_PARAMETER_BYTES,
2238 )
2239 .expect("a 4 MiB ICC profile is real media");
2240 assert_eq!(footprint_of(&cloned), footprint_of(¶meters));
2243 }
2244
2245 #[test]
2246 fn the_bounded_clone_keeps_every_field_a_decoder_consumes() {
2247 let parameters = parameters_with(32, 128);
2252 unsafe {
2255 let par = parameters.as_ptr() as *mut ffmpeg_next::ffi::AVCodecParameters;
2256 (*par).codec_id = ffmpeg_next::ffi::AVCodecID::AV_CODEC_ID_H264;
2257 (*par).width = 1920;
2258 (*par).height = 1080;
2259 (*par).bit_rate = 5_000_000;
2260 (*par).sample_rate = 48_000;
2261 core::ptr::write_bytes((*par).extradata, 0xAB, 32);
2262 core::ptr::write_bytes((*(*par).coded_side_data).data, 0xCD, 128);
2263 }
2264
2265 let cloned = bounded_clone_parameters(¶meters, 0, usize::MAX).expect("clone");
2266 unsafe {
2268 let src = parameters.as_ptr();
2269 let dst = cloned.as_ptr();
2270 assert_eq!((*dst).codec_id, (*src).codec_id, "the scalar sweep");
2271 assert_eq!(((*dst).width, (*dst).height), (1920, 1080));
2272 assert_eq!((*dst).bit_rate, 5_000_000);
2273 assert_eq!((*dst).sample_rate, 48_000);
2274
2275 assert_eq!((*dst).extradata_size, 32);
2276 assert_ne!(
2277 (*dst).extradata,
2278 (*src).extradata,
2279 "it is a copy, not an alias"
2280 );
2281 let extradata = core::slice::from_raw_parts((*dst).extradata, 32);
2282 assert!(extradata.iter().all(|&b| b == 0xAB), "SPS/PPS survived");
2283 let padded = core::slice::from_raw_parts(
2286 (*dst).extradata.add(32),
2287 ffmpeg_next::ffi::AV_INPUT_BUFFER_PADDING_SIZE as usize,
2288 );
2289 assert!(padded.iter().all(|&b| b == 0), "the read-past padding");
2290
2291 assert_eq!((*dst).nb_coded_side_data, 1);
2292 let entry = &*(*dst).coded_side_data;
2293 assert_eq!(entry.size, 128);
2294 assert_eq!(
2295 entry.type_,
2296 ffmpeg_next::ffi::AVPacketSideDataType::AV_PKT_DATA_ICC_PROFILE,
2297 );
2298 assert_ne!(entry.data, (*(*src).coded_side_data).data, "a copy");
2299 let payload = core::slice::from_raw_parts(entry.data, 128);
2300 assert!(
2301 payload.iter().all(|&b| b == 0xCD),
2302 "the ICC profile survived"
2303 );
2304 }
2305 }
2306
2307 #[test]
2308 fn side_data_entry_carries_bytes() {
2309 let entry = SideDataEntry::new(12345, FfmpegBytes::copy_from_slice(&[1, 2, 3, 4]));
2310 assert_eq!(entry.kind(), 12345);
2311 assert_eq!(entry.data(), &[1, 2, 3, 4]);
2312 }
2313
2314 #[test]
2315 fn side_data_entry_clone_shares_its_payload() {
2316 let entry = SideDataEntry::new(7, FfmpegBytes::copy_from_slice(&[9u8; 64]));
2319 let cloned = entry.clone();
2320 assert!(
2321 entry.data_ref().ptr_eq(cloned.data_ref()),
2322 "cloning a side-data entry copied its bytes",
2323 );
2324 assert_eq!(cloned.data(), entry.data());
2325 }
2326
2327 const MEASURED: [(u16, ImageOrientation, [i32; 4]); 8] = [
2332 (1, ImageOrientation::TopLeft, [65536, 0, 0, 65536]),
2333 (2, ImageOrientation::TopRight, [-65536, 0, 0, 65536]),
2334 (3, ImageOrientation::BottomRight, [-65536, 0, 0, -65536]),
2335 (4, ImageOrientation::BottomLeft, [65536, 0, 0, -65536]),
2336 (5, ImageOrientation::LeftTop, [0, 65536, 65536, 0]),
2337 (6, ImageOrientation::RightTop, [0, 65536, -65536, 0]),
2338 (7, ImageOrientation::RightBottom, [0, -65536, -65536, 0]),
2339 (8, ImageOrientation::LeftBottom, [0, -65536, 65536, 0]),
2340 ];
2341
2342 fn display_matrix(linear: [i32; 4]) -> Vec<u8> {
2347 words_to_bytes([
2348 linear[0],
2349 linear[1],
2350 0,
2351 linear[2],
2352 linear[3],
2353 0,
2354 0,
2355 0,
2356 1 << 30,
2357 ])
2358 }
2359
2360 fn words_to_bytes(words: [i32; 9]) -> Vec<u8> {
2361 words.iter().flat_map(|w| w.to_ne_bytes()).collect()
2362 }
2363
2364 #[test]
2365 fn every_measured_display_matrix_reads_back_as_its_exif_tag() {
2366 for (tag, expected, linear) in MEASURED {
2367 let read = ImageOrientation::from_display_matrix(&display_matrix(linear))
2368 .expect("a nine-word matrix is readable");
2369 assert_eq!(read, expected, "tag {tag}");
2370 assert_eq!(read.to_exif_code(), Some(tag));
2371 assert_eq!(ImageOrientation::from_exif_code(tag), Some(read));
2372 assert_eq!(read.linear(), linear, "tag {tag}");
2374 }
2375 }
2376
2377 #[test]
2378 fn the_four_mirrored_tags_are_the_ones_exif_says_they_are() {
2379 for (tag, orientation, _) in MEASURED {
2382 assert_eq!(
2383 orientation.is_mirrored(),
2384 matches!(tag, 2 | 4 | 5 | 7),
2385 "tag {tag}",
2386 );
2387 }
2388 }
2389
2390 #[test]
2391 fn the_quarter_turn_lands_in_the_workspace_rotation_vocabulary() {
2392 use ImageOrientation::*;
2393 assert_eq!(TopLeft.rotation(), Some(Rotation::D0));
2394 assert_eq!(TopRight.rotation(), Some(Rotation::D0));
2395 assert_eq!(RightTop.rotation(), Some(Rotation::D90));
2396 assert_eq!(LeftTop.rotation(), Some(Rotation::D90));
2397 assert_eq!(BottomRight.rotation(), Some(Rotation::D180));
2398 assert_eq!(BottomLeft.rotation(), Some(Rotation::D180));
2399 assert_eq!(LeftBottom.rotation(), Some(Rotation::D270));
2400 assert_eq!(RightBottom.rotation(), Some(Rotation::D270));
2401 assert_eq!(TopLeft.rotation(), TopRight.rotation());
2404 assert_ne!(TopLeft, TopRight);
2405 }
2406
2407 #[test]
2408 fn a_transform_the_vocabulary_cannot_name_is_carried_not_collapsed() {
2409 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];
2413 let read =
2414 ImageOrientation::from_display_matrix(&display_matrix(odd)).expect("readable, just unnamed");
2415 assert_eq!(read, ImageOrientation::Other(words));
2416 assert_eq!(read.to_exif_code(), None, "there is no tag to invent");
2417 assert_eq!(read.rotation(), None, "it is not a quarter turn");
2418 assert_eq!(read.linear(), odd, "the linear projection still answers");
2419 assert_eq!(read.matrix(), words, "and nothing was dropped");
2420 assert!(!read.is_mirrored());
2423 assert!(ImageOrientation::Other([65536, 0, 0, 0, -65536, 0, 0, 0, 1 << 30]).is_mirrored());
2424 }
2425
2426 #[test]
2427 fn a_noncanonical_word_keeps_a_matrix_out_of_the_named_variants() {
2428 let named = ImageOrientation::RightTop;
2436 let canonical = named.matrix();
2437 assert_eq!(
2438 ImageOrientation::from_display_matrix(&words_to_bytes(canonical)),
2439 Some(named),
2440 "the canonical matrix must still be named",
2441 );
2442
2443 for index in [2usize, 5, 6, 7, 8] {
2444 let mut forged = canonical;
2445 forged[index] = if index == 8 { 1 << 29 } else { 4_096 };
2448 let read = ImageOrientation::from_display_matrix(&words_to_bytes(forged))
2449 .expect("nine words are readable");
2450 assert_eq!(
2451 read,
2452 ImageOrientation::Other(forged),
2453 "word {index} was collapsed into a named variant",
2454 );
2455 assert_eq!(read.to_exif_code(), None, "word {index}");
2456 assert_eq!(read.matrix(), forged, "word {index} round-trips whole");
2457 assert_eq!(read.linear(), named.linear(), "word {index}");
2459 }
2460 }
2461
2462 #[test]
2463 fn the_escape_round_trips_every_word_losslessly() {
2464 let words: [i32; 9] = [1, -2, 3, -4, 5, -6, i32::MIN, i32::MAX, 0];
2467 let read = ImageOrientation::from_display_matrix(&words_to_bytes(words)).expect("readable");
2468 assert_eq!(read, ImageOrientation::Other(words));
2469 assert_eq!(read.matrix(), words);
2470 let again =
2472 ImageOrientation::from_display_matrix(&words_to_bytes(read.matrix())).expect("readable");
2473 assert_eq!(again, read);
2474 }
2475
2476 #[test]
2477 fn every_named_orientation_reconstructs_its_canonical_matrix() {
2478 for (tag, orientation, linear) in MEASURED {
2479 let matrix = orientation.matrix();
2480 assert_eq!(
2481 [matrix[0], matrix[1], matrix[3], matrix[4]],
2482 linear,
2483 "tag {tag}",
2484 );
2485 assert_eq!(
2486 [matrix[2], matrix[5], matrix[6], matrix[7]],
2487 [0, 0, 0, 0],
2488 "tag {tag}: no translation, no perspective",
2489 );
2490 assert_eq!(matrix[8], 1 << 30, "tag {tag}: unity `w`");
2491 assert_eq!(
2493 ImageOrientation::from_display_matrix(&words_to_bytes(matrix)),
2494 Some(orientation),
2495 "tag {tag}",
2496 );
2497 }
2498 }
2499
2500 #[test]
2501 fn a_malformed_matrix_is_no_orientation_rather_than_a_guessed_one() {
2502 assert_eq!(ImageOrientation::from_display_matrix(&[]), None);
2503 assert_eq!(ImageOrientation::from_display_matrix(&[0u8; 16]), None);
2504 assert_eq!(ImageOrientation::from_display_matrix(&[0u8; 40]), None);
2505 assert_eq!(
2507 ImageOrientation::DISPLAY_MATRIX_BYTES,
2508 36,
2509 "nine int32, per libavutil/display.h",
2510 );
2511 assert!(ImageOrientation::from_display_matrix(&[0u8; 36]).is_some());
2512 }
2513
2514 #[test]
2515 fn an_out_of_range_exif_tag_is_refused_not_clamped() {
2516 for code in [0u16, 9, 255, u16::MAX] {
2517 assert_eq!(ImageOrientation::from_exif_code(code), None, "code {code}");
2518 }
2519 }
2520
2521 #[test]
2522 fn the_orientation_seat_rides_the_image_extras() {
2523 let extra = ImageFrameExtra::default();
2524 assert_eq!(extra.orientation(), None, "absent until a file says");
2525
2526 let carried = ImageFrameExtra::new().with_orientation(Some(ImageOrientation::RightTop));
2527 assert_eq!(carried.orientation(), Some(ImageOrientation::RightTop));
2528
2529 let mut mutated = carried.clone();
2530 mutated.set_orientation(None);
2531 assert_eq!(mutated.orientation(), None);
2532 assert_eq!(carried.orientation(), Some(ImageOrientation::RightTop));
2533 }
2534
2535 #[test]
2536 fn the_image_household_is_one_seat() {
2537 let extra = ImageFrameExtra::default();
2538 assert!(extra.side_data().is_empty());
2539 let carried = ImageFrameExtra::new().with_side_data(vec![SideDataEntry::new(
2540 3,
2541 FfmpegBytes::copy_from_slice(&[1]),
2542 )]);
2543 assert_eq!(carried.side_data().len(), 1);
2544 assert_eq!(carried.side_data()[0].kind(), 3);
2545 let mut mutated = carried.clone();
2546 mutated.set_side_data(Vec::new());
2547 assert!(mutated.side_data().is_empty());
2548 assert_eq!(carried.side_data().len(), 1);
2549 }
2550
2551 #[test]
2552 fn content_light_level_default_is_zero() {
2553 let cll = ContentLightLevel::default();
2554 assert_eq!(cll.max_cll(), 0);
2555 assert_eq!(cll.max_fall(), 0);
2556 }
2557
2558 #[test]
2559 fn builders_chain() {
2560 let v = VideoPacketExtra::new(7)
2561 .with_byte_pos(Some(1234))
2562 .with_side_data(vec![SideDataEntry::new(
2563 1,
2564 FfmpegBytes::copy_from_slice(&[0xAB]),
2565 )]);
2566 assert_eq!(v.stream_index(), 7);
2567 assert_eq!(v.byte_pos(), Some(1234));
2568 assert_eq!(v.side_data().len(), 1);
2569 }
2570
2571 #[test]
2572 fn setters_chain() {
2573 let mut v = VideoPacketExtra::default();
2574 v.set_stream_index(3).set_byte_pos(Some(99));
2575 assert_eq!(v.stream_index(), 3);
2576 assert_eq!(v.byte_pos(), Some(99));
2577 }
2578}