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::demuxer::{
33 DemuxError, ParametersAlloc, ParametersCopy, ParametersMissing, ParametersTooLarge,
34};
35
36#[derive(Clone, Debug, Default)]
38pub struct VideoPacketExtra {
39 stream_index: i32,
40 byte_pos: Option<i64>,
41 side_data: Vec<SideDataEntry>,
42}
43
44impl VideoPacketExtra {
45 #[cfg_attr(not(tarpaulin), inline(always))]
48 pub const fn new(stream_index: i32) -> Self {
49 Self {
50 stream_index,
51 byte_pos: None,
52 side_data: Vec::new(),
53 }
54 }
55
56 #[cfg_attr(not(tarpaulin), inline(always))]
58 pub const fn stream_index(&self) -> i32 {
59 self.stream_index
60 }
61
62 #[cfg_attr(not(tarpaulin), inline(always))]
65 pub const fn byte_pos(&self) -> Option<i64> {
66 self.byte_pos
67 }
68
69 #[cfg_attr(not(tarpaulin), inline(always))]
71 pub fn side_data(&self) -> &[SideDataEntry] {
72 self.side_data.as_slice()
73 }
74
75 #[cfg_attr(not(tarpaulin), inline(always))]
77 #[must_use]
78 pub const fn with_stream_index(mut self, value: i32) -> Self {
79 self.stream_index = value;
80 self
81 }
82 #[cfg_attr(not(tarpaulin), inline(always))]
84 #[must_use]
85 pub const fn with_byte_pos(mut self, value: Option<i64>) -> Self {
86 self.byte_pos = value;
87 self
88 }
89 #[cfg_attr(not(tarpaulin), inline(always))]
91 #[must_use]
92 pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
93 self.side_data = value;
94 self
95 }
96
97 #[cfg_attr(not(tarpaulin), inline(always))]
99 pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
100 self.stream_index = value;
101 self
102 }
103 #[cfg_attr(not(tarpaulin), inline(always))]
105 pub const fn set_byte_pos(&mut self, value: Option<i64>) -> &mut Self {
106 self.byte_pos = value;
107 self
108 }
109 #[cfg_attr(not(tarpaulin), inline(always))]
111 pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
112 self.side_data = value;
113 self
114 }
115}
116
117#[derive(Clone, Debug, Default)]
120pub struct VideoFrameExtra {
121 sample_aspect_ratio: Option<(u32, u32)>,
122 picture_type: PictureType,
123 key_frame: bool,
124 interlaced: bool,
125 top_field_first: bool,
126 best_effort_timestamp: Option<i64>,
127 mastering_display: Option<MasteringDisplay>,
128 content_light_level: Option<ContentLightLevel>,
129 smpte_timecode: Vec<u32>,
130 side_data: Vec<SideDataEntry>,
131}
132
133impl VideoFrameExtra {
134 #[cfg_attr(not(tarpaulin), inline(always))]
136 pub const fn new() -> Self {
137 Self {
138 sample_aspect_ratio: None,
139 picture_type: PictureType::Unspecified,
140 key_frame: false,
141 interlaced: false,
142 top_field_first: false,
143 best_effort_timestamp: None,
144 mastering_display: None,
145 content_light_level: None,
146 smpte_timecode: Vec::new(),
147 side_data: Vec::new(),
148 }
149 }
150
151 #[cfg_attr(not(tarpaulin), inline(always))]
154 pub const fn sample_aspect_ratio(&self) -> Option<(u32, u32)> {
155 self.sample_aspect_ratio
156 }
157 #[cfg_attr(not(tarpaulin), inline(always))]
159 pub const fn picture_type(&self) -> PictureType {
160 self.picture_type
161 }
162 #[cfg_attr(not(tarpaulin), inline(always))]
164 pub const fn key_frame(&self) -> bool {
165 self.key_frame
166 }
167 #[cfg_attr(not(tarpaulin), inline(always))]
169 pub const fn interlaced(&self) -> bool {
170 self.interlaced
171 }
172 #[cfg_attr(not(tarpaulin), inline(always))]
174 pub const fn top_field_first(&self) -> bool {
175 self.top_field_first
176 }
177 #[cfg_attr(not(tarpaulin), inline(always))]
179 pub const fn best_effort_timestamp(&self) -> Option<i64> {
180 self.best_effort_timestamp
181 }
182 #[cfg_attr(not(tarpaulin), inline(always))]
184 pub const fn mastering_display(&self) -> Option<MasteringDisplay> {
185 self.mastering_display
186 }
187 #[cfg_attr(not(tarpaulin), inline(always))]
189 pub const fn content_light_level(&self) -> Option<ContentLightLevel> {
190 self.content_light_level
191 }
192 #[cfg_attr(not(tarpaulin), inline(always))]
194 pub fn smpte_timecode(&self) -> &[u32] {
195 self.smpte_timecode.as_slice()
196 }
197 #[cfg_attr(not(tarpaulin), inline(always))]
199 pub fn side_data(&self) -> &[SideDataEntry] {
200 self.side_data.as_slice()
201 }
202
203 #[cfg_attr(not(tarpaulin), inline(always))]
205 pub const fn with_sample_aspect_ratio(mut self, value: Option<(u32, u32)>) -> Self {
206 self.sample_aspect_ratio = value;
207 self
208 }
209 #[cfg_attr(not(tarpaulin), inline(always))]
211 #[must_use]
212 pub const fn with_picture_type(mut self, value: PictureType) -> Self {
213 self.picture_type = value;
214 self
215 }
216 #[cfg_attr(not(tarpaulin), inline(always))]
218 #[must_use]
219 pub const fn with_key_frame(mut self, value: bool) -> Self {
220 self.key_frame = value;
221 self
222 }
223 #[cfg_attr(not(tarpaulin), inline(always))]
225 #[must_use]
226 pub const fn with_interlaced(mut self, value: bool) -> Self {
227 self.interlaced = value;
228 self
229 }
230 #[cfg_attr(not(tarpaulin), inline(always))]
232 #[must_use]
233 pub const fn with_top_field_first(mut self, value: bool) -> Self {
234 self.top_field_first = value;
235 self
236 }
237 #[cfg_attr(not(tarpaulin), inline(always))]
239 #[must_use]
240 pub const fn with_best_effort_timestamp(mut self, value: Option<i64>) -> Self {
241 self.best_effort_timestamp = value;
242 self
243 }
244 #[cfg_attr(not(tarpaulin), inline(always))]
246 #[must_use]
247 pub const fn with_mastering_display(mut self, value: Option<MasteringDisplay>) -> Self {
248 self.mastering_display = value;
249 self
250 }
251 #[cfg_attr(not(tarpaulin), inline(always))]
253 #[must_use]
254 pub const fn with_content_light_level(mut self, value: Option<ContentLightLevel>) -> Self {
255 self.content_light_level = value;
256 self
257 }
258 #[cfg_attr(not(tarpaulin), inline(always))]
260 #[must_use]
261 pub fn with_smpte_timecode(mut self, value: Vec<u32>) -> Self {
262 self.smpte_timecode = value;
263 self
264 }
265 #[cfg_attr(not(tarpaulin), inline(always))]
267 #[must_use]
268 pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
269 self.side_data = value;
270 self
271 }
272
273 #[cfg_attr(not(tarpaulin), inline(always))]
275 pub const fn set_sample_aspect_ratio(&mut self, value: Option<(u32, u32)>) -> &mut Self {
276 self.sample_aspect_ratio = value;
277 self
278 }
279 #[cfg_attr(not(tarpaulin), inline(always))]
281 pub const fn set_picture_type(&mut self, value: PictureType) -> &mut Self {
282 self.picture_type = value;
283 self
284 }
285 #[cfg_attr(not(tarpaulin), inline(always))]
287 pub const fn set_key_frame(&mut self, value: bool) -> &mut Self {
288 self.key_frame = value;
289 self
290 }
291 #[cfg_attr(not(tarpaulin), inline(always))]
293 pub const fn set_interlaced(&mut self, value: bool) -> &mut Self {
294 self.interlaced = value;
295 self
296 }
297 #[cfg_attr(not(tarpaulin), inline(always))]
299 pub const fn set_top_field_first(&mut self, value: bool) -> &mut Self {
300 self.top_field_first = value;
301 self
302 }
303 #[cfg_attr(not(tarpaulin), inline(always))]
305 pub const fn set_best_effort_timestamp(&mut self, value: Option<i64>) -> &mut Self {
306 self.best_effort_timestamp = value;
307 self
308 }
309 #[cfg_attr(not(tarpaulin), inline(always))]
311 pub const fn set_mastering_display(&mut self, value: Option<MasteringDisplay>) -> &mut Self {
312 self.mastering_display = value;
313 self
314 }
315 #[cfg_attr(not(tarpaulin), inline(always))]
317 pub const fn set_content_light_level(&mut self, value: Option<ContentLightLevel>) -> &mut Self {
318 self.content_light_level = value;
319 self
320 }
321 #[cfg_attr(not(tarpaulin), inline(always))]
323 pub fn set_smpte_timecode(&mut self, value: Vec<u32>) -> &mut Self {
324 self.smpte_timecode = value;
325 self
326 }
327 #[cfg_attr(not(tarpaulin), inline(always))]
329 pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
330 self.side_data = value;
331 self
332 }
333}
334
335#[derive(Clone, Debug, Default)]
337pub struct AudioPacketExtra {
338 stream_index: i32,
339 byte_pos: Option<i64>,
340 side_data: Vec<SideDataEntry>,
341}
342
343impl AudioPacketExtra {
344 #[cfg_attr(not(tarpaulin), inline(always))]
346 pub const fn new(stream_index: i32) -> Self {
347 Self {
348 stream_index,
349 byte_pos: None,
350 side_data: Vec::new(),
351 }
352 }
353
354 #[cfg_attr(not(tarpaulin), inline(always))]
356 pub const fn stream_index(&self) -> i32 {
357 self.stream_index
358 }
359 #[cfg_attr(not(tarpaulin), inline(always))]
361 pub const fn byte_pos(&self) -> Option<i64> {
362 self.byte_pos
363 }
364 #[cfg_attr(not(tarpaulin), inline(always))]
366 pub fn side_data(&self) -> &[SideDataEntry] {
367 self.side_data.as_slice()
368 }
369
370 #[cfg_attr(not(tarpaulin), inline(always))]
372 #[must_use]
373 pub const fn with_stream_index(mut self, value: i32) -> Self {
374 self.stream_index = value;
375 self
376 }
377 #[cfg_attr(not(tarpaulin), inline(always))]
379 #[must_use]
380 pub const fn with_byte_pos(mut self, value: Option<i64>) -> Self {
381 self.byte_pos = value;
382 self
383 }
384 #[cfg_attr(not(tarpaulin), inline(always))]
386 #[must_use]
387 pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
388 self.side_data = value;
389 self
390 }
391
392 #[cfg_attr(not(tarpaulin), inline(always))]
394 pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
395 self.stream_index = value;
396 self
397 }
398 #[cfg_attr(not(tarpaulin), inline(always))]
400 pub const fn set_byte_pos(&mut self, value: Option<i64>) -> &mut Self {
401 self.byte_pos = value;
402 self
403 }
404 #[cfg_attr(not(tarpaulin), inline(always))]
406 pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
407 self.side_data = value;
408 self
409 }
410}
411
412#[derive(Clone, Debug, Default)]
414pub struct AudioFrameExtra {
415 best_effort_timestamp: Option<i64>,
416 side_data: Vec<SideDataEntry>,
417}
418
419impl AudioFrameExtra {
420 #[cfg_attr(not(tarpaulin), inline(always))]
422 pub const fn new() -> Self {
423 Self {
424 best_effort_timestamp: None,
425 side_data: Vec::new(),
426 }
427 }
428
429 #[cfg_attr(not(tarpaulin), inline(always))]
431 pub const fn best_effort_timestamp(&self) -> Option<i64> {
432 self.best_effort_timestamp
433 }
434 #[cfg_attr(not(tarpaulin), inline(always))]
436 pub fn side_data(&self) -> &[SideDataEntry] {
437 self.side_data.as_slice()
438 }
439
440 #[cfg_attr(not(tarpaulin), inline(always))]
442 #[must_use]
443 pub const fn with_best_effort_timestamp(mut self, value: Option<i64>) -> Self {
444 self.best_effort_timestamp = value;
445 self
446 }
447 #[cfg_attr(not(tarpaulin), inline(always))]
449 #[must_use]
450 pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
451 self.side_data = value;
452 self
453 }
454
455 #[cfg_attr(not(tarpaulin), inline(always))]
457 pub const fn set_best_effort_timestamp(&mut self, value: Option<i64>) -> &mut Self {
458 self.best_effort_timestamp = value;
459 self
460 }
461 #[cfg_attr(not(tarpaulin), inline(always))]
463 pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
464 self.side_data = value;
465 self
466 }
467}
468
469#[derive(Clone, Debug, Default)]
471pub struct SubtitlePacketExtra {
472 stream_index: i32,
473 language: Option<[u8; 3]>,
474 forced: bool,
475 side_data: Vec<SideDataEntry>,
476}
477
478impl SubtitlePacketExtra {
479 #[cfg_attr(not(tarpaulin), inline(always))]
482 pub const fn new(stream_index: i32) -> Self {
483 Self {
484 stream_index,
485 language: None,
486 forced: false,
487 side_data: Vec::new(),
488 }
489 }
490
491 #[cfg_attr(not(tarpaulin), inline(always))]
493 pub const fn stream_index(&self) -> i32 {
494 self.stream_index
495 }
496 #[cfg_attr(not(tarpaulin), inline(always))]
498 pub const fn language(&self) -> Option<[u8; 3]> {
499 self.language
500 }
501 #[cfg_attr(not(tarpaulin), inline(always))]
503 pub const fn forced(&self) -> bool {
504 self.forced
505 }
506 #[cfg_attr(not(tarpaulin), inline(always))]
513 pub fn side_data(&self) -> &[SideDataEntry] {
514 self.side_data.as_slice()
515 }
516
517 #[cfg_attr(not(tarpaulin), inline(always))]
519 #[must_use]
520 pub const fn with_stream_index(mut self, value: i32) -> Self {
521 self.stream_index = value;
522 self
523 }
524 #[cfg_attr(not(tarpaulin), inline(always))]
526 #[must_use]
527 pub const fn with_language(mut self, value: Option<[u8; 3]>) -> Self {
528 self.language = value;
529 self
530 }
531 #[cfg_attr(not(tarpaulin), inline(always))]
533 #[must_use]
534 pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
535 self.side_data = value;
536 self
537 }
538 #[cfg_attr(not(tarpaulin), inline(always))]
540 pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
541 self.side_data = value;
542 self
543 }
544 #[cfg_attr(not(tarpaulin), inline(always))]
546 #[must_use]
547 pub const fn with_forced(mut self, value: bool) -> Self {
548 self.forced = value;
549 self
550 }
551
552 #[cfg_attr(not(tarpaulin), inline(always))]
554 pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
555 self.stream_index = value;
556 self
557 }
558 #[cfg_attr(not(tarpaulin), inline(always))]
560 pub const fn set_language(&mut self, value: Option<[u8; 3]>) -> &mut Self {
561 self.language = value;
562 self
563 }
564 #[cfg_attr(not(tarpaulin), inline(always))]
566 pub const fn set_forced(&mut self, value: bool) -> &mut Self {
567 self.forced = value;
568 self
569 }
570}
571
572#[derive(Clone, Debug, Default)]
574pub struct SubtitleFrameExtra {
575 start_display_time: u32,
576 end_display_time: u32,
577}
578
579impl SubtitleFrameExtra {
580 #[cfg_attr(not(tarpaulin), inline(always))]
582 pub const fn new(start_display_time: u32, end_display_time: u32) -> Self {
583 Self {
584 start_display_time,
585 end_display_time,
586 }
587 }
588
589 #[cfg_attr(not(tarpaulin), inline(always))]
591 pub const fn start_display_time(&self) -> u32 {
592 self.start_display_time
593 }
594 #[cfg_attr(not(tarpaulin), inline(always))]
596 pub const fn end_display_time(&self) -> u32 {
597 self.end_display_time
598 }
599
600 #[cfg_attr(not(tarpaulin), inline(always))]
602 #[must_use]
603 pub const fn with_start_display_time(mut self, value: u32) -> Self {
604 self.start_display_time = value;
605 self
606 }
607 #[cfg_attr(not(tarpaulin), inline(always))]
609 #[must_use]
610 pub const fn with_end_display_time(mut self, value: u32) -> Self {
611 self.end_display_time = value;
612 self
613 }
614
615 #[cfg_attr(not(tarpaulin), inline(always))]
617 pub const fn set_start_display_time(&mut self, value: u32) -> &mut Self {
618 self.start_display_time = value;
619 self
620 }
621 #[cfg_attr(not(tarpaulin), inline(always))]
623 pub const fn set_end_display_time(&mut self, value: u32) -> &mut Self {
624 self.end_display_time = value;
625 self
626 }
627}
628
629#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, IsVariant)]
671#[non_exhaustive]
672pub enum ImageOrientation {
673 #[default]
676 TopLeft,
677 TopRight,
679 BottomRight,
681 BottomLeft,
683 LeftTop,
685 RightTop,
687 RightBottom,
690 LeftBottom,
692 Other([i32; 9]),
709}
710
711impl ImageOrientation {
712 const UNIT: i32 = 1 << 16;
715
716 const PERSPECTIVE_UNIT: i32 = 1 << 30;
720
721 pub const DISPLAY_MATRIX_BYTES: usize = 9 * core::mem::size_of::<i32>();
724
725 #[cfg_attr(not(tarpaulin), inline(always))]
742 pub const fn matrix(&self) -> [i32; 9] {
743 match self {
744 Self::Other(matrix) => *matrix,
745 named => {
746 let [a, b, c, d] = named.linear();
747 [a, b, 0, c, d, 0, 0, 0, Self::PERSPECTIVE_UNIT]
748 }
749 }
750 }
751
752 pub fn from_display_matrix(bytes: &[u8]) -> Option<Self> {
763 if bytes.len() != Self::DISPLAY_MATRIX_BYTES {
764 return None;
765 }
766 let mut matrix = [0i32; 9];
767 for (index, word) in matrix.iter_mut().enumerate() {
768 let mut raw = [0u8; 4];
769 raw.copy_from_slice(&bytes[index * 4..index * 4 + 4]);
770 *word = i32::from_ne_bytes(raw);
771 }
772 Some(Self::from_matrix(matrix))
773 }
774
775 fn from_matrix(matrix: [i32; 9]) -> Self {
785 const P: i32 = ImageOrientation::UNIT;
786 const N: i32 = -ImageOrientation::UNIT;
787 const W: i32 = ImageOrientation::PERSPECTIVE_UNIT;
788 match matrix {
789 [P, 0, 0, 0, P, 0, 0, 0, W] => Self::TopLeft,
790 [N, 0, 0, 0, P, 0, 0, 0, W] => Self::TopRight,
791 [N, 0, 0, 0, N, 0, 0, 0, W] => Self::BottomRight,
792 [P, 0, 0, 0, N, 0, 0, 0, W] => Self::BottomLeft,
793 [0, P, 0, P, 0, 0, 0, 0, W] => Self::LeftTop,
794 [0, P, 0, N, 0, 0, 0, 0, W] => Self::RightTop,
795 [0, N, 0, N, 0, 0, 0, 0, W] => Self::RightBottom,
796 [0, N, 0, P, 0, 0, 0, 0, W] => Self::LeftBottom,
797 other => Self::Other(other),
798 }
799 }
800
801 #[cfg_attr(not(tarpaulin), inline(always))]
806 pub const fn to_exif_code(&self) -> Option<u16> {
807 Some(match self {
808 Self::TopLeft => 1,
809 Self::TopRight => 2,
810 Self::BottomRight => 3,
811 Self::BottomLeft => 4,
812 Self::LeftTop => 5,
813 Self::RightTop => 6,
814 Self::RightBottom => 7,
815 Self::LeftBottom => 8,
816 Self::Other(_) => return None,
817 })
818 }
819
820 #[cfg_attr(not(tarpaulin), inline(always))]
824 pub const fn from_exif_code(code: u16) -> Option<Self> {
825 Some(match code {
826 1 => Self::TopLeft,
827 2 => Self::TopRight,
828 3 => Self::BottomRight,
829 4 => Self::BottomLeft,
830 5 => Self::LeftTop,
831 6 => Self::RightTop,
832 7 => Self::RightBottom,
833 8 => Self::LeftBottom,
834 _ => return None,
835 })
836 }
837
838 #[cfg_attr(not(tarpaulin), inline(always))]
844 pub const fn is_mirrored(&self) -> bool {
845 let [a, b, c, d] = self.linear();
846 (a as i64) * (d as i64) - (b as i64) * (c as i64) < 0
850 }
851
852 #[cfg_attr(not(tarpaulin), inline(always))]
865 pub const fn rotation(&self) -> Option<Rotation> {
866 Some(match self {
867 Self::TopLeft | Self::TopRight => Rotation::D0,
868 Self::RightTop | Self::LeftTop => Rotation::D90,
869 Self::BottomRight | Self::BottomLeft => Rotation::D180,
870 Self::LeftBottom | Self::RightBottom => Rotation::D270,
871 Self::Other(_) => return None,
872 })
873 }
874
875 #[cfg_attr(not(tarpaulin), inline(always))]
885 pub const fn linear(&self) -> [i32; 4] {
886 const P: i32 = ImageOrientation::UNIT;
887 const N: i32 = -ImageOrientation::UNIT;
888 match self {
889 Self::TopLeft => [P, 0, 0, P],
890 Self::TopRight => [N, 0, 0, P],
891 Self::BottomRight => [N, 0, 0, N],
892 Self::BottomLeft => [P, 0, 0, N],
893 Self::LeftTop => [0, P, P, 0],
894 Self::RightTop => [0, P, N, 0],
895 Self::RightBottom => [0, N, N, 0],
896 Self::LeftBottom => [0, N, P, 0],
897 Self::Other(matrix) => [matrix[0], matrix[1], matrix[3], matrix[4]],
898 }
899 }
900}
901
902#[derive(Clone, Debug, Default)]
925pub struct ImageFrameExtra {
926 orientation: Option<ImageOrientation>,
927 side_data: Vec<SideDataEntry>,
928}
929
930impl ImageFrameExtra {
931 #[cfg_attr(not(tarpaulin), inline(always))]
934 pub const fn new() -> Self {
935 Self {
936 orientation: None,
937 side_data: Vec::new(),
938 }
939 }
940
941 #[cfg_attr(not(tarpaulin), inline(always))]
952 pub const fn orientation(&self) -> Option<ImageOrientation> {
953 self.orientation
954 }
955
956 #[cfg_attr(not(tarpaulin), inline(always))]
959 pub fn side_data(&self) -> &[SideDataEntry] {
960 self.side_data.as_slice()
961 }
962
963 #[cfg_attr(not(tarpaulin), inline(always))]
965 #[must_use]
966 pub const fn with_orientation(mut self, value: Option<ImageOrientation>) -> Self {
967 self.orientation = value;
968 self
969 }
970
971 #[cfg_attr(not(tarpaulin), inline(always))]
973 #[must_use]
974 pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
975 self.side_data = value;
976 self
977 }
978
979 #[cfg_attr(not(tarpaulin), inline(always))]
981 pub const fn set_orientation(&mut self, value: Option<ImageOrientation>) -> &mut Self {
982 self.orientation = value;
983 self
984 }
985
986 #[cfg_attr(not(tarpaulin), inline(always))]
988 pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
989 self.side_data = value;
990 self
991 }
992}
993
994#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, IsVariant)]
996#[non_exhaustive]
997pub enum PictureType {
998 #[default]
1000 Unspecified,
1001 I,
1003 P,
1005 B,
1007 S,
1009 Si,
1011 Sp,
1013 Bi,
1015}
1016
1017#[derive(Clone, Debug)]
1025pub struct SideDataEntry {
1026 kind: i32,
1027 data: FfmpegBytes,
1028}
1029
1030impl SideDataEntry {
1031 #[cfg_attr(not(tarpaulin), inline(always))]
1033 pub const fn new(kind: i32, data: FfmpegBytes) -> Self {
1034 Self { kind, data }
1035 }
1036
1037 #[cfg_attr(not(tarpaulin), inline(always))]
1039 pub const fn kind(&self) -> i32 {
1040 self.kind
1041 }
1042 #[cfg_attr(not(tarpaulin), inline(always))]
1044 pub fn data(&self) -> &[u8] {
1045 self.data.as_slice()
1046 }
1047 #[cfg_attr(not(tarpaulin), inline(always))]
1050 pub const fn data_ref(&self) -> &FfmpegBytes {
1051 &self.data
1052 }
1053
1054 #[cfg_attr(not(tarpaulin), inline(always))]
1056 #[must_use]
1057 pub const fn with_kind(mut self, value: i32) -> Self {
1058 self.kind = value;
1059 self
1060 }
1061 #[cfg_attr(not(tarpaulin), inline(always))]
1063 #[must_use]
1064 pub fn with_data(mut self, value: FfmpegBytes) -> Self {
1065 self.data = value;
1066 self
1067 }
1068
1069 #[cfg_attr(not(tarpaulin), inline(always))]
1071 pub const fn set_kind(&mut self, value: i32) -> &mut Self {
1072 self.kind = value;
1073 self
1074 }
1075 #[cfg_attr(not(tarpaulin), inline(always))]
1077 pub fn set_data(&mut self, value: FfmpegBytes) -> &mut Self {
1078 self.data = value;
1079 self
1080 }
1081}
1082
1083#[derive(Copy, Clone, Debug, PartialEq)]
1085pub struct MasteringDisplay {
1086 display_primaries: [(u32, u32); 3],
1087 white_point: (u32, u32),
1088 max_luminance: (u32, u32),
1089 min_luminance: (u32, u32),
1090}
1091
1092impl MasteringDisplay {
1093 #[cfg_attr(not(tarpaulin), inline(always))]
1095 pub const fn new(
1096 display_primaries: [(u32, u32); 3],
1097 white_point: (u32, u32),
1098 max_luminance: (u32, u32),
1099 min_luminance: (u32, u32),
1100 ) -> Self {
1101 Self {
1102 display_primaries,
1103 white_point,
1104 max_luminance,
1105 min_luminance,
1106 }
1107 }
1108
1109 #[cfg_attr(not(tarpaulin), inline(always))]
1112 pub const fn display_primaries(&self) -> [(u32, u32); 3] {
1113 self.display_primaries
1114 }
1115 #[cfg_attr(not(tarpaulin), inline(always))]
1117 pub const fn white_point(&self) -> (u32, u32) {
1118 self.white_point
1119 }
1120 #[cfg_attr(not(tarpaulin), inline(always))]
1122 pub const fn max_luminance(&self) -> (u32, u32) {
1123 self.max_luminance
1124 }
1125 #[cfg_attr(not(tarpaulin), inline(always))]
1127 pub const fn min_luminance(&self) -> (u32, u32) {
1128 self.min_luminance
1129 }
1130
1131 #[cfg_attr(not(tarpaulin), inline(always))]
1133 pub const fn with_display_primaries(mut self, value: [(u32, u32); 3]) -> Self {
1134 self.display_primaries = value;
1135 self
1136 }
1137 #[cfg_attr(not(tarpaulin), inline(always))]
1139 pub const fn with_white_point(mut self, value: (u32, u32)) -> Self {
1140 self.white_point = value;
1141 self
1142 }
1143 #[cfg_attr(not(tarpaulin), inline(always))]
1145 pub const fn with_max_luminance(mut self, value: (u32, u32)) -> Self {
1146 self.max_luminance = value;
1147 self
1148 }
1149 #[cfg_attr(not(tarpaulin), inline(always))]
1151 pub const fn with_min_luminance(mut self, value: (u32, u32)) -> Self {
1152 self.min_luminance = value;
1153 self
1154 }
1155
1156 #[cfg_attr(not(tarpaulin), inline(always))]
1158 pub const fn set_display_primaries(&mut self, value: [(u32, u32); 3]) -> &mut Self {
1159 self.display_primaries = value;
1160 self
1161 }
1162 #[cfg_attr(not(tarpaulin), inline(always))]
1164 pub const fn set_white_point(&mut self, value: (u32, u32)) -> &mut Self {
1165 self.white_point = value;
1166 self
1167 }
1168 #[cfg_attr(not(tarpaulin), inline(always))]
1170 pub const fn set_max_luminance(&mut self, value: (u32, u32)) -> &mut Self {
1171 self.max_luminance = value;
1172 self
1173 }
1174 #[cfg_attr(not(tarpaulin), inline(always))]
1176 pub const fn set_min_luminance(&mut self, value: (u32, u32)) -> &mut Self {
1177 self.min_luminance = value;
1178 self
1179 }
1180}
1181
1182#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
1184pub struct ContentLightLevel {
1185 max_cll: u32,
1186 max_fall: u32,
1187}
1188
1189impl ContentLightLevel {
1190 #[cfg_attr(not(tarpaulin), inline(always))]
1192 pub const fn new(max_cll: u32, max_fall: u32) -> Self {
1193 Self { max_cll, max_fall }
1194 }
1195
1196 #[cfg_attr(not(tarpaulin), inline(always))]
1198 pub const fn max_cll(&self) -> u32 {
1199 self.max_cll
1200 }
1201 #[cfg_attr(not(tarpaulin), inline(always))]
1203 pub const fn max_fall(&self) -> u32 {
1204 self.max_fall
1205 }
1206
1207 #[cfg_attr(not(tarpaulin), inline(always))]
1209 #[must_use]
1210 pub const fn with_max_cll(mut self, value: u32) -> Self {
1211 self.max_cll = value;
1212 self
1213 }
1214 #[cfg_attr(not(tarpaulin), inline(always))]
1216 #[must_use]
1217 pub const fn with_max_fall(mut self, value: u32) -> Self {
1218 self.max_fall = value;
1219 self
1220 }
1221
1222 #[cfg_attr(not(tarpaulin), inline(always))]
1224 pub const fn set_max_cll(&mut self, value: u32) -> &mut Self {
1225 self.max_cll = value;
1226 self
1227 }
1228 #[cfg_attr(not(tarpaulin), inline(always))]
1230 pub const fn set_max_fall(&mut self, value: u32) -> &mut Self {
1231 self.max_fall = value;
1232 self
1233 }
1234}
1235
1236#[derive(Clone, Debug, Default)]
1248pub struct DataPacketExtra {
1249 stream_index: i32,
1250 byte_pos: Option<i64>,
1251 side_data: Vec<SideDataEntry>,
1252}
1253
1254impl DataPacketExtra {
1255 #[cfg_attr(not(tarpaulin), inline(always))]
1258 pub const fn new(stream_index: i32) -> Self {
1259 Self {
1260 stream_index,
1261 byte_pos: None,
1262 side_data: Vec::new(),
1263 }
1264 }
1265
1266 #[cfg_attr(not(tarpaulin), inline(always))]
1268 pub const fn stream_index(&self) -> i32 {
1269 self.stream_index
1270 }
1271 #[cfg_attr(not(tarpaulin), inline(always))]
1274 pub const fn byte_pos(&self) -> Option<i64> {
1275 self.byte_pos
1276 }
1277 #[cfg_attr(not(tarpaulin), inline(always))]
1279 pub fn side_data(&self) -> &[SideDataEntry] {
1280 self.side_data.as_slice()
1281 }
1282
1283 #[cfg_attr(not(tarpaulin), inline(always))]
1285 #[must_use]
1286 pub const fn with_stream_index(mut self, value: i32) -> Self {
1287 self.stream_index = value;
1288 self
1289 }
1290 #[cfg_attr(not(tarpaulin), inline(always))]
1292 #[must_use]
1293 pub const fn with_byte_pos(mut self, value: Option<i64>) -> Self {
1294 self.byte_pos = value;
1295 self
1296 }
1297 #[cfg_attr(not(tarpaulin), inline(always))]
1299 #[must_use]
1300 pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
1301 self.side_data = value;
1302 self
1303 }
1304
1305 #[cfg_attr(not(tarpaulin), inline(always))]
1307 pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
1308 self.stream_index = value;
1309 self
1310 }
1311 #[cfg_attr(not(tarpaulin), inline(always))]
1313 pub const fn set_byte_pos(&mut self, value: Option<i64>) -> &mut Self {
1314 self.byte_pos = value;
1315 self
1316 }
1317 #[cfg_attr(not(tarpaulin), inline(always))]
1319 pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
1320 self.side_data = value;
1321 self
1322 }
1323}
1324
1325#[derive(Clone, Debug, Default)]
1335pub struct AttachmentPacketExtra {
1336 stream_index: i32,
1337 synthesized: bool,
1338}
1339
1340impl AttachmentPacketExtra {
1341 #[cfg_attr(not(tarpaulin), inline(always))]
1344 pub const fn new(stream_index: i32) -> Self {
1345 Self {
1346 stream_index,
1347 synthesized: false,
1348 }
1349 }
1350
1351 #[cfg_attr(not(tarpaulin), inline(always))]
1353 pub const fn stream_index(&self) -> i32 {
1354 self.stream_index
1355 }
1356 #[cfg_attr(not(tarpaulin), inline(always))]
1359 pub const fn synthesized(&self) -> bool {
1360 self.synthesized
1361 }
1362
1363 #[cfg_attr(not(tarpaulin), inline(always))]
1365 #[must_use]
1366 pub const fn with_stream_index(mut self, value: i32) -> Self {
1367 self.stream_index = value;
1368 self
1369 }
1370 #[cfg_attr(not(tarpaulin), inline(always))]
1372 #[must_use]
1373 pub const fn with_synthesized(mut self, value: bool) -> Self {
1374 self.synthesized = value;
1375 self
1376 }
1377
1378 #[cfg_attr(not(tarpaulin), inline(always))]
1380 pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
1381 self.stream_index = value;
1382 self
1383 }
1384 #[cfg_attr(not(tarpaulin), inline(always))]
1386 pub const fn set_synthesized(&mut self, value: bool) -> &mut Self {
1387 self.synthesized = value;
1388 self
1389 }
1390}
1391
1392#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
1398pub struct ParameterFootprint {
1399 extradata: usize,
1400 extradata_payload: usize,
1401 coded_side_data: usize,
1402 channel_map: usize,
1403}
1404
1405impl ParameterFootprint {
1406 #[cfg_attr(not(tarpaulin), inline(always))]
1415 pub const fn extradata(&self) -> usize {
1416 self.extradata
1417 }
1418
1419 #[cfg_attr(not(tarpaulin), inline(always))]
1430 pub const fn extradata_payload(&self) -> usize {
1431 self.extradata_payload
1432 }
1433 #[cfg_attr(not(tarpaulin), inline(always))]
1436 pub const fn coded_side_data(&self) -> usize {
1437 self.coded_side_data
1438 }
1439 #[cfg_attr(not(tarpaulin), inline(always))]
1442 pub const fn channel_map(&self) -> usize {
1443 self.channel_map
1444 }
1445 #[cfg_attr(not(tarpaulin), inline(always))]
1450 pub const fn total(&self) -> Option<usize> {
1451 match self.extradata.checked_add(self.coded_side_data) {
1452 Some(sum) => sum.checked_add(self.channel_map),
1453 None => None,
1454 }
1455 }
1456
1457 #[cfg_attr(not(tarpaulin), inline(always))]
1465 pub const fn total_without_extradata(&self) -> Option<usize> {
1466 self.coded_side_data.checked_add(self.channel_map)
1467 }
1468}
1469
1470#[cfg(target_pointer_width = "64")]
1483const _: () = {
1484 assert!(
1485 core::mem::size_of::<ffmpeg_next::ffi::AVCodecParameters>() == 184,
1486 "AVCodecParameters changed shape — re-census its heap fields against \
1487 `measure_parameters` and `bounded_clone_parameters` before raising this",
1488 );
1489};
1490
1491pub(crate) unsafe fn measure_parameters(
1503 par: *const ffmpeg_next::ffi::AVCodecParameters,
1504) -> Option<ParameterFootprint> {
1505 let extradata_payload = if unsafe { (*par).extradata }.is_null() {
1508 0
1509 } else {
1510 usize::try_from(unsafe { (*par).extradata_size }).ok()?
1511 };
1512 let extradata = if extradata_payload == 0 {
1516 0
1517 } else {
1518 extradata_payload.checked_add(ffmpeg_next::ffi::AV_INPUT_BUFFER_PADDING_SIZE as usize)?
1519 };
1520
1521 let side_data_ptr = unsafe { (*par).coded_side_data };
1522 let side_data_count = unsafe { (*par).nb_coded_side_data };
1523 let coded_side_data = if side_data_ptr.is_null() || side_data_count <= 0 {
1524 0
1525 } else {
1526 let count = usize::try_from(side_data_count).ok()?;
1527 let mut total = count.checked_mul(core::mem::size_of::<ffmpeg_next::ffi::AVPacketSideData>())?;
1528 for index in 0..count {
1529 let size =
1544 unsafe { core::ptr::read_unaligned(core::ptr::addr_of!((*side_data_ptr.add(index)).size)) };
1545 total = total.checked_add(size)?;
1546 }
1547 total
1548 };
1549
1550 let channel_map = {
1551 let order = unsafe {
1562 core::ptr::read_unaligned(core::ptr::addr_of!((*par).ch_layout.order).cast::<i32>())
1563 };
1564 const UNSPEC: i32 = ffmpeg_next::ffi::AVChannelOrder::AV_CHANNEL_ORDER_UNSPEC as i32;
1565 const NATIVE: i32 = ffmpeg_next::ffi::AVChannelOrder::AV_CHANNEL_ORDER_NATIVE as i32;
1566 const CUSTOM: i32 = ffmpeg_next::ffi::AVChannelOrder::AV_CHANNEL_ORDER_CUSTOM as i32;
1567 const AMBISONIC: i32 = ffmpeg_next::ffi::AVChannelOrder::AV_CHANNEL_ORDER_AMBISONIC as i32;
1568 match order {
1569 UNSPEC | NATIVE | AMBISONIC => 0,
1572 CUSTOM => {
1574 let channels = usize::try_from(unsafe { (*par).ch_layout.nb_channels }).ok()?;
1575 channels.checked_mul(core::mem::size_of::<ffmpeg_next::ffi::AVChannelCustom>())?
1576 }
1577 _ => return None,
1581 }
1582 };
1583
1584 Some(ParameterFootprint {
1585 extradata,
1586 extradata_payload,
1587 coded_side_data,
1588 channel_map,
1589 })
1590}
1591
1592#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
1609pub(crate) enum ExtradataPolicy {
1610 #[default]
1612 Copy,
1613 Omit,
1615}
1616
1617fn seat_copy_failed(stream_index: usize) -> DemuxError {
1625 DemuxError::ParametersCopy(ParametersCopy::new(
1626 stream_index,
1627 ffmpeg_next::Error::Other {
1628 errno: libc::ENOMEM,
1629 },
1630 ))
1631}
1632
1633pub(crate) fn bounded_clone_parameters(
1678 source: &Parameters,
1679 stream_index: usize,
1680 budget: usize,
1681) -> Result<Parameters, DemuxError> {
1682 bounded_clone_parameters_with(source, stream_index, budget, ExtradataPolicy::Copy)
1683}
1684
1685pub(crate) fn bounded_clone_parameters_with(
1687 source: &Parameters,
1688 stream_index: usize,
1689 budget: usize,
1690 extradata_policy: ExtradataPolicy,
1691) -> Result<Parameters, DemuxError> {
1692 let src = unsafe { source.as_ptr() };
1698 if src.is_null() {
1699 return Err(DemuxError::ParametersMissing(ParametersMissing::new(
1700 stream_index,
1701 )));
1702 }
1703
1704 let footprint = unsafe { measure_parameters(src) }.ok_or_else(|| {
1707 DemuxError::ParametersTooLarge(ParametersTooLarge::new(stream_index, usize::MAX, budget))
1708 })?;
1709 let total = match extradata_policy {
1714 ExtradataPolicy::Copy => footprint.total(),
1715 ExtradataPolicy::Omit => footprint.total_without_extradata(),
1716 }
1717 .ok_or_else(|| {
1718 DemuxError::ParametersTooLarge(ParametersTooLarge::new(stream_index, usize::MAX, budget))
1719 })?;
1720 if total > budget {
1721 return Err(DemuxError::ParametersTooLarge(ParametersTooLarge::new(
1722 stream_index,
1723 total,
1724 budget,
1725 )));
1726 }
1727
1728 let mut out = Parameters::new();
1729 let dst = unsafe { out.as_mut_ptr() };
1732 if dst.is_null() {
1733 return Err(DemuxError::ParametersAlloc(ParametersAlloc::new(
1734 stream_index,
1735 )));
1736 }
1737
1738 unsafe {
1744 core::ptr::copy_nonoverlapping(src, dst, 1);
1745 (*dst).extradata = core::ptr::null_mut();
1746 (*dst).extradata_size = 0;
1747 (*dst).coded_side_data = core::ptr::null_mut();
1748 (*dst).nb_coded_side_data = 0;
1749 (*dst).ch_layout = core::mem::zeroed();
1754 }
1755
1756 if footprint.extradata() > 0 && matches!(extradata_policy, ExtradataPolicy::Copy) {
1761 let padded = footprint.extradata();
1762 unsafe {
1766 let buffer = ffmpeg_next::ffi::av_mallocz(padded) as *mut u8;
1767 if buffer.is_null() {
1768 return Err(seat_copy_failed(stream_index));
1769 }
1770 let payload =
1771 usize::try_from((*src).extradata_size).map_err(|_| seat_copy_failed(stream_index))?;
1772 core::ptr::copy_nonoverlapping((*src).extradata, buffer, payload);
1773 (*dst).extradata = buffer;
1774 (*dst).extradata_size = (*src).extradata_size;
1775 }
1776 }
1777
1778 unsafe {
1784 let count = (*src).nb_coded_side_data;
1785 if count > 0 && !(*src).coded_side_data.is_null() {
1786 let entries = usize::try_from(count)
1787 .ok()
1788 .and_then(|c| c.checked_mul(core::mem::size_of::<ffmpeg_next::ffi::AVPacketSideData>()))
1789 .ok_or_else(|| seat_copy_failed(stream_index))?;
1790 let array = ffmpeg_next::ffi::av_mallocz(entries) as *mut ffmpeg_next::ffi::AVPacketSideData;
1791 if array.is_null() {
1792 return Err(seat_copy_failed(stream_index));
1793 }
1794 (*dst).coded_side_data = array;
1799 (*dst).nb_coded_side_data = count;
1800 for index in 0..count as usize {
1801 let from = (*src).coded_side_data.add(index);
1806 let into = array.add(index);
1807 let kind = core::ptr::read_unaligned(core::ptr::addr_of!((*from).type_).cast::<i32>());
1812 core::ptr::write_unaligned(core::ptr::addr_of_mut!((*into).type_).cast::<i32>(), kind);
1813
1814 let size = core::ptr::read_unaligned(core::ptr::addr_of!((*from).size));
1815 let data = core::ptr::read_unaligned(core::ptr::addr_of!((*from).data));
1816 if size > 0 && !data.is_null() {
1817 let payload = ffmpeg_next::ffi::av_mallocz(size) as *mut u8;
1818 if payload.is_null() {
1819 return Err(seat_copy_failed(stream_index));
1820 }
1821 core::ptr::copy_nonoverlapping(data, payload, size);
1822 core::ptr::write_unaligned(core::ptr::addr_of_mut!((*into).data), payload);
1823 core::ptr::write_unaligned(core::ptr::addr_of_mut!((*into).size), size);
1824 } else {
1825 core::ptr::write_unaligned(core::ptr::addr_of_mut!((*into).size), 0);
1826 }
1827 }
1828 }
1829 }
1830
1831 let rc = unsafe {
1839 ffmpeg_next::ffi::av_channel_layout_copy(
1840 core::ptr::addr_of_mut!((*dst).ch_layout),
1841 core::ptr::addr_of!((*src).ch_layout),
1842 )
1843 };
1844 if rc < 0 {
1845 return Err(DemuxError::ParametersCopy(ParametersCopy::new(
1846 stream_index,
1847 ffmpeg_next::Error::from(rc),
1848 )));
1849 }
1850
1851 Ok(out)
1852}
1853
1854pub struct TrackExtra {
1892 stream_index: i32,
1893 disposition: i32,
1894 start_time: Option<i64>,
1895 frame_count: Option<i64>,
1896 parameters: Parameters,
1897 parameter_bytes: usize,
1900}
1901
1902impl TrackExtra {
1903 pub fn new(stream_index: i32, parameters: Parameters) -> Result<Self, DemuxError> {
1917 let par = unsafe { parameters.as_ptr() };
1919 if par.is_null() {
1920 return Err(DemuxError::ParametersMissing(ParametersMissing::new(
1921 stream_index.max(0) as usize,
1922 )));
1923 }
1924 let parameter_bytes = unsafe { measure_parameters(par) }
1935 .and_then(|footprint| footprint.total())
1936 .ok_or_else(|| {
1937 DemuxError::ParametersTooLarge(ParametersTooLarge::new(
1938 stream_index.max(0) as usize,
1939 usize::MAX,
1940 usize::MAX,
1941 ))
1942 })?;
1943 Ok(Self {
1944 stream_index,
1945 disposition: 0,
1946 start_time: None,
1947 frame_count: None,
1948 parameters,
1949 parameter_bytes,
1950 })
1951 }
1952
1953 #[cfg_attr(not(tarpaulin), inline(always))]
1959 pub const fn parameter_bytes(&self) -> usize {
1960 self.parameter_bytes
1961 }
1962
1963 pub fn try_clone(&self) -> Result<Self, DemuxError> {
1968 Ok(Self {
1971 stream_index: self.stream_index,
1972 disposition: self.disposition,
1973 start_time: self.start_time,
1974 frame_count: self.frame_count,
1975 parameters: self.clone_parameters()?,
1976 parameter_bytes: self.parameter_bytes,
1977 })
1978 }
1979
1980 pub fn clone_parameters(&self) -> Result<Parameters, DemuxError> {
1988 bounded_clone_parameters(
1994 &self.parameters,
1995 self.stream_index.max(0) as usize,
1996 self.parameter_bytes,
1997 )
1998 }
1999
2000 #[cfg_attr(not(tarpaulin), inline(always))]
2002 pub const fn stream_index(&self) -> i32 {
2003 self.stream_index
2004 }
2005 #[cfg_attr(not(tarpaulin), inline(always))]
2007 pub const fn disposition(&self) -> i32 {
2008 self.disposition
2009 }
2010 #[cfg_attr(not(tarpaulin), inline(always))]
2013 pub const fn start_time(&self) -> Option<i64> {
2014 self.start_time
2015 }
2016 #[cfg_attr(not(tarpaulin), inline(always))]
2018 pub const fn frame_count(&self) -> Option<i64> {
2019 self.frame_count
2020 }
2021 #[cfg_attr(not(tarpaulin), inline(always))]
2024 pub const fn parameters(&self) -> &Parameters {
2025 &self.parameters
2026 }
2027
2028 #[cfg_attr(not(tarpaulin), inline(always))]
2030 #[must_use]
2031 pub const fn with_disposition(mut self, value: i32) -> Self {
2032 self.disposition = value;
2033 self
2034 }
2035 #[cfg_attr(not(tarpaulin), inline(always))]
2037 #[must_use]
2038 pub const fn with_start_time(mut self, value: Option<i64>) -> Self {
2039 self.start_time = value;
2040 self
2041 }
2042 #[cfg_attr(not(tarpaulin), inline(always))]
2044 #[must_use]
2045 pub const fn with_frame_count(mut self, value: Option<i64>) -> Self {
2046 self.frame_count = value;
2047 self
2048 }
2049
2050 #[cfg_attr(not(tarpaulin), inline(always))]
2052 pub const fn set_disposition(&mut self, value: i32) -> &mut Self {
2053 self.disposition = value;
2054 self
2055 }
2056 #[cfg_attr(not(tarpaulin), inline(always))]
2058 pub const fn set_start_time(&mut self, value: Option<i64>) -> &mut Self {
2059 self.start_time = value;
2060 self
2061 }
2062 #[cfg_attr(not(tarpaulin), inline(always))]
2064 pub const fn set_frame_count(&mut self, value: Option<i64>) -> &mut Self {
2065 self.frame_count = value;
2066 self
2067 }
2068}
2069
2070impl std::fmt::Debug for TrackExtra {
2071 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2076 f.debug_struct("TrackExtra")
2077 .field("stream_index", &self.stream_index)
2078 .field("disposition", &format_args!("{:#x}", self.disposition))
2079 .field("start_time", &self.start_time)
2080 .field("frame_count", &self.frame_count)
2081 .field(
2082 "parameters",
2083 &format_args!("{:?}", crate::boundary::media_kind_of(&self.parameters)),
2084 )
2085 .finish()
2086 }
2087}
2088
2089#[cfg(test)]
2090mod tests {
2091 use super::*;
2092
2093 #[test]
2094 fn defaults_construct() {
2095 let v = VideoPacketExtra::default();
2096 assert_eq!(v.stream_index(), 0);
2097 assert!(v.side_data().is_empty());
2098
2099 let f = VideoFrameExtra::default();
2100 assert_eq!(f.picture_type(), PictureType::Unspecified);
2101 assert!(!f.key_frame());
2102 assert!(f.mastering_display().is_none());
2103
2104 let s = SubtitleFrameExtra::default();
2105 assert_eq!(s.start_display_time(), 0);
2106 assert_eq!(s.end_display_time(), 0);
2107 }
2108
2109 #[test]
2110 fn picture_type_default_is_unspecified() {
2111 assert_eq!(PictureType::default(), PictureType::Unspecified);
2112 }
2113
2114 fn parameters_with(extradata: usize, icc_profile: usize) -> Parameters {
2124 let mut out = Parameters::new();
2125 unsafe {
2129 let par = out.as_mut_ptr();
2130 if extradata > 0 {
2131 let buffer = ffmpeg_next::ffi::av_mallocz(extradata) as *mut u8;
2132 assert!(!buffer.is_null(), "av_mallocz extradata");
2133 (*par).extradata = buffer;
2134 (*par).extradata_size = extradata as i32;
2135 }
2136 if icc_profile > 0 {
2137 let array = ffmpeg_next::ffi::av_mallocz(core::mem::size_of::<
2138 ffmpeg_next::ffi::AVPacketSideData,
2139 >()) as *mut ffmpeg_next::ffi::AVPacketSideData;
2140 assert!(!array.is_null(), "av_mallocz side-data array");
2141 let payload = ffmpeg_next::ffi::av_mallocz(icc_profile) as *mut u8;
2142 assert!(!payload.is_null(), "av_mallocz icc profile");
2143 (*array).data = payload;
2144 (*array).size = icc_profile;
2145 (*array).type_ = ffmpeg_next::ffi::AVPacketSideDataType::AV_PKT_DATA_ICC_PROFILE;
2146 (*par).coded_side_data = array;
2147 (*par).nb_coded_side_data = 1;
2148 }
2149 }
2150 out
2151 }
2152
2153 fn footprint_of(parameters: &Parameters) -> ParameterFootprint {
2154 unsafe { measure_parameters(parameters.as_ptr()) }.expect("measurable")
2156 }
2157
2158 #[test]
2159 fn the_measurement_counts_every_heap_seat_and_allocates_nothing() {
2160 const PAD: usize = ffmpeg_next::ffi::AV_INPUT_BUFFER_PADDING_SIZE as usize;
2165 const DESCRIPTOR: usize = core::mem::size_of::<ffmpeg_next::ffi::AVPacketSideData>();
2166 let parameters = parameters_with(4_096, 64 * 1024);
2167 let footprint = footprint_of(¶meters);
2168 assert_eq!(footprint.extradata(), 4_096 + PAD);
2172 assert_eq!(
2173 footprint.coded_side_data(),
2174 64 * 1024 + DESCRIPTOR,
2175 "the descriptor array is an allocation too",
2176 );
2177 assert_eq!(footprint.channel_map(), 0, "no custom layout here");
2178 assert_eq!(
2179 footprint.total(),
2180 Some(4_096 + PAD + 64 * 1024 + DESCRIPTOR),
2181 );
2182 assert_eq!(
2186 footprint.total_without_extradata(),
2187 Some(64 * 1024 + DESCRIPTOR),
2188 );
2189 assert_eq!(footprint_of(¶meters_with(0, 8)).extradata(), 0);
2191 }
2192
2193 #[test]
2194 fn an_oversized_coded_side_data_entry_is_refused_before_the_clone() {
2195 let parameters = parameters_with(0, 8 * 1024 * 1024);
2199 let declared = footprint_of(¶meters).total().expect("measurable");
2200
2201 match bounded_clone_parameters(¶meters, 3, 64 * 1024) {
2202 Err(DemuxError::ParametersTooLarge(p)) => {
2203 assert_eq!(p.stream_index(), 3);
2204 assert_eq!(p.bytes(), declared);
2205 assert_eq!(p.limit(), 64 * 1024);
2206 }
2207 Err(other) => panic!("expected ParametersTooLarge, got {other:?}"),
2208 Ok(_) => panic!("an 8 MiB ICC profile passed a 64 KiB ceiling"),
2209 }
2210
2211 let cloned =
2213 bounded_clone_parameters(¶meters, 3, declared).expect("at the cap is not over it");
2214 assert_eq!(footprint_of(&cloned), footprint_of(¶meters));
2215 }
2216
2217 #[test]
2218 fn a_legitimate_multi_megabyte_icc_profile_is_admitted_by_default() {
2219 let parameters = parameters_with(1_024, 4 * 1024 * 1024);
2223 let cloned = bounded_clone_parameters(
2224 ¶meters,
2225 0,
2226 crate::limits::DEFAULT_MAX_CODEC_PARAMETER_BYTES,
2227 )
2228 .expect("a 4 MiB ICC profile is real media");
2229 assert_eq!(footprint_of(&cloned), footprint_of(¶meters));
2232 }
2233
2234 #[test]
2235 fn the_bounded_clone_keeps_every_field_a_decoder_consumes() {
2236 let parameters = parameters_with(32, 128);
2241 unsafe {
2244 let par = parameters.as_ptr() as *mut ffmpeg_next::ffi::AVCodecParameters;
2245 (*par).codec_id = ffmpeg_next::ffi::AVCodecID::AV_CODEC_ID_H264;
2246 (*par).width = 1920;
2247 (*par).height = 1080;
2248 (*par).bit_rate = 5_000_000;
2249 (*par).sample_rate = 48_000;
2250 core::ptr::write_bytes((*par).extradata, 0xAB, 32);
2251 core::ptr::write_bytes((*(*par).coded_side_data).data, 0xCD, 128);
2252 }
2253
2254 let cloned = bounded_clone_parameters(¶meters, 0, usize::MAX).expect("clone");
2255 unsafe {
2257 let src = parameters.as_ptr();
2258 let dst = cloned.as_ptr();
2259 assert_eq!((*dst).codec_id, (*src).codec_id, "the scalar sweep");
2260 assert_eq!(((*dst).width, (*dst).height), (1920, 1080));
2261 assert_eq!((*dst).bit_rate, 5_000_000);
2262 assert_eq!((*dst).sample_rate, 48_000);
2263
2264 assert_eq!((*dst).extradata_size, 32);
2265 assert_ne!(
2266 (*dst).extradata,
2267 (*src).extradata,
2268 "it is a copy, not an alias"
2269 );
2270 let extradata = core::slice::from_raw_parts((*dst).extradata, 32);
2271 assert!(extradata.iter().all(|&b| b == 0xAB), "SPS/PPS survived");
2272 let padded = core::slice::from_raw_parts(
2275 (*dst).extradata.add(32),
2276 ffmpeg_next::ffi::AV_INPUT_BUFFER_PADDING_SIZE as usize,
2277 );
2278 assert!(padded.iter().all(|&b| b == 0), "the read-past padding");
2279
2280 assert_eq!((*dst).nb_coded_side_data, 1);
2281 let entry = &*(*dst).coded_side_data;
2282 assert_eq!(entry.size, 128);
2283 assert_eq!(
2284 entry.type_,
2285 ffmpeg_next::ffi::AVPacketSideDataType::AV_PKT_DATA_ICC_PROFILE,
2286 );
2287 assert_ne!(entry.data, (*(*src).coded_side_data).data, "a copy");
2288 let payload = core::slice::from_raw_parts(entry.data, 128);
2289 assert!(
2290 payload.iter().all(|&b| b == 0xCD),
2291 "the ICC profile survived"
2292 );
2293 }
2294 }
2295
2296 #[test]
2297 fn side_data_entry_carries_bytes() {
2298 let entry = SideDataEntry::new(12345, FfmpegBytes::copy_from_slice(&[1, 2, 3, 4]));
2299 assert_eq!(entry.kind(), 12345);
2300 assert_eq!(entry.data(), &[1, 2, 3, 4]);
2301 }
2302
2303 #[test]
2304 fn side_data_entry_clone_shares_its_payload() {
2305 let entry = SideDataEntry::new(7, FfmpegBytes::copy_from_slice(&[9u8; 64]));
2308 let cloned = entry.clone();
2309 assert!(
2310 entry.data_ref().ptr_eq(cloned.data_ref()),
2311 "cloning a side-data entry copied its bytes",
2312 );
2313 assert_eq!(cloned.data(), entry.data());
2314 }
2315
2316 const MEASURED: [(u16, ImageOrientation, [i32; 4]); 8] = [
2321 (1, ImageOrientation::TopLeft, [65536, 0, 0, 65536]),
2322 (2, ImageOrientation::TopRight, [-65536, 0, 0, 65536]),
2323 (3, ImageOrientation::BottomRight, [-65536, 0, 0, -65536]),
2324 (4, ImageOrientation::BottomLeft, [65536, 0, 0, -65536]),
2325 (5, ImageOrientation::LeftTop, [0, 65536, 65536, 0]),
2326 (6, ImageOrientation::RightTop, [0, 65536, -65536, 0]),
2327 (7, ImageOrientation::RightBottom, [0, -65536, -65536, 0]),
2328 (8, ImageOrientation::LeftBottom, [0, -65536, 65536, 0]),
2329 ];
2330
2331 fn display_matrix(linear: [i32; 4]) -> Vec<u8> {
2336 words_to_bytes([
2337 linear[0],
2338 linear[1],
2339 0,
2340 linear[2],
2341 linear[3],
2342 0,
2343 0,
2344 0,
2345 1 << 30,
2346 ])
2347 }
2348
2349 fn words_to_bytes(words: [i32; 9]) -> Vec<u8> {
2350 words.iter().flat_map(|w| w.to_ne_bytes()).collect()
2351 }
2352
2353 #[test]
2354 fn every_measured_display_matrix_reads_back_as_its_exif_tag() {
2355 for (tag, expected, linear) in MEASURED {
2356 let read = ImageOrientation::from_display_matrix(&display_matrix(linear))
2357 .expect("a nine-word matrix is readable");
2358 assert_eq!(read, expected, "tag {tag}");
2359 assert_eq!(read.to_exif_code(), Some(tag));
2360 assert_eq!(ImageOrientation::from_exif_code(tag), Some(read));
2361 assert_eq!(read.linear(), linear, "tag {tag}");
2363 }
2364 }
2365
2366 #[test]
2367 fn the_four_mirrored_tags_are_the_ones_exif_says_they_are() {
2368 for (tag, orientation, _) in MEASURED {
2371 assert_eq!(
2372 orientation.is_mirrored(),
2373 matches!(tag, 2 | 4 | 5 | 7),
2374 "tag {tag}",
2375 );
2376 }
2377 }
2378
2379 #[test]
2380 fn the_quarter_turn_lands_in_the_workspace_rotation_vocabulary() {
2381 use ImageOrientation::*;
2382 assert_eq!(TopLeft.rotation(), Some(Rotation::D0));
2383 assert_eq!(TopRight.rotation(), Some(Rotation::D0));
2384 assert_eq!(RightTop.rotation(), Some(Rotation::D90));
2385 assert_eq!(LeftTop.rotation(), Some(Rotation::D90));
2386 assert_eq!(BottomRight.rotation(), Some(Rotation::D180));
2387 assert_eq!(BottomLeft.rotation(), Some(Rotation::D180));
2388 assert_eq!(LeftBottom.rotation(), Some(Rotation::D270));
2389 assert_eq!(RightBottom.rotation(), Some(Rotation::D270));
2390 assert_eq!(TopLeft.rotation(), TopRight.rotation());
2393 assert_ne!(TopLeft, TopRight);
2394 }
2395
2396 #[test]
2397 fn a_transform_the_vocabulary_cannot_name_is_carried_not_collapsed() {
2398 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];
2402 let read =
2403 ImageOrientation::from_display_matrix(&display_matrix(odd)).expect("readable, just unnamed");
2404 assert_eq!(read, ImageOrientation::Other(words));
2405 assert_eq!(read.to_exif_code(), None, "there is no tag to invent");
2406 assert_eq!(read.rotation(), None, "it is not a quarter turn");
2407 assert_eq!(read.linear(), odd, "the linear projection still answers");
2408 assert_eq!(read.matrix(), words, "and nothing was dropped");
2409 assert!(!read.is_mirrored());
2412 assert!(ImageOrientation::Other([65536, 0, 0, 0, -65536, 0, 0, 0, 1 << 30]).is_mirrored());
2413 }
2414
2415 #[test]
2416 fn a_noncanonical_word_keeps_a_matrix_out_of_the_named_variants() {
2417 let named = ImageOrientation::RightTop;
2425 let canonical = named.matrix();
2426 assert_eq!(
2427 ImageOrientation::from_display_matrix(&words_to_bytes(canonical)),
2428 Some(named),
2429 "the canonical matrix must still be named",
2430 );
2431
2432 for index in [2usize, 5, 6, 7, 8] {
2433 let mut forged = canonical;
2434 forged[index] = if index == 8 { 1 << 29 } else { 4_096 };
2437 let read = ImageOrientation::from_display_matrix(&words_to_bytes(forged))
2438 .expect("nine words are readable");
2439 assert_eq!(
2440 read,
2441 ImageOrientation::Other(forged),
2442 "word {index} was collapsed into a named variant",
2443 );
2444 assert_eq!(read.to_exif_code(), None, "word {index}");
2445 assert_eq!(read.matrix(), forged, "word {index} round-trips whole");
2446 assert_eq!(read.linear(), named.linear(), "word {index}");
2448 }
2449 }
2450
2451 #[test]
2452 fn the_escape_round_trips_every_word_losslessly() {
2453 let words: [i32; 9] = [1, -2, 3, -4, 5, -6, i32::MIN, i32::MAX, 0];
2456 let read = ImageOrientation::from_display_matrix(&words_to_bytes(words)).expect("readable");
2457 assert_eq!(read, ImageOrientation::Other(words));
2458 assert_eq!(read.matrix(), words);
2459 let again =
2461 ImageOrientation::from_display_matrix(&words_to_bytes(read.matrix())).expect("readable");
2462 assert_eq!(again, read);
2463 }
2464
2465 #[test]
2466 fn every_named_orientation_reconstructs_its_canonical_matrix() {
2467 for (tag, orientation, linear) in MEASURED {
2468 let matrix = orientation.matrix();
2469 assert_eq!(
2470 [matrix[0], matrix[1], matrix[3], matrix[4]],
2471 linear,
2472 "tag {tag}",
2473 );
2474 assert_eq!(
2475 [matrix[2], matrix[5], matrix[6], matrix[7]],
2476 [0, 0, 0, 0],
2477 "tag {tag}: no translation, no perspective",
2478 );
2479 assert_eq!(matrix[8], 1 << 30, "tag {tag}: unity `w`");
2480 assert_eq!(
2482 ImageOrientation::from_display_matrix(&words_to_bytes(matrix)),
2483 Some(orientation),
2484 "tag {tag}",
2485 );
2486 }
2487 }
2488
2489 #[test]
2490 fn a_malformed_matrix_is_no_orientation_rather_than_a_guessed_one() {
2491 assert_eq!(ImageOrientation::from_display_matrix(&[]), None);
2492 assert_eq!(ImageOrientation::from_display_matrix(&[0u8; 16]), None);
2493 assert_eq!(ImageOrientation::from_display_matrix(&[0u8; 40]), None);
2494 assert_eq!(
2496 ImageOrientation::DISPLAY_MATRIX_BYTES,
2497 36,
2498 "nine int32, per libavutil/display.h",
2499 );
2500 assert!(ImageOrientation::from_display_matrix(&[0u8; 36]).is_some());
2501 }
2502
2503 #[test]
2504 fn an_out_of_range_exif_tag_is_refused_not_clamped() {
2505 for code in [0u16, 9, 255, u16::MAX] {
2506 assert_eq!(ImageOrientation::from_exif_code(code), None, "code {code}");
2507 }
2508 }
2509
2510 #[test]
2511 fn the_orientation_seat_rides_the_image_extras() {
2512 let extra = ImageFrameExtra::default();
2513 assert_eq!(extra.orientation(), None, "absent until a file says");
2514
2515 let carried = ImageFrameExtra::new().with_orientation(Some(ImageOrientation::RightTop));
2516 assert_eq!(carried.orientation(), Some(ImageOrientation::RightTop));
2517
2518 let mut mutated = carried.clone();
2519 mutated.set_orientation(None);
2520 assert_eq!(mutated.orientation(), None);
2521 assert_eq!(carried.orientation(), Some(ImageOrientation::RightTop));
2522 }
2523
2524 #[test]
2525 fn the_image_household_is_one_seat() {
2526 let extra = ImageFrameExtra::default();
2527 assert!(extra.side_data().is_empty());
2528 let carried = ImageFrameExtra::new().with_side_data(vec![SideDataEntry::new(
2529 3,
2530 FfmpegBytes::copy_from_slice(&[1]),
2531 )]);
2532 assert_eq!(carried.side_data().len(), 1);
2533 assert_eq!(carried.side_data()[0].kind(), 3);
2534 let mut mutated = carried.clone();
2535 mutated.set_side_data(Vec::new());
2536 assert!(mutated.side_data().is_empty());
2537 assert_eq!(carried.side_data().len(), 1);
2538 }
2539
2540 #[test]
2541 fn content_light_level_default_is_zero() {
2542 let cll = ContentLightLevel::default();
2543 assert_eq!(cll.max_cll(), 0);
2544 assert_eq!(cll.max_fall(), 0);
2545 }
2546
2547 #[test]
2548 fn builders_chain() {
2549 let v = VideoPacketExtra::new(7)
2550 .with_byte_pos(Some(1234))
2551 .with_side_data(vec![SideDataEntry::new(
2552 1,
2553 FfmpegBytes::copy_from_slice(&[0xAB]),
2554 )]);
2555 assert_eq!(v.stream_index(), 7);
2556 assert_eq!(v.byte_pos(), Some(1234));
2557 assert_eq!(v.side_data().len(), 1);
2558 }
2559
2560 #[test]
2561 fn setters_chain() {
2562 let mut v = VideoPacketExtra::default();
2563 v.set_stream_index(3).set_byte_pos(Some(99));
2564 assert_eq!(v.stream_index(), 3);
2565 assert_eq!(v.byte_pos(), Some(99));
2566 }
2567}