Skip to main content

mediadecode_ffmpeg/
extras.rs

1//! Backend-specific `*Extra` carriers used as the
2//! `mediadecode::*Adapter::*Extra` associated types.
3//!
4//! Fields are private; values are read through getters and set through
5//! `with_*` (consuming builders) / `set_*` (in-place mutators) — the
6//! crate-wide encapsulation convention. `const fn` is used wherever
7//! the field type permits (i.e. anything but `Vec`).
8
9use std::vec::Vec;
10
11use ffmpeg_next::{codec::Parameters, ffi::avcodec_parameters_copy};
12
13use crate::demuxer::DemuxError;
14
15/// Per-`VideoPacket` extras.
16#[derive(Clone, Debug, Default)]
17pub struct VideoPacketExtra {
18  stream_index: i32,
19  byte_pos: Option<i64>,
20  side_data: Vec<SideDataEntry>,
21}
22
23impl VideoPacketExtra {
24  /// Constructs a `VideoPacketExtra` with the given stream index.
25  /// `byte_pos` defaults to `None` and `side_data` to empty.
26  #[cfg_attr(not(tarpaulin), inline(always))]
27  pub const fn new(stream_index: i32) -> Self {
28    Self {
29      stream_index,
30      byte_pos: None,
31      side_data: Vec::new(),
32    }
33  }
34
35  /// Returns the source `AVStream.index`.
36  #[cfg_attr(not(tarpaulin), inline(always))]
37  pub const fn stream_index(&self) -> i32 {
38    self.stream_index
39  }
40
41  /// Returns the byte position of the packet in the input file, or
42  /// `None` if unknown.
43  #[cfg_attr(not(tarpaulin), inline(always))]
44  pub const fn byte_pos(&self) -> Option<i64> {
45    self.byte_pos
46  }
47
48  /// Returns the raw side-data entries from `AVPacket.side_data`.
49  #[cfg_attr(not(tarpaulin), inline(always))]
50  pub fn side_data(&self) -> &[SideDataEntry] {
51    self.side_data.as_slice()
52  }
53
54  /// Sets the stream index (consuming builder).
55  #[cfg_attr(not(tarpaulin), inline(always))]
56  #[must_use]
57  pub const fn with_stream_index(mut self, value: i32) -> Self {
58    self.stream_index = value;
59    self
60  }
61  /// Sets the byte position (consuming builder).
62  #[cfg_attr(not(tarpaulin), inline(always))]
63  #[must_use]
64  pub const fn with_byte_pos(mut self, value: Option<i64>) -> Self {
65    self.byte_pos = value;
66    self
67  }
68  /// Sets the side-data list (consuming builder).
69  #[cfg_attr(not(tarpaulin), inline(always))]
70  #[must_use]
71  pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
72    self.side_data = value;
73    self
74  }
75
76  /// Sets the stream index in place.
77  #[cfg_attr(not(tarpaulin), inline(always))]
78  pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
79    self.stream_index = value;
80    self
81  }
82  /// Sets the byte position in place.
83  #[cfg_attr(not(tarpaulin), inline(always))]
84  pub const fn set_byte_pos(&mut self, value: Option<i64>) -> &mut Self {
85    self.byte_pos = value;
86    self
87  }
88  /// Sets the side-data list in place.
89  #[cfg_attr(not(tarpaulin), inline(always))]
90  pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
91    self.side_data = value;
92    self
93  }
94}
95
96/// Per-`VideoFrame` extras carrying everything the unified
97/// `mediadecode::ColorInfo` doesn't already cover.
98#[derive(Clone, Debug, Default)]
99pub struct VideoFrameExtra {
100  sample_aspect_ratio: Option<(u32, u32)>,
101  picture_type: PictureType,
102  key_frame: bool,
103  interlaced: bool,
104  top_field_first: bool,
105  best_effort_timestamp: Option<i64>,
106  mastering_display: Option<MasteringDisplay>,
107  content_light_level: Option<ContentLightLevel>,
108  smpte_timecode: Vec<u32>,
109  side_data: Vec<SideDataEntry>,
110}
111
112impl VideoFrameExtra {
113  /// Constructs an empty `VideoFrameExtra` (all fields at default).
114  #[cfg_attr(not(tarpaulin), inline(always))]
115  pub const fn new() -> Self {
116    Self {
117      sample_aspect_ratio: None,
118      picture_type: PictureType::Unspecified,
119      key_frame: false,
120      interlaced: false,
121      top_field_first: false,
122      best_effort_timestamp: None,
123      mastering_display: None,
124      content_light_level: None,
125      smpte_timecode: Vec::new(),
126      side_data: Vec::new(),
127    }
128  }
129
130  /// Sample aspect ratio (par numerator / denominator), `None` if 1:1
131  /// or unspecified.
132  #[cfg_attr(not(tarpaulin), inline(always))]
133  pub const fn sample_aspect_ratio(&self) -> Option<(u32, u32)> {
134    self.sample_aspect_ratio
135  }
136  /// Frame picture type (I/P/B/etc.).
137  #[cfg_attr(not(tarpaulin), inline(always))]
138  pub const fn picture_type(&self) -> PictureType {
139    self.picture_type
140  }
141  /// `True` if this frame is a key frame.
142  #[cfg_attr(not(tarpaulin), inline(always))]
143  pub const fn key_frame(&self) -> bool {
144    self.key_frame
145  }
146  /// `True` if the frame is interlaced.
147  #[cfg_attr(not(tarpaulin), inline(always))]
148  pub const fn interlaced(&self) -> bool {
149    self.interlaced
150  }
151  /// `True` if the top field is first (only meaningful with `interlaced`).
152  #[cfg_attr(not(tarpaulin), inline(always))]
153  pub const fn top_field_first(&self) -> bool {
154    self.top_field_first
155  }
156  /// FFmpeg's heuristic best-effort PTS, or `None` if unknown.
157  #[cfg_attr(not(tarpaulin), inline(always))]
158  pub const fn best_effort_timestamp(&self) -> Option<i64> {
159    self.best_effort_timestamp
160  }
161  /// HDR10 mastering-display metadata, if present on the source frame.
162  #[cfg_attr(not(tarpaulin), inline(always))]
163  pub const fn mastering_display(&self) -> Option<MasteringDisplay> {
164    self.mastering_display
165  }
166  /// HDR10 content-light-level.
167  #[cfg_attr(not(tarpaulin), inline(always))]
168  pub const fn content_light_level(&self) -> Option<ContentLightLevel> {
169    self.content_light_level
170  }
171  /// SMPTE ST 12-M timecode entries (raw 32-bit BCD-packed values).
172  #[cfg_attr(not(tarpaulin), inline(always))]
173  pub fn smpte_timecode(&self) -> &[u32] {
174    self.smpte_timecode.as_slice()
175  }
176  /// Raw side-data entries from `AVFrame.side_data`.
177  #[cfg_attr(not(tarpaulin), inline(always))]
178  pub fn side_data(&self) -> &[SideDataEntry] {
179    self.side_data.as_slice()
180  }
181
182  /// Sets the sample aspect ratio (consuming builder).
183  #[cfg_attr(not(tarpaulin), inline(always))]
184  pub const fn with_sample_aspect_ratio(mut self, value: Option<(u32, u32)>) -> Self {
185    self.sample_aspect_ratio = value;
186    self
187  }
188  /// Sets the picture type (consuming builder).
189  #[cfg_attr(not(tarpaulin), inline(always))]
190  #[must_use]
191  pub const fn with_picture_type(mut self, value: PictureType) -> Self {
192    self.picture_type = value;
193    self
194  }
195  /// Sets the key-frame flag (consuming builder).
196  #[cfg_attr(not(tarpaulin), inline(always))]
197  #[must_use]
198  pub const fn with_key_frame(mut self, value: bool) -> Self {
199    self.key_frame = value;
200    self
201  }
202  /// Sets the interlaced flag (consuming builder).
203  #[cfg_attr(not(tarpaulin), inline(always))]
204  #[must_use]
205  pub const fn with_interlaced(mut self, value: bool) -> Self {
206    self.interlaced = value;
207    self
208  }
209  /// Sets the top-field-first flag (consuming builder).
210  #[cfg_attr(not(tarpaulin), inline(always))]
211  #[must_use]
212  pub const fn with_top_field_first(mut self, value: bool) -> Self {
213    self.top_field_first = value;
214    self
215  }
216  /// Sets the best-effort timestamp (consuming builder).
217  #[cfg_attr(not(tarpaulin), inline(always))]
218  #[must_use]
219  pub const fn with_best_effort_timestamp(mut self, value: Option<i64>) -> Self {
220    self.best_effort_timestamp = value;
221    self
222  }
223  /// Sets the mastering-display metadata (consuming builder).
224  #[cfg_attr(not(tarpaulin), inline(always))]
225  #[must_use]
226  pub const fn with_mastering_display(mut self, value: Option<MasteringDisplay>) -> Self {
227    self.mastering_display = value;
228    self
229  }
230  /// Sets the content-light-level metadata (consuming builder).
231  #[cfg_attr(not(tarpaulin), inline(always))]
232  #[must_use]
233  pub const fn with_content_light_level(mut self, value: Option<ContentLightLevel>) -> Self {
234    self.content_light_level = value;
235    self
236  }
237  /// Sets the SMPTE timecode list (consuming builder).
238  #[cfg_attr(not(tarpaulin), inline(always))]
239  #[must_use]
240  pub fn with_smpte_timecode(mut self, value: Vec<u32>) -> Self {
241    self.smpte_timecode = value;
242    self
243  }
244  /// Sets the side-data list (consuming builder).
245  #[cfg_attr(not(tarpaulin), inline(always))]
246  #[must_use]
247  pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
248    self.side_data = value;
249    self
250  }
251
252  /// Sets the sample aspect ratio in place.
253  #[cfg_attr(not(tarpaulin), inline(always))]
254  pub const fn set_sample_aspect_ratio(&mut self, value: Option<(u32, u32)>) -> &mut Self {
255    self.sample_aspect_ratio = value;
256    self
257  }
258  /// Sets the picture type in place.
259  #[cfg_attr(not(tarpaulin), inline(always))]
260  pub const fn set_picture_type(&mut self, value: PictureType) -> &mut Self {
261    self.picture_type = value;
262    self
263  }
264  /// Sets the key-frame flag in place.
265  #[cfg_attr(not(tarpaulin), inline(always))]
266  pub const fn set_key_frame(&mut self, value: bool) -> &mut Self {
267    self.key_frame = value;
268    self
269  }
270  /// Sets the interlaced flag in place.
271  #[cfg_attr(not(tarpaulin), inline(always))]
272  pub const fn set_interlaced(&mut self, value: bool) -> &mut Self {
273    self.interlaced = value;
274    self
275  }
276  /// Sets the top-field-first flag in place.
277  #[cfg_attr(not(tarpaulin), inline(always))]
278  pub const fn set_top_field_first(&mut self, value: bool) -> &mut Self {
279    self.top_field_first = value;
280    self
281  }
282  /// Sets the best-effort timestamp in place.
283  #[cfg_attr(not(tarpaulin), inline(always))]
284  pub const fn set_best_effort_timestamp(&mut self, value: Option<i64>) -> &mut Self {
285    self.best_effort_timestamp = value;
286    self
287  }
288  /// Sets the mastering-display metadata in place.
289  #[cfg_attr(not(tarpaulin), inline(always))]
290  pub const fn set_mastering_display(&mut self, value: Option<MasteringDisplay>) -> &mut Self {
291    self.mastering_display = value;
292    self
293  }
294  /// Sets the content-light-level metadata in place.
295  #[cfg_attr(not(tarpaulin), inline(always))]
296  pub const fn set_content_light_level(&mut self, value: Option<ContentLightLevel>) -> &mut Self {
297    self.content_light_level = value;
298    self
299  }
300  /// Sets the SMPTE timecode list in place.
301  #[cfg_attr(not(tarpaulin), inline(always))]
302  pub fn set_smpte_timecode(&mut self, value: Vec<u32>) -> &mut Self {
303    self.smpte_timecode = value;
304    self
305  }
306  /// Sets the side-data list in place.
307  #[cfg_attr(not(tarpaulin), inline(always))]
308  pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
309    self.side_data = value;
310    self
311  }
312}
313
314/// Per-`AudioPacket` extras.
315#[derive(Clone, Debug, Default)]
316pub struct AudioPacketExtra {
317  stream_index: i32,
318  byte_pos: Option<i64>,
319  side_data: Vec<SideDataEntry>,
320}
321
322impl AudioPacketExtra {
323  /// Constructs an `AudioPacketExtra` with the given stream index.
324  #[cfg_attr(not(tarpaulin), inline(always))]
325  pub const fn new(stream_index: i32) -> Self {
326    Self {
327      stream_index,
328      byte_pos: None,
329      side_data: Vec::new(),
330    }
331  }
332
333  /// Returns the source `AVStream.index`.
334  #[cfg_attr(not(tarpaulin), inline(always))]
335  pub const fn stream_index(&self) -> i32 {
336    self.stream_index
337  }
338  /// Returns the byte position, or `None` if unknown.
339  #[cfg_attr(not(tarpaulin), inline(always))]
340  pub const fn byte_pos(&self) -> Option<i64> {
341    self.byte_pos
342  }
343  /// Returns the raw side-data entries.
344  #[cfg_attr(not(tarpaulin), inline(always))]
345  pub fn side_data(&self) -> &[SideDataEntry] {
346    self.side_data.as_slice()
347  }
348
349  /// Sets the stream index (consuming builder).
350  #[cfg_attr(not(tarpaulin), inline(always))]
351  #[must_use]
352  pub const fn with_stream_index(mut self, value: i32) -> Self {
353    self.stream_index = value;
354    self
355  }
356  /// Sets the byte position (consuming builder).
357  #[cfg_attr(not(tarpaulin), inline(always))]
358  #[must_use]
359  pub const fn with_byte_pos(mut self, value: Option<i64>) -> Self {
360    self.byte_pos = value;
361    self
362  }
363  /// Sets the side-data list (consuming builder).
364  #[cfg_attr(not(tarpaulin), inline(always))]
365  #[must_use]
366  pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
367    self.side_data = value;
368    self
369  }
370
371  /// Sets the stream index in place.
372  #[cfg_attr(not(tarpaulin), inline(always))]
373  pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
374    self.stream_index = value;
375    self
376  }
377  /// Sets the byte position in place.
378  #[cfg_attr(not(tarpaulin), inline(always))]
379  pub const fn set_byte_pos(&mut self, value: Option<i64>) -> &mut Self {
380    self.byte_pos = value;
381    self
382  }
383  /// Sets the side-data list in place.
384  #[cfg_attr(not(tarpaulin), inline(always))]
385  pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
386    self.side_data = value;
387    self
388  }
389}
390
391/// Per-`AudioFrame` extras.
392#[derive(Clone, Debug, Default)]
393pub struct AudioFrameExtra {
394  best_effort_timestamp: Option<i64>,
395  side_data: Vec<SideDataEntry>,
396}
397
398impl AudioFrameExtra {
399  /// Constructs an empty `AudioFrameExtra`.
400  #[cfg_attr(not(tarpaulin), inline(always))]
401  pub const fn new() -> Self {
402    Self {
403      best_effort_timestamp: None,
404      side_data: Vec::new(),
405    }
406  }
407
408  /// FFmpeg's heuristic best-effort PTS, or `None` if unknown.
409  #[cfg_attr(not(tarpaulin), inline(always))]
410  pub const fn best_effort_timestamp(&self) -> Option<i64> {
411    self.best_effort_timestamp
412  }
413  /// Returns the raw side-data entries.
414  #[cfg_attr(not(tarpaulin), inline(always))]
415  pub fn side_data(&self) -> &[SideDataEntry] {
416    self.side_data.as_slice()
417  }
418
419  /// Sets the best-effort timestamp (consuming builder).
420  #[cfg_attr(not(tarpaulin), inline(always))]
421  #[must_use]
422  pub const fn with_best_effort_timestamp(mut self, value: Option<i64>) -> Self {
423    self.best_effort_timestamp = value;
424    self
425  }
426  /// Sets the side-data list (consuming builder).
427  #[cfg_attr(not(tarpaulin), inline(always))]
428  #[must_use]
429  pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
430    self.side_data = value;
431    self
432  }
433
434  /// Sets the best-effort timestamp in place.
435  #[cfg_attr(not(tarpaulin), inline(always))]
436  pub const fn set_best_effort_timestamp(&mut self, value: Option<i64>) -> &mut Self {
437    self.best_effort_timestamp = value;
438    self
439  }
440  /// Sets the side-data list in place.
441  #[cfg_attr(not(tarpaulin), inline(always))]
442  pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
443    self.side_data = value;
444    self
445  }
446}
447
448/// Per-`SubtitlePacket` extras.
449#[derive(Clone, Debug, Default)]
450pub struct SubtitlePacketExtra {
451  stream_index: i32,
452  language: Option<[u8; 3]>,
453  forced: bool,
454  side_data: Vec<SideDataEntry>,
455}
456
457impl SubtitlePacketExtra {
458  /// Constructs a `SubtitlePacketExtra` with the given stream index.
459  /// `side_data` defaults to empty.
460  #[cfg_attr(not(tarpaulin), inline(always))]
461  pub const fn new(stream_index: i32) -> Self {
462    Self {
463      stream_index,
464      language: None,
465      forced: false,
466      side_data: Vec::new(),
467    }
468  }
469
470  /// Returns the source `AVStream.index`.
471  #[cfg_attr(not(tarpaulin), inline(always))]
472  pub const fn stream_index(&self) -> i32 {
473    self.stream_index
474  }
475  /// Returns the ISO 639-2/T language tag, or `None` if unspecified.
476  #[cfg_attr(not(tarpaulin), inline(always))]
477  pub const fn language(&self) -> Option<[u8; 3]> {
478    self.language
479  }
480  /// Returns whether this subtitle stream is marked "forced".
481  #[cfg_attr(not(tarpaulin), inline(always))]
482  pub const fn forced(&self) -> bool {
483    self.forced
484  }
485  /// Returns the raw side-data entries from `AVPacket.side_data`.
486  ///
487  /// A subtitle packet's side data is rare but not absent — and a
488  /// packet that carries *nothing else* is exactly the case this seat
489  /// exists for: with no seat, a side-data-only packet has nowhere to
490  /// put its only content.
491  #[cfg_attr(not(tarpaulin), inline(always))]
492  pub fn side_data(&self) -> &[SideDataEntry] {
493    self.side_data.as_slice()
494  }
495
496  /// Sets the stream index (consuming builder).
497  #[cfg_attr(not(tarpaulin), inline(always))]
498  #[must_use]
499  pub const fn with_stream_index(mut self, value: i32) -> Self {
500    self.stream_index = value;
501    self
502  }
503  /// Sets the language tag (consuming builder).
504  #[cfg_attr(not(tarpaulin), inline(always))]
505  #[must_use]
506  pub const fn with_language(mut self, value: Option<[u8; 3]>) -> Self {
507    self.language = value;
508    self
509  }
510  /// Sets the side-data list (consuming builder).
511  #[cfg_attr(not(tarpaulin), inline(always))]
512  #[must_use]
513  pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
514    self.side_data = value;
515    self
516  }
517  /// Sets the side-data list in place.
518  #[cfg_attr(not(tarpaulin), inline(always))]
519  pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
520    self.side_data = value;
521    self
522  }
523  /// Sets the forced flag (consuming builder).
524  #[cfg_attr(not(tarpaulin), inline(always))]
525  #[must_use]
526  pub const fn with_forced(mut self, value: bool) -> Self {
527    self.forced = value;
528    self
529  }
530
531  /// Sets the stream index in place.
532  #[cfg_attr(not(tarpaulin), inline(always))]
533  pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
534    self.stream_index = value;
535    self
536  }
537  /// Sets the language tag in place.
538  #[cfg_attr(not(tarpaulin), inline(always))]
539  pub const fn set_language(&mut self, value: Option<[u8; 3]>) -> &mut Self {
540    self.language = value;
541    self
542  }
543  /// Sets the forced flag in place.
544  #[cfg_attr(not(tarpaulin), inline(always))]
545  pub const fn set_forced(&mut self, value: bool) -> &mut Self {
546    self.forced = value;
547    self
548  }
549}
550
551/// Per-`SubtitleFrame` extras.
552#[derive(Clone, Debug, Default)]
553pub struct SubtitleFrameExtra {
554  start_display_time: u32,
555  end_display_time: u32,
556}
557
558impl SubtitleFrameExtra {
559  /// Constructs a `SubtitleFrameExtra`.
560  #[cfg_attr(not(tarpaulin), inline(always))]
561  pub const fn new(start_display_time: u32, end_display_time: u32) -> Self {
562    Self {
563      start_display_time,
564      end_display_time,
565    }
566  }
567
568  /// `AVSubtitle.start_display_time` — milliseconds from `pts`.
569  #[cfg_attr(not(tarpaulin), inline(always))]
570  pub const fn start_display_time(&self) -> u32 {
571    self.start_display_time
572  }
573  /// `AVSubtitle.end_display_time` — milliseconds from `pts`.
574  #[cfg_attr(not(tarpaulin), inline(always))]
575  pub const fn end_display_time(&self) -> u32 {
576    self.end_display_time
577  }
578
579  /// Sets the start display time (consuming builder).
580  #[cfg_attr(not(tarpaulin), inline(always))]
581  #[must_use]
582  pub const fn with_start_display_time(mut self, value: u32) -> Self {
583    self.start_display_time = value;
584    self
585  }
586  /// Sets the end display time (consuming builder).
587  #[cfg_attr(not(tarpaulin), inline(always))]
588  #[must_use]
589  pub const fn with_end_display_time(mut self, value: u32) -> Self {
590    self.end_display_time = value;
591    self
592  }
593
594  /// Sets the start display time in place.
595  #[cfg_attr(not(tarpaulin), inline(always))]
596  pub const fn set_start_display_time(&mut self, value: u32) -> &mut Self {
597    self.start_display_time = value;
598    self
599  }
600  /// Sets the end display time in place.
601  #[cfg_attr(not(tarpaulin), inline(always))]
602  pub const fn set_end_display_time(&mut self, value: u32) -> &mut Self {
603    self.end_display_time = value;
604    self
605  }
606}
607
608/// Picture type per `AVFrame.pict_type`.
609#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
610#[non_exhaustive]
611pub enum PictureType {
612  /// Unspecified / unset.
613  #[default]
614  Unspecified,
615  /// Intra (I-frame).
616  I,
617  /// Predicted (P-frame).
618  P,
619  /// Bi-directional predicted (B-frame).
620  B,
621  /// S(GMC)-VOP from MPEG-4.
622  S,
623  /// Switching Intra (H.264).
624  Si,
625  /// Switching Predicted (H.264).
626  Sp,
627  /// Bi-predicted intra (BI-frame).
628  Bi,
629}
630
631/// Raw side-data entry carrying the FFmpeg type id and the unparsed
632/// byte buffer. Type ids correspond to FFmpeg's
633/// `AV_FRAME_DATA_*` / `AV_PKT_DATA_*` constants — see
634/// `libavutil/frame.h` and `libavcodec/packet.h`.
635#[derive(Clone, Debug)]
636pub struct SideDataEntry {
637  kind: i32,
638  data: Vec<u8>,
639}
640
641impl SideDataEntry {
642  /// Constructs a `SideDataEntry`.
643  #[cfg_attr(not(tarpaulin), inline(always))]
644  pub const fn new(kind: i32, data: Vec<u8>) -> Self {
645    Self { kind, data }
646  }
647
648  /// FFmpeg side-data type id.
649  #[cfg_attr(not(tarpaulin), inline(always))]
650  pub const fn kind(&self) -> i32 {
651    self.kind
652  }
653  /// Side-data payload as raw bytes.
654  #[cfg_attr(not(tarpaulin), inline(always))]
655  pub fn data(&self) -> &[u8] {
656    self.data.as_slice()
657  }
658
659  /// Sets the type id (consuming builder).
660  #[cfg_attr(not(tarpaulin), inline(always))]
661  #[must_use]
662  pub const fn with_kind(mut self, value: i32) -> Self {
663    self.kind = value;
664    self
665  }
666  /// Sets the payload (consuming builder).
667  #[cfg_attr(not(tarpaulin), inline(always))]
668  #[must_use]
669  pub fn with_data(mut self, value: Vec<u8>) -> Self {
670    self.data = value;
671    self
672  }
673
674  /// Sets the type id in place.
675  #[cfg_attr(not(tarpaulin), inline(always))]
676  pub const fn set_kind(&mut self, value: i32) -> &mut Self {
677    self.kind = value;
678    self
679  }
680  /// Sets the payload in place.
681  #[cfg_attr(not(tarpaulin), inline(always))]
682  pub fn set_data(&mut self, value: Vec<u8>) -> &mut Self {
683    self.data = value;
684    self
685  }
686}
687
688/// HDR10 mastering display metadata.
689#[derive(Copy, Clone, Debug, PartialEq)]
690pub struct MasteringDisplay {
691  display_primaries: [(u32, u32); 3],
692  white_point: (u32, u32),
693  max_luminance: (u32, u32),
694  min_luminance: (u32, u32),
695}
696
697impl MasteringDisplay {
698  /// Constructs a `MasteringDisplay`.
699  #[cfg_attr(not(tarpaulin), inline(always))]
700  pub const fn new(
701    display_primaries: [(u32, u32); 3],
702    white_point: (u32, u32),
703    max_luminance: (u32, u32),
704    min_luminance: (u32, u32),
705  ) -> Self {
706    Self {
707      display_primaries,
708      white_point,
709      max_luminance,
710      min_luminance,
711    }
712  }
713
714  /// Display primary chromaticities `(x, y)` for R, G, B in CIE 1931
715  /// (each as `(num, den)` rational, with `den` non-zero).
716  #[cfg_attr(not(tarpaulin), inline(always))]
717  pub const fn display_primaries(&self) -> [(u32, u32); 3] {
718    self.display_primaries
719  }
720  /// White-point chromaticity `(x, y)` as rationals.
721  #[cfg_attr(not(tarpaulin), inline(always))]
722  pub const fn white_point(&self) -> (u32, u32) {
723    self.white_point
724  }
725  /// Maximum luminance in `0.0001 cd/m²` units (rational `(num, den)`).
726  #[cfg_attr(not(tarpaulin), inline(always))]
727  pub const fn max_luminance(&self) -> (u32, u32) {
728    self.max_luminance
729  }
730  /// Minimum luminance in `0.0001 cd/m²` units.
731  #[cfg_attr(not(tarpaulin), inline(always))]
732  pub const fn min_luminance(&self) -> (u32, u32) {
733    self.min_luminance
734  }
735
736  /// Sets the display primaries (consuming builder).
737  #[cfg_attr(not(tarpaulin), inline(always))]
738  pub const fn with_display_primaries(mut self, value: [(u32, u32); 3]) -> Self {
739    self.display_primaries = value;
740    self
741  }
742  /// Sets the white point (consuming builder).
743  #[cfg_attr(not(tarpaulin), inline(always))]
744  pub const fn with_white_point(mut self, value: (u32, u32)) -> Self {
745    self.white_point = value;
746    self
747  }
748  /// Sets the max luminance (consuming builder).
749  #[cfg_attr(not(tarpaulin), inline(always))]
750  pub const fn with_max_luminance(mut self, value: (u32, u32)) -> Self {
751    self.max_luminance = value;
752    self
753  }
754  /// Sets the min luminance (consuming builder).
755  #[cfg_attr(not(tarpaulin), inline(always))]
756  pub const fn with_min_luminance(mut self, value: (u32, u32)) -> Self {
757    self.min_luminance = value;
758    self
759  }
760
761  /// Sets the display primaries in place.
762  #[cfg_attr(not(tarpaulin), inline(always))]
763  pub const fn set_display_primaries(&mut self, value: [(u32, u32); 3]) -> &mut Self {
764    self.display_primaries = value;
765    self
766  }
767  /// Sets the white point in place.
768  #[cfg_attr(not(tarpaulin), inline(always))]
769  pub const fn set_white_point(&mut self, value: (u32, u32)) -> &mut Self {
770    self.white_point = value;
771    self
772  }
773  /// Sets the max luminance in place.
774  #[cfg_attr(not(tarpaulin), inline(always))]
775  pub const fn set_max_luminance(&mut self, value: (u32, u32)) -> &mut Self {
776    self.max_luminance = value;
777    self
778  }
779  /// Sets the min luminance in place.
780  #[cfg_attr(not(tarpaulin), inline(always))]
781  pub const fn set_min_luminance(&mut self, value: (u32, u32)) -> &mut Self {
782    self.min_luminance = value;
783    self
784  }
785}
786
787/// HDR10 content light level (`AV_FRAME_DATA_CONTENT_LIGHT_LEVEL`).
788#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
789pub struct ContentLightLevel {
790  max_cll: u32,
791  max_fall: u32,
792}
793
794impl ContentLightLevel {
795  /// Constructs a `ContentLightLevel`.
796  #[cfg_attr(not(tarpaulin), inline(always))]
797  pub const fn new(max_cll: u32, max_fall: u32) -> Self {
798    Self { max_cll, max_fall }
799  }
800
801  /// Maximum content light level (cd/m²).
802  #[cfg_attr(not(tarpaulin), inline(always))]
803  pub const fn max_cll(&self) -> u32 {
804    self.max_cll
805  }
806  /// Maximum frame-average light level (cd/m²).
807  #[cfg_attr(not(tarpaulin), inline(always))]
808  pub const fn max_fall(&self) -> u32 {
809    self.max_fall
810  }
811
812  /// Sets `max_cll` (consuming builder).
813  #[cfg_attr(not(tarpaulin), inline(always))]
814  #[must_use]
815  pub const fn with_max_cll(mut self, value: u32) -> Self {
816    self.max_cll = value;
817    self
818  }
819  /// Sets `max_fall` (consuming builder).
820  #[cfg_attr(not(tarpaulin), inline(always))]
821  #[must_use]
822  pub const fn with_max_fall(mut self, value: u32) -> Self {
823    self.max_fall = value;
824    self
825  }
826
827  /// Sets `max_cll` in place.
828  #[cfg_attr(not(tarpaulin), inline(always))]
829  pub const fn set_max_cll(&mut self, value: u32) -> &mut Self {
830    self.max_cll = value;
831    self
832  }
833  /// Sets `max_fall` in place.
834  #[cfg_attr(not(tarpaulin), inline(always))]
835  pub const fn set_max_fall(&mut self, value: u32) -> &mut Self {
836    self.max_fall = value;
837    self
838  }
839}
840
841// ---------------------------------------------------------------------------
842//  The demux tier's carriers.
843// ---------------------------------------------------------------------------
844
845/// Per-`DataPacket` extras — timecode, KLV, timed ID3.
846///
847/// The same three seats as [`VideoPacketExtra`]. The side-data list was
848/// left off at first — data demuxers carry their whole payload in the
849/// packet body — and then earned its place: a packet with no body and
850/// only side data is a real packet, and without this seat its only
851/// content would have nowhere to go.
852#[derive(Clone, Debug, Default)]
853pub struct DataPacketExtra {
854  stream_index: i32,
855  byte_pos: Option<i64>,
856  side_data: Vec<SideDataEntry>,
857}
858
859impl DataPacketExtra {
860  /// Constructs a `DataPacketExtra` with the given stream index.
861  /// `byte_pos` defaults to `None` and `side_data` to empty.
862  #[cfg_attr(not(tarpaulin), inline(always))]
863  pub const fn new(stream_index: i32) -> Self {
864    Self {
865      stream_index,
866      byte_pos: None,
867      side_data: Vec::new(),
868    }
869  }
870
871  /// Returns the source `AVStream.index`.
872  #[cfg_attr(not(tarpaulin), inline(always))]
873  pub const fn stream_index(&self) -> i32 {
874    self.stream_index
875  }
876  /// Returns the byte position of the packet in the input file, or
877  /// `None` if unknown.
878  #[cfg_attr(not(tarpaulin), inline(always))]
879  pub const fn byte_pos(&self) -> Option<i64> {
880    self.byte_pos
881  }
882  /// Returns the raw side-data entries from `AVPacket.side_data`.
883  #[cfg_attr(not(tarpaulin), inline(always))]
884  pub fn side_data(&self) -> &[SideDataEntry] {
885    self.side_data.as_slice()
886  }
887
888  /// Sets the stream index (consuming builder).
889  #[cfg_attr(not(tarpaulin), inline(always))]
890  #[must_use]
891  pub const fn with_stream_index(mut self, value: i32) -> Self {
892    self.stream_index = value;
893    self
894  }
895  /// Sets the byte position (consuming builder).
896  #[cfg_attr(not(tarpaulin), inline(always))]
897  #[must_use]
898  pub const fn with_byte_pos(mut self, value: Option<i64>) -> Self {
899    self.byte_pos = value;
900    self
901  }
902  /// Sets the side-data list (consuming builder).
903  #[cfg_attr(not(tarpaulin), inline(always))]
904  #[must_use]
905  pub fn with_side_data(mut self, value: Vec<SideDataEntry>) -> Self {
906    self.side_data = value;
907    self
908  }
909
910  /// Sets the stream index in place.
911  #[cfg_attr(not(tarpaulin), inline(always))]
912  pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
913    self.stream_index = value;
914    self
915  }
916  /// Sets the byte position in place.
917  #[cfg_attr(not(tarpaulin), inline(always))]
918  pub const fn set_byte_pos(&mut self, value: Option<i64>) -> &mut Self {
919    self.byte_pos = value;
920    self
921  }
922  /// Sets the side-data list in place.
923  #[cfg_attr(not(tarpaulin), inline(always))]
924  pub fn set_side_data(&mut self, value: Vec<SideDataEntry>) -> &mut Self {
925    self.side_data = value;
926    self
927  }
928}
929
930/// Per-`AttachmentPacket` extras — fonts, cover art.
931///
932/// `synthesized` records where the payload came from, which is not a
933/// detail: an attachment track's single packet is either a real packet
934/// the container stores (cover art, which libavformat parks in
935/// `AVStream.attached_pic`) or one this crate builds out of the
936/// track's codec extradata (fonts, whose bytes never appear in the
937/// packet stream at all). A consumer chasing a payload that looks
938/// wrong needs to know which.
939#[derive(Clone, Debug, Default)]
940pub struct AttachmentPacketExtra {
941  stream_index: i32,
942  synthesized: bool,
943}
944
945impl AttachmentPacketExtra {
946  /// Constructs an `AttachmentPacketExtra` with the given stream index.
947  /// `synthesized` defaults to `false`.
948  #[cfg_attr(not(tarpaulin), inline(always))]
949  pub const fn new(stream_index: i32) -> Self {
950    Self {
951      stream_index,
952      synthesized: false,
953    }
954  }
955
956  /// Returns the source `AVStream.index`.
957  #[cfg_attr(not(tarpaulin), inline(always))]
958  pub const fn stream_index(&self) -> i32 {
959    self.stream_index
960  }
961  /// `true` when the payload was built from the track's codec
962  /// extradata rather than taken from a packet the container stores.
963  #[cfg_attr(not(tarpaulin), inline(always))]
964  pub const fn synthesized(&self) -> bool {
965    self.synthesized
966  }
967
968  /// Sets the stream index (consuming builder).
969  #[cfg_attr(not(tarpaulin), inline(always))]
970  #[must_use]
971  pub const fn with_stream_index(mut self, value: i32) -> Self {
972    self.stream_index = value;
973    self
974  }
975  /// Sets the synthesized flag (consuming builder).
976  #[cfg_attr(not(tarpaulin), inline(always))]
977  #[must_use]
978  pub const fn with_synthesized(mut self, value: bool) -> Self {
979    self.synthesized = value;
980    self
981  }
982
983  /// Sets the stream index in place.
984  #[cfg_attr(not(tarpaulin), inline(always))]
985  pub const fn set_stream_index(&mut self, value: i32) -> &mut Self {
986    self.stream_index = value;
987    self
988  }
989  /// Sets the synthesized flag in place.
990  #[cfg_attr(not(tarpaulin), inline(always))]
991  pub const fn set_synthesized(&mut self, value: bool) -> &mut Self {
992    self.synthesized = value;
993    self
994  }
995}
996
997/// A deep copy of codec parameters, with both fallible steps checked.
998///
999/// `ffmpeg_next`'s `Clone` for `Parameters` checks neither.
1000/// `Parameters::new` does not test `avcodec_parameters_alloc` for null
1001/// and the copy dereferences the result immediately — measured under a
1002/// capped allocator, that is a SIGSEGV — while
1003/// `avcodec_parameters_copy`'s return value is discarded, so a copy
1004/// that failed part way yields parameters that look complete and open a
1005/// decoder wrong.
1006///
1007/// A partial copy leaves with `out`'s own destructor:
1008/// `avcodec_parameters_copy` resets the destination before it starts,
1009/// so whatever it managed to allocate belongs to `out`.
1010pub(crate) fn clone_parameters(
1011  source: &Parameters,
1012  stream_index: usize,
1013) -> Result<Parameters, DemuxError> {
1014  // The *source* first. `Parameters::new()` and `Parameters::default()`
1015  // hand back a value whose pointer is null when
1016  // `avcodec_parameters_alloc` failed — safe code, no error, no way to
1017  // tell — and `avcodec_parameters_copy` dereferences its source. So a
1018  // copier that checks only what it allocates still crashes, one
1019  // allocator recovery later, on a `Parameters` that never allocated.
1020  // SAFETY: reading the pointer without dereferencing it.
1021  if unsafe { source.as_ptr() }.is_null() {
1022    return Err(DemuxError::ParametersMissing { stream_index });
1023  }
1024  let mut out = Parameters::new();
1025  // SAFETY: reading the pointer the constructor stored without
1026  // dereferencing it — which is exactly what the check is for.
1027  if unsafe { out.as_ptr() }.is_null() {
1028    return Err(DemuxError::ParametersAlloc { stream_index });
1029  }
1030  // SAFETY: both pointers are live `AVCodecParameters` — the
1031  // destination freshly allocated and non-null, the source owned by its
1032  // holder for the duration of this call.
1033  let rc = unsafe { avcodec_parameters_copy(out.as_mut_ptr(), source.as_ptr()) };
1034  if rc < 0 {
1035    return Err(DemuxError::ParametersCopy {
1036      stream_index,
1037      source: ffmpeg_next::Error::from(rc),
1038    });
1039  }
1040  Ok(out)
1041}
1042
1043/// Per-`TrackInfo` extras — the FFmpeg side of one track-table row.
1044///
1045/// Carries the stream's [`Parameters`], which is what opens a decoder
1046/// for the track — through [`Self::clone_parameters`], which is a deep
1047/// `avcodec_parameters_copy` with no tie back to the format context, so
1048/// a decoder outlives the demuxer that named it.
1049///
1050/// **No `Clone`, and no `Default`.** Both would have to go through
1051/// `ffmpeg_next`'s `Clone` / `Default` for [`Parameters`], which check
1052/// neither the allocation nor the copy: safe public code could
1053/// dereference a null destination or receive parameters that are
1054/// quietly incomplete. `Clone` cannot report either, so this type does
1055/// not implement it; [`Self::try_clone`] is the same copy with the
1056/// answer a caller can act on, and [`Self::clone_parameters`] is the
1057/// handoff a decoder actually needs.
1058///
1059/// `disposition` is the raw `AV_DISPOSITION_*` bit set, not
1060/// `ffmpeg_next::format::stream::Disposition`. That type's
1061/// `from_bits_truncate` drops bits the linked build has no constant
1062/// for, and this crate's stance on bit sets is that every pattern is a
1063/// value — the same reason `PacketFlags` reaches the wire as a number.
1064pub struct TrackExtra {
1065  stream_index: i32,
1066  disposition: i32,
1067  start_time: Option<i64>,
1068  frame_count: Option<i64>,
1069  parameters: Parameters,
1070}
1071
1072impl TrackExtra {
1073  /// Constructs a `TrackExtra` from the stream index and its codec
1074  /// parameters. Everything else starts absent.
1075  ///
1076  /// **Fallible, and that is the point.** `Parameters::new()` and
1077  /// `Parameters::default()` are safe constructors that hand back a
1078  /// null-backed value when `avcodec_parameters_alloc` fails, saying
1079  /// nothing; accepting one here would store a landmine that goes off
1080  /// later, in a copy, on a thread that has forgotten the allocator
1081  /// ever failed. Refusing it at the door is what lets every other
1082  /// method on this type — and every reader of
1083  /// [`Self::parameters`] — rely on there being parameters at all.
1084  ///
1085  /// Not `const fn`: [`Parameters`] owns a heap allocation.
1086  pub fn new(stream_index: i32, parameters: Parameters) -> Result<Self, DemuxError> {
1087    // SAFETY: reading the pointer without dereferencing it.
1088    if unsafe { parameters.as_ptr() }.is_null() {
1089      return Err(DemuxError::ParametersMissing {
1090        stream_index: stream_index.max(0) as usize,
1091      });
1092    }
1093    Ok(Self {
1094      stream_index,
1095      disposition: 0,
1096      start_time: None,
1097      frame_count: None,
1098      parameters,
1099    })
1100  }
1101
1102  /// A deep copy of this row, with the codec-parameter copy checked.
1103  ///
1104  /// The fallible counterpart of the `Clone` this type deliberately
1105  /// does not implement — see the type's own documentation for why.
1106  pub fn try_clone(&self) -> Result<Self, DemuxError> {
1107    // No re-check: `self` cannot exist over null-backed parameters, and
1108    // `clone_parameters` never returns one.
1109    Ok(Self {
1110      stream_index: self.stream_index,
1111      disposition: self.disposition,
1112      start_time: self.start_time,
1113      frame_count: self.frame_count,
1114      parameters: self.clone_parameters()?,
1115    })
1116  }
1117
1118  /// An owned deep copy of the track's codec parameters — the handoff
1119  /// that opens a decoder for this track.
1120  ///
1121  /// `FfmpegAudioStreamDecoder::open(track.extra().clone_parameters()?,
1122  /// track.timebase())`. Fallible because the copy is: an allocation
1123  /// failure here is the difference between a decoder that is not
1124  /// opened and one opened on parameters that are not the file's.
1125  pub fn clone_parameters(&self) -> Result<Parameters, DemuxError> {
1126    clone_parameters(&self.parameters, self.stream_index.max(0) as usize)
1127  }
1128
1129  /// Returns the source `AVStream.index`.
1130  #[cfg_attr(not(tarpaulin), inline(always))]
1131  pub const fn stream_index(&self) -> i32 {
1132    self.stream_index
1133  }
1134  /// Returns the raw `AVStream.disposition` bit set.
1135  #[cfg_attr(not(tarpaulin), inline(always))]
1136  pub const fn disposition(&self) -> i32 {
1137    self.disposition
1138  }
1139  /// Returns the stream's start time in the track's timebase, or
1140  /// `None` when the container does not carry one.
1141  #[cfg_attr(not(tarpaulin), inline(always))]
1142  pub const fn start_time(&self) -> Option<i64> {
1143    self.start_time
1144  }
1145  /// Returns `AVStream.nb_frames` when the container carries it.
1146  #[cfg_attr(not(tarpaulin), inline(always))]
1147  pub const fn frame_count(&self) -> Option<i64> {
1148    self.frame_count
1149  }
1150  /// Returns the stream's codec parameters — the handle a decoder is
1151  /// opened from.
1152  #[cfg_attr(not(tarpaulin), inline(always))]
1153  pub const fn parameters(&self) -> &Parameters {
1154    &self.parameters
1155  }
1156
1157  /// Sets the disposition bits (consuming builder).
1158  #[cfg_attr(not(tarpaulin), inline(always))]
1159  #[must_use]
1160  pub const fn with_disposition(mut self, value: i32) -> Self {
1161    self.disposition = value;
1162    self
1163  }
1164  /// Sets the start time (consuming builder).
1165  #[cfg_attr(not(tarpaulin), inline(always))]
1166  #[must_use]
1167  pub const fn with_start_time(mut self, value: Option<i64>) -> Self {
1168    self.start_time = value;
1169    self
1170  }
1171  /// Sets the frame count (consuming builder).
1172  #[cfg_attr(not(tarpaulin), inline(always))]
1173  #[must_use]
1174  pub const fn with_frame_count(mut self, value: Option<i64>) -> Self {
1175    self.frame_count = value;
1176    self
1177  }
1178
1179  /// Sets the disposition bits in place.
1180  #[cfg_attr(not(tarpaulin), inline(always))]
1181  pub const fn set_disposition(&mut self, value: i32) -> &mut Self {
1182    self.disposition = value;
1183    self
1184  }
1185  /// Sets the start time in place.
1186  #[cfg_attr(not(tarpaulin), inline(always))]
1187  pub const fn set_start_time(&mut self, value: Option<i64>) -> &mut Self {
1188    self.start_time = value;
1189    self
1190  }
1191  /// Sets the frame count in place.
1192  #[cfg_attr(not(tarpaulin), inline(always))]
1193  pub const fn set_frame_count(&mut self, value: Option<i64>) -> &mut Self {
1194    self.frame_count = value;
1195    self
1196  }
1197}
1198
1199impl std::fmt::Debug for TrackExtra {
1200  /// Hand-written because [`Parameters`] does not derive `Debug`. The
1201  /// medium and codec id are the two fields worth printing; the rest of
1202  /// `AVCodecParameters` is per-kind detail the track row already
1203  /// carries in typed form.
1204  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1205    f.debug_struct("TrackExtra")
1206      .field("stream_index", &self.stream_index)
1207      .field("disposition", &format_args!("{:#x}", self.disposition))
1208      .field("start_time", &self.start_time)
1209      .field("frame_count", &self.frame_count)
1210      .field(
1211        "parameters",
1212        &format_args!("{:?}", self.parameters.medium()),
1213      )
1214      .finish()
1215  }
1216}
1217
1218#[cfg(test)]
1219mod tests {
1220  use super::*;
1221
1222  #[test]
1223  fn defaults_construct() {
1224    let v = VideoPacketExtra::default();
1225    assert_eq!(v.stream_index(), 0);
1226    assert!(v.side_data().is_empty());
1227
1228    let f = VideoFrameExtra::default();
1229    assert_eq!(f.picture_type(), PictureType::Unspecified);
1230    assert!(!f.key_frame());
1231    assert!(f.mastering_display().is_none());
1232
1233    let s = SubtitleFrameExtra::default();
1234    assert_eq!(s.start_display_time(), 0);
1235    assert_eq!(s.end_display_time(), 0);
1236  }
1237
1238  #[test]
1239  fn picture_type_default_is_unspecified() {
1240    assert_eq!(PictureType::default(), PictureType::Unspecified);
1241  }
1242
1243  #[test]
1244  fn side_data_entry_carries_bytes() {
1245    let entry = SideDataEntry::new(12345, vec![1, 2, 3, 4]);
1246    assert_eq!(entry.kind(), 12345);
1247    assert_eq!(entry.data(), &[1, 2, 3, 4]);
1248  }
1249
1250  #[test]
1251  fn content_light_level_default_is_zero() {
1252    let cll = ContentLightLevel::default();
1253    assert_eq!(cll.max_cll(), 0);
1254    assert_eq!(cll.max_fall(), 0);
1255  }
1256
1257  #[test]
1258  fn builders_chain() {
1259    let v = VideoPacketExtra::new(7)
1260      .with_byte_pos(Some(1234))
1261      .with_side_data(vec![SideDataEntry::new(1, vec![0xAB])]);
1262    assert_eq!(v.stream_index(), 7);
1263    assert_eq!(v.byte_pos(), Some(1234));
1264    assert_eq!(v.side_data().len(), 1);
1265  }
1266
1267  #[test]
1268  fn setters_chain() {
1269    let mut v = VideoPacketExtra::default();
1270    v.set_stream_index(3).set_byte_pos(Some(99));
1271    assert_eq!(v.stream_index(), 3);
1272    assert_eq!(v.byte_pos(), Some(99));
1273  }
1274}