Skip to main content

zenavif_serialize/
boxes.rs

1use crate::constants::{ColorPrimaries, MatrixCoefficients, TransferCharacteristics};
2use crate::writer::{Writer, WriterBackend, IO};
3use arrayvec::ArrayVec;
4use std::io::Write;
5use std::num::NonZeroU32;
6use std::{fmt, io};
7
8pub trait MpegBox {
9    fn len(&self) -> usize;
10    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error>;
11}
12
13#[derive(Copy, Clone)]
14pub struct FourCC(pub [u8; 4]);
15
16impl fmt::Debug for FourCC {
17    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
18        match std::str::from_utf8(&self.0) {
19            Ok(s) => s.fmt(f),
20            Err(_) => self.0.fmt(f),
21        }
22    }
23}
24
25#[derive(Debug, Clone)]
26pub struct AvifFile<'data> {
27    pub ftyp: FtypBox,
28    pub meta: MetaBox<'data>,
29    pub mdat: MdatBox,
30}
31
32impl AvifFile<'_> {
33    /// Where the primary data starts inside the `mdat` box, for `iloc`'s offset
34    fn mdat_payload_start_offset(&self) -> u32 {
35        (self.ftyp.len() + self.meta.len()
36            + BASIC_BOX_SIZE) as u32 // mdat head
37    }
38
39    /// `iloc` is mostly unnecssary, high risk of out-of-buffer accesses in parsers that don't pay attention,
40    /// and also awkward to serialize, because its content depends on its own serialized byte size.
41    fn fix_iloc_positions(&mut self) {
42        let start_offset = self.mdat_payload_start_offset();
43        self.meta.iloc.absolute_offset_start = NonZeroU32::new(start_offset);
44    }
45
46    fn write_header(&mut self, out: &mut Vec<u8>) -> io::Result<()> {
47        if self.meta.iprp.ipco.ispe().is_none_or(|b| b.width == 0 || b.height == 0) {
48            return Err(io::Error::new(io::ErrorKind::InvalidInput, "missing width/height"));
49        }
50
51        self.fix_iloc_positions();
52
53        out.try_reserve_exact(self.ftyp.len() + self.meta.len())?;
54        let mut w = Writer::new(out);
55        self.ftyp.write(&mut w).map_err(|_| io::ErrorKind::OutOfMemory)?;
56        self.meta.write(&mut w).map_err(|_| io::ErrorKind::OutOfMemory)?;
57        Ok(())
58    }
59
60    pub fn file_size(&self) -> usize {
61        self.ftyp.len() + self.meta.len() + self.mdat.len(&self.meta.iloc)
62    }
63
64    pub fn write_to_vec(&mut self, out: &mut Vec<u8>) -> io::Result<()> {
65        let expected_file_size = self.file_size();
66        out.try_reserve_exact(expected_file_size)?;
67        let initial = out.len();
68        self.write_header(out)?;
69
70        let _ = self.mdat.write(&mut Writer::new(out), &self.meta.iloc);
71        let written = out.len() - initial;
72        debug_assert_eq!(expected_file_size, written);
73        Ok(())
74    }
75
76    pub fn write<W: Write>(&mut self, mut out: W) -> io::Result<()> {
77        let mut tmp = Vec::new();
78
79        self.write_header(&mut tmp)?;
80        out.write_all(&tmp)?;
81        drop(tmp);
82
83        self.mdat.write(&mut Writer::new(&mut IO(out)), &self.meta.iloc)
84    }
85}
86
87const BASIC_BOX_SIZE: usize = 8;
88const FULL_BOX_SIZE: usize = BASIC_BOX_SIZE + 4;
89
90#[derive(Debug, Clone)]
91pub struct FtypBox {
92    pub major_brand: FourCC,
93    pub minor_version: u32,
94    pub compatible_brands: ArrayVec<FourCC, 2>,
95}
96
97/// File Type box (chunk)
98impl MpegBox for FtypBox {
99    #[inline(always)]
100    fn len(&self) -> usize {
101        BASIC_BOX_SIZE
102        + 4 // brand
103        + 4 // ver
104        + 4 * self.compatible_brands.len()
105    }
106
107    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
108        let mut b = w.basic_box(self.len(), *b"ftyp")?;
109        b.push(&self.major_brand.0)?;
110        b.u32(self.minor_version)?;
111        for cb in &self.compatible_brands {
112            b.push(&cb.0)?;
113        }
114        Ok(())
115    }
116}
117
118/// Metadata box
119#[derive(Debug, Clone)]
120pub struct MetaBox<'data> {
121    pub hdlr: HdlrBox,
122    pub iloc: IlocBox<'data>,
123    pub iinf: IinfBox,
124    pub pitm: PitmBox,
125    pub iprp: IprpBox,
126    pub iref: IrefBox,
127}
128
129impl MpegBox for MetaBox<'_> {
130    #[inline]
131    fn len(&self) -> usize {
132        FULL_BOX_SIZE
133            + self.hdlr.len()
134            + self.pitm.len()
135            + self.iloc.len()
136            + self.iinf.len()
137            + self.iprp.len()
138            + if !self.iref.is_empty() { self.iref.len() } else { 0 }
139    }
140
141    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
142        let mut b = w.full_box(self.len(), *b"meta", 0)?;
143        self.hdlr.write(&mut b)?;
144        self.pitm.write(&mut b)?;
145        self.iloc.write(&mut b)?;
146        self.iinf.write(&mut b)?;
147        if !self.iref.is_empty() {
148            self.iref.write(&mut b)?;
149        }
150        self.iprp.write(&mut b)
151    }
152}
153
154/// Item Info box
155#[derive(Debug, Clone)]
156pub struct IinfBox {
157    pub items: ArrayVec<InfeBox, 8>,
158}
159
160impl MpegBox for IinfBox {
161    #[inline]
162    fn len(&self) -> usize {
163        FULL_BOX_SIZE
164        + 2 // num items u16
165        + self.items.iter().map(|item| item.len()).sum::<usize>()
166    }
167
168    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
169        let mut b = w.full_box(self.len(), *b"iinf", 0)?;
170        b.u16(self.items.len() as _)?;
171        for infe in &self.items {
172            infe.write(&mut b)?;
173        }
174        Ok(())
175    }
176}
177
178/// Item Info Entry box
179#[derive(Debug, Copy, Clone)]
180pub struct InfeBox {
181    pub id: u16,
182    pub typ: FourCC,
183    pub name: &'static str,
184    /// Content type (only for `mime` items, e.g. "application/rdf+xml" for XMP)
185    pub content_type: &'static str,
186}
187
188impl MpegBox for InfeBox {
189    #[inline(always)]
190    fn len(&self) -> usize {
191        FULL_BOX_SIZE
192        + 2 // id
193        + 2 // item_protection_index
194        + 4 // type
195        + self.name.len() + 1 // nul-terminated
196        + if self.content_type.is_empty() { 0 } else { self.content_type.len() + 1 }
197    }
198
199    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
200        let mut b = w.full_box(self.len(), *b"infe", 2)?;
201        b.u16(self.id)?;
202        b.u16(0)?;
203        b.push(&self.typ.0)?;
204        b.push(self.name.as_bytes())?;
205        b.u8(0)?;
206        if !self.content_type.is_empty() {
207            b.push(self.content_type.as_bytes())?;
208            b.u8(0)?;
209        }
210        Ok(())
211    }
212}
213
214#[derive(Debug, Clone)]
215pub struct HdlrBox {
216}
217
218impl MpegBox for HdlrBox {
219    #[inline(always)]
220    fn len(&self) -> usize {
221        FULL_BOX_SIZE + 4 + 4 + 13
222    }
223
224    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
225        // because an image format needs to be told it's an image format,
226        // and it does it the way classic MacOS used to, because Quicktime.
227        let mut b = w.full_box(self.len(), *b"hdlr", 0)?;
228        b.u32(0)?; // old MacOS file type handler
229        b.push(b"pict")?; // MacOS Quicktime subtype
230        b.u32(0)?; // Firefox 92 wants all 0 here
231        b.u32(0)?; // Reserved
232        b.u32(0)?; // Reserved
233        b.u8(0)?; // Pascal string for component name
234        Ok(())
235    }
236}
237
238/// Item properties + associations
239#[derive(Debug, Clone)]
240pub struct IprpBox {
241    pub ipco: IpcoBox,
242    pub ipma: IpmaBox,
243}
244
245impl MpegBox for IprpBox {
246    #[inline(always)]
247    fn len(&self) -> usize {
248        BASIC_BOX_SIZE
249            + self.ipco.len()
250            + self.ipma.len()
251    }
252
253    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
254        let mut b = w.basic_box(self.len(), *b"iprp")?;
255        self.ipco.write(&mut b)?;
256        self.ipma.write(&mut b)
257    }
258}
259
260#[derive(Debug, Clone)]
261#[non_exhaustive]
262pub enum IpcoProp {
263    Av1C(Av1CBox),
264    Pixi(PixiBox),
265    Ispe(IspeBox),
266    AuxC(AuxCBox),
267    Colr(ColrBox),
268    ColrIcc(ColrIccBox),
269    Clli(ClliBox),
270    Mdcv(MdcvBox),
271    Irot(IrotBox),
272    Imir(ImirBox),
273    Clap(ClapBox),
274    Pasp(PaspBox),
275}
276
277impl IpcoProp {
278    pub fn len(&self) -> usize {
279        match self {
280            Self::Av1C(p) => p.len(),
281            Self::Pixi(p) => p.len(),
282            Self::Ispe(p) => p.len(),
283            Self::AuxC(p) => p.len(),
284            Self::Colr(p) => p.len(),
285            Self::ColrIcc(p) => p.len(),
286            Self::Clli(p) => p.len(),
287            Self::Mdcv(p) => p.len(),
288            Self::Irot(p) => p.len(),
289            Self::Imir(p) => p.len(),
290            Self::Clap(p) => p.len(),
291            Self::Pasp(p) => p.len(),
292        }
293    }
294
295    pub fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
296        match self {
297            Self::Av1C(p) => p.write(w),
298            Self::Pixi(p) => p.write(w),
299            Self::Ispe(p) => p.write(w),
300            Self::AuxC(p) => p.write(w),
301            Self::Colr(p) => p.write(w),
302            Self::ColrIcc(p) => p.write(w),
303            Self::Clli(p) => p.write(w),
304            Self::Mdcv(p) => p.write(w),
305            Self::Irot(p) => p.write(w),
306            Self::Imir(p) => p.write(w),
307            Self::Clap(p) => p.write(w),
308            Self::Pasp(p) => p.write(w),
309        }
310    }
311}
312
313/// Item Property Container box
314#[derive(Debug, Clone)]
315pub struct IpcoBox {
316    props: ArrayVec<IpcoProp, 16>,
317}
318
319impl IpcoBox {
320    pub fn new() -> Self {
321        Self { props: ArrayVec::new() }
322    }
323
324    #[must_use]
325    pub fn push(&mut self, prop: IpcoProp) -> Option<u8> {
326        self.props.try_push(prop).ok()?;
327        Some(self.props.len() as u8) // the spec wants them off by one
328    }
329
330    pub(crate) fn ispe(&self) -> Option<&IspeBox> {
331        self.props.iter().find_map(|b| match b {
332            IpcoProp::Ispe(i) => Some(i),
333            _ => None,
334        })
335    }
336}
337
338impl MpegBox for IpcoBox {
339    #[inline]
340    fn len(&self) -> usize {
341        BASIC_BOX_SIZE
342            + self.props.iter().map(|a| a.len()).sum::<usize>()
343    }
344
345    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
346        let mut b = w.basic_box(self.len(), *b"ipco")?;
347        for p in &self.props {
348            p.write(&mut b)?;
349        }
350        Ok(())
351    }
352}
353
354#[derive(Debug, Copy, Clone)]
355pub struct AuxCBox {
356    pub urn: &'static str,
357}
358
359impl AuxCBox {
360    pub fn len(&self) -> usize {
361        FULL_BOX_SIZE + self.urn.len() + 1
362    }
363
364    pub fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
365        let mut b = w.full_box(self.len(), *b"auxC", 0)?;
366        b.push(self.urn.as_bytes())?;
367        b.u8(0)
368    }
369}
370
371/// Pixies, I guess.
372#[derive(Debug, Copy, Clone)]
373pub struct PixiBox {
374    pub depth: u8,
375    pub channels: u8,
376}
377
378impl PixiBox {
379    pub fn len(self) -> usize {
380        FULL_BOX_SIZE
381            + 1 + self.channels as usize
382    }
383
384    pub fn write<B: WriterBackend>(self, w: &mut Writer<B>) -> Result<(), B::Error> {
385        let mut b = w.full_box(self.len(), *b"pixi", 0)?;
386        b.u8(self.channels)?;
387        for _ in 0..self.channels {
388            b.u8(self.depth)?;
389        }
390        Ok(())
391    }
392}
393
394/// This is HEVC-specific and not for AVIF, but Chrome wants it :(
395#[derive(Debug, Copy, Clone)]
396pub struct IspeBox {
397    pub width: u32,
398    pub height: u32,
399}
400
401impl MpegBox for IspeBox {
402    #[inline(always)]
403    fn len(&self) -> usize {
404        FULL_BOX_SIZE + 4 + 4
405    }
406
407    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
408        let mut b = w.full_box(self.len(), *b"ispe", 0)?;
409        b.u32(self.width)?;
410        b.u32(self.height)
411    }
412}
413
414/// Property→image associations
415#[derive(Debug, Clone)]
416pub struct IpmaEntry {
417    pub item_id: u16,
418    pub prop_ids: ArrayVec<u8, 12>,
419}
420
421#[derive(Debug, Clone)]
422pub struct IpmaBox {
423    pub entries: ArrayVec<IpmaEntry, 4>,
424}
425
426impl MpegBox for IpmaBox {
427    #[inline]
428    fn len(&self) -> usize {
429        FULL_BOX_SIZE + 4 + self.entries.iter().map(|e| 2 + 1 + e.prop_ids.len()).sum::<usize>()
430    }
431
432    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
433        let mut b = w.full_box(self.len(), *b"ipma", 0)?;
434        b.u32(self.entries.len() as _)?; // entry count
435
436        for e in &self.entries {
437            b.u16(e.item_id)?;
438            b.u8(e.prop_ids.len() as u8)?; // assoc count
439            for &p in &e.prop_ids {
440                b.u8(p)?;
441            }
442        }
443        Ok(())
444    }
445}
446
447/// Single-reference item reference entry (e.g., auxl, prem, cdsc).
448#[derive(Debug, Copy, Clone)]
449pub struct IrefEntryBox {
450    pub from_id: u16,
451    pub to_id: u16,
452    pub typ: FourCC,
453}
454
455impl MpegBox for IrefEntryBox {
456    #[inline(always)]
457    fn len(&self) -> usize {
458        BASIC_BOX_SIZE
459            + 2 // from
460            + 2 // refcount
461            + 2 // to
462    }
463
464    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
465        let mut b = w.basic_box(self.len(), self.typ.0)?;
466        b.u16(self.from_id)?;
467        b.u16(1)?;
468        b.u16(self.to_id)
469    }
470}
471
472/// Multi-reference item reference entry (e.g., dimg from tmap to primary+gain_map).
473///
474/// Writes a single SingleItemTypeReferenceBox with `reference_count` equal to
475/// the number of `to_ids`, preserving the ordering that parsers rely on
476/// (e.g., `dimg` reference index 0 = base image, index 1 = gain map).
477#[derive(Debug, Clone)]
478pub struct IrefMultiEntryBox {
479    pub from_id: u16,
480    pub to_ids: ArrayVec<u16, 4>,
481    pub typ: FourCC,
482}
483
484impl MpegBox for IrefMultiEntryBox {
485    #[inline(always)]
486    fn len(&self) -> usize {
487        BASIC_BOX_SIZE
488            + 2 // from_id
489            + 2 // reference_count
490            + 2 * self.to_ids.len() // to_ids
491    }
492
493    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
494        let mut b = w.basic_box(self.len(), self.typ.0)?;
495        b.u16(self.from_id)?;
496        b.u16(self.to_ids.len() as u16)?;
497        for &to_id in &self.to_ids {
498            b.u16(to_id)?;
499        }
500        Ok(())
501    }
502}
503
504#[derive(Debug, Clone)]
505pub struct IrefBox {
506    pub entries: ArrayVec<IrefEntryBox, 6>,
507    pub multi_entries: ArrayVec<IrefMultiEntryBox, 2>,
508}
509
510impl IrefBox {
511    pub fn is_empty(&self) -> bool {
512        self.entries.is_empty() && self.multi_entries.is_empty()
513    }
514}
515
516impl MpegBox for IrefBox {
517    #[inline(always)]
518    fn len(&self) -> usize {
519        FULL_BOX_SIZE
520            + self.entries.iter().map(|e| e.len()).sum::<usize>()
521            + self.multi_entries.iter().map(|e| e.len()).sum::<usize>()
522    }
523
524    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
525        let mut b = w.full_box(self.len(), *b"iref", 0)?;
526        for entry in &self.entries {
527            entry.write(&mut b)?;
528        }
529        for entry in &self.multi_entries {
530            entry.write(&mut b)?;
531        }
532        Ok(())
533    }
534}
535
536/// Auxiliary item (alpha or depth map)
537#[derive(Debug, Copy, Clone)]
538#[allow(unused)]
539pub struct AuxlBox {}
540
541impl MpegBox for AuxlBox {
542    #[inline(always)]
543    fn len(&self) -> usize {
544        FULL_BOX_SIZE
545    }
546
547    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
548        w.full_box(self.len(), *b"auxl", 0)?;
549        Ok(())
550    }
551}
552
553/// ColourInformationBox
554#[derive(Debug, Copy, Clone, PartialEq)]
555#[non_exhaustive]
556pub struct ColrBox {
557    pub color_primaries: ColorPrimaries,
558    pub transfer_characteristics: TransferCharacteristics,
559    pub matrix_coefficients: MatrixCoefficients,
560    pub full_range_flag: bool, // u1 + u7
561}
562
563impl Default for ColrBox {
564    fn default() -> Self {
565        Self {
566            color_primaries: ColorPrimaries::Bt709,
567            transfer_characteristics: TransferCharacteristics::Srgb,
568            matrix_coefficients: MatrixCoefficients::Bt601,
569            full_range_flag: true,
570        }
571    }
572}
573
574impl MpegBox for ColrBox {
575    #[inline(always)]
576    fn len(&self) -> usize {
577        BASIC_BOX_SIZE + 4 + 2 + 2 + 2 + 1
578    }
579
580    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
581        let mut b = w.basic_box(self.len(), *b"colr")?;
582        b.u32(u32::from_be_bytes(*b"nclx"))?;
583        b.u16(self.color_primaries as u16)?;
584        b.u16(self.transfer_characteristics as u16)?;
585        b.u16(self.matrix_coefficients as u16)?;
586        b.u8(if self.full_range_flag { 1 << 7 } else { 0 })
587    }
588}
589
590/// Content Light Level Information box (`clli`), per ISOBMFF § 12.1.5 / CEA-861.3.
591///
592/// Signals the content light level of HDR content to the display.
593/// Both values are in cd/m² (nits).
594#[derive(Debug, Copy, Clone, PartialEq)]
595#[non_exhaustive]
596pub struct ClliBox {
597    /// Maximum light level of any single pixel in the content (MaxCLL).
598    pub max_content_light_level: u16,
599    /// Maximum average light level of any single frame in the content (MaxFALL).
600    pub max_pic_average_light_level: u16,
601}
602
603impl ClliBox {
604    pub fn new(max_content_light_level: u16, max_pic_average_light_level: u16) -> Self {
605        Self { max_content_light_level, max_pic_average_light_level }
606    }
607}
608
609impl MpegBox for ClliBox {
610    #[inline(always)]
611    fn len(&self) -> usize {
612        BASIC_BOX_SIZE + 4
613    }
614
615    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
616        let mut b = w.basic_box(self.len(), *b"clli")?;
617        b.u16(self.max_content_light_level)?;
618        b.u16(self.max_pic_average_light_level)
619    }
620}
621
622/// Mastering Display Colour Volume box (`mdcv`), per ISOBMFF § 12.1.5 / SMPTE ST 2086.
623///
624/// Describes the color volume of the mastering display used to author the content.
625/// This does not describe the content itself — see [`ClliBox`] for that.
626#[derive(Debug, Copy, Clone, PartialEq)]
627#[non_exhaustive]
628pub struct MdcvBox {
629    /// Display primaries in CIE 1931 xy chromaticity, encoded as the value × 50000.
630    /// For example, D65 white (0.3127, 0.3290) encodes as (15635, 16450).
631    /// Order: \[green, blue, red\] per SMPTE ST 2086.
632    pub primaries: [(u16, u16); 3],
633    /// White point in CIE 1931 xy chromaticity, same encoding as `primaries`.
634    pub white_point: (u16, u16),
635    /// Maximum luminance of the mastering display in cd/m² × 10000.
636    /// For example, 1000 cd/m² = 10_000_000.
637    pub max_luminance: u32,
638    /// Minimum luminance of the mastering display in cd/m² × 10000.
639    /// For example, 0.005 cd/m² = 50.
640    pub min_luminance: u32,
641}
642
643impl MdcvBox {
644    pub fn new(primaries: [(u16, u16); 3], white_point: (u16, u16), max_luminance: u32, min_luminance: u32) -> Self {
645        Self { primaries, white_point, max_luminance, min_luminance }
646    }
647}
648
649impl MpegBox for MdcvBox {
650    #[inline(always)]
651    fn len(&self) -> usize {
652        BASIC_BOX_SIZE + 24
653    }
654
655    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
656        let mut b = w.basic_box(self.len(), *b"mdcv")?;
657        for &(x, y) in &self.primaries {
658            b.u16(x)?;
659            b.u16(y)?;
660        }
661        b.u16(self.white_point.0)?;
662        b.u16(self.white_point.1)?;
663        b.u32(self.max_luminance)?;
664        b.u32(self.min_luminance)
665    }
666}
667
668#[derive(Debug, Copy, Clone)]
669#[non_exhaustive]
670pub struct Av1CBox {
671    pub seq_profile: u8,
672    pub seq_level_idx_0: u8,
673    pub seq_tier_0: bool,
674    pub high_bitdepth: bool,
675    pub twelve_bit: bool,
676    pub monochrome: bool,
677    pub chroma_subsampling_x: bool,
678    pub chroma_subsampling_y: bool,
679    pub chroma_sample_position: u8,
680}
681
682impl Default for Av1CBox {
683    /// Default: 8-bit 4:2:0, seq_profile=0, level=4
684    fn default() -> Self {
685        Self {
686            seq_profile: 0,
687            seq_level_idx_0: 4,
688            seq_tier_0: false,
689            high_bitdepth: false,
690            twelve_bit: false,
691            monochrome: false,
692            chroma_subsampling_x: true,
693            chroma_subsampling_y: true,
694            chroma_sample_position: 0,
695        }
696    }
697}
698
699impl MpegBox for Av1CBox {
700    #[inline(always)]
701    fn len(&self) -> usize {
702        BASIC_BOX_SIZE + 4
703    }
704
705    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
706        let mut b = w.basic_box(self.len(), *b"av1C")?;
707        let flags1 =
708            u8::from(self.seq_tier_0) << 7 |
709            u8::from(self.high_bitdepth) << 6 |
710            u8::from(self.twelve_bit) << 5 |
711            u8::from(self.monochrome) << 4 |
712            u8::from(self.chroma_subsampling_x) << 3 |
713            u8::from(self.chroma_subsampling_y) << 2 |
714            self.chroma_sample_position;
715
716        b.push(&[
717            0x81, // marker and version
718            (self.seq_profile << 5) | self.seq_level_idx_0, // x2d == 45
719            flags1,
720            0,
721        ])
722    }
723}
724
725#[derive(Debug, Copy, Clone)]
726pub struct PitmBox(pub u16);
727
728impl MpegBox for PitmBox {
729    #[inline(always)]
730    fn len(&self) -> usize {
731        FULL_BOX_SIZE + 2
732    }
733
734    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
735        let mut b = w.full_box(self.len(), *b"pitm", 0)?;
736        b.u16(self.0)
737    }
738}
739
740#[derive(Debug, Clone)]
741pub struct IlocBox<'data> {
742    /// update before writing
743    pub absolute_offset_start: Option<NonZeroU32>,
744    pub items: ArrayVec<IlocItem<'data>, 8>,
745}
746
747#[derive(Debug, Clone)]
748pub struct IlocItem<'data> {
749    pub id: u16,
750    pub extents: [IlocExtent<'data>; 1],
751}
752
753#[derive(Debug, Copy, Clone)]
754pub struct IlocExtent<'data> {
755    /// offset and len will be calculated when writing
756    pub data: &'data [u8],
757}
758
759impl MpegBox for IlocBox<'_> {
760    #[inline(always)]
761    #[allow(unused_parens, clippy::identity_op)]
762    fn len(&self) -> usize {
763        FULL_BOX_SIZE
764        + 1 // offset_size, length_size
765        + 1 // base_offset_size, reserved
766        + 2 // num items
767        + self.items.iter().map(|i| ( // for each item
768            2 // id
769            + 2 // dat ref idx
770            + 0 // base_offset_size
771            + 2 // extent count
772            + i.extents.len() * ( // for each extent
773               4 // extent_offset
774               + 4 // extent_len
775            )
776        )).sum::<usize>()
777    }
778
779    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
780        let mut b = w.full_box(self.len(), *b"iloc", 0)?;
781        b.push(&[4 << 4 | 4, 0])?; // offset and length are 4 bytes
782
783        b.u16(self.items.len() as _)?; // num items
784        let mut next_start = if let Some(ok) = self.absolute_offset_start { ok.get() } else {
785            debug_assert!(false);
786            !0
787        };
788        for item in &self.items {
789            b.u16(item.id)?;
790            b.u16(0)?;
791            b.u16(item.extents.len() as _)?; // num extents
792            for ex in &item.extents {
793                let len = ex.data.len() as u32;
794                b.u32(next_start)?;
795                next_start += len;
796                b.u32(len)?;
797            }
798        }
799        Ok(())
800    }
801}
802
803/// Image Rotation box (`irot`). NOT a FullBox.
804///
805/// Specifies counter-clockwise rotation. The `angle` field is the
806/// raw 2-bit code: 0 = 0°, 1 = 90°, 2 = 180°, 3 = 270°.
807#[derive(Debug, Copy, Clone, PartialEq)]
808#[non_exhaustive]
809pub struct IrotBox {
810    /// Rotation code (0-3): 0=0°, 1=90° CCW, 2=180°, 3=270° CCW
811    pub angle: u8,
812}
813
814impl IrotBox {
815    pub fn new(angle: u8) -> Self { Self { angle } }
816}
817
818impl MpegBox for IrotBox {
819    #[inline(always)]
820    fn len(&self) -> usize {
821        BASIC_BOX_SIZE + 1
822    }
823
824    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
825        let mut b = w.basic_box(self.len(), *b"irot")?;
826        b.u8(self.angle & 0x03)
827    }
828}
829
830/// Image Mirror box (`imir`). NOT a FullBox.
831///
832/// Specifies a mirror axis to apply after rotation.
833/// `axis` = 0 means vertical axis (left-right flip),
834/// `axis` = 1 means horizontal axis (top-bottom flip).
835#[derive(Debug, Copy, Clone, PartialEq)]
836#[non_exhaustive]
837pub struct ImirBox {
838    /// 0 = vertical axis (left-right flip), 1 = horizontal axis (top-bottom flip)
839    pub axis: u8,
840}
841
842impl ImirBox {
843    pub fn new(axis: u8) -> Self { Self { axis } }
844}
845
846impl MpegBox for ImirBox {
847    #[inline(always)]
848    fn len(&self) -> usize {
849        BASIC_BOX_SIZE + 1
850    }
851
852    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
853        let mut b = w.basic_box(self.len(), *b"imir")?;
854        b.u8(self.axis & 0x01)
855    }
856}
857
858/// Clean Aperture box (`clap`).
859///
860/// Defines a crop rectangle as centered rational values.
861/// 32 bytes payload: 4 pairs of (numerator u32, denominator u32),
862/// except offsets which are signed numerators.
863#[derive(Debug, Copy, Clone, PartialEq)]
864#[non_exhaustive]
865pub struct ClapBox {
866    pub width_n: u32,
867    pub width_d: u32,
868    pub height_n: u32,
869    pub height_d: u32,
870    pub horiz_off_n: i32,
871    pub horiz_off_d: u32,
872    pub vert_off_n: i32,
873    pub vert_off_d: u32,
874}
875
876impl ClapBox {
877    /// Create from width, height, and center offset as rational numbers (n/d pairs).
878    #[allow(clippy::too_many_arguments)]
879    pub fn new(width_n: u32, width_d: u32, height_n: u32, height_d: u32,
880               horiz_off_n: i32, horiz_off_d: u32, vert_off_n: i32, vert_off_d: u32) -> Self {
881        Self { width_n, width_d, height_n, height_d, horiz_off_n, horiz_off_d, vert_off_n, vert_off_d }
882    }
883}
884
885impl MpegBox for ClapBox {
886    #[inline(always)]
887    fn len(&self) -> usize {
888        BASIC_BOX_SIZE + 32
889    }
890
891    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
892        let mut b = w.basic_box(self.len(), *b"clap")?;
893        b.u32(self.width_n)?;
894        b.u32(self.width_d)?;
895        b.u32(self.height_n)?;
896        b.u32(self.height_d)?;
897        b.push(&self.horiz_off_n.to_be_bytes())?;
898        b.u32(self.horiz_off_d)?;
899        b.push(&self.vert_off_n.to_be_bytes())?;
900        b.u32(self.vert_off_d)
901    }
902}
903
904/// Pixel Aspect Ratio box (`pasp`).
905///
906/// 8 bytes payload: h_spacing (u32) + v_spacing (u32).
907#[derive(Debug, Copy, Clone, PartialEq)]
908#[non_exhaustive]
909pub struct PaspBox {
910    pub h_spacing: u32,
911    pub v_spacing: u32,
912}
913
914impl PaspBox {
915    pub fn new(h_spacing: u32, v_spacing: u32) -> Self { Self { h_spacing, v_spacing } }
916}
917
918impl MpegBox for PaspBox {
919    #[inline(always)]
920    fn len(&self) -> usize {
921        BASIC_BOX_SIZE + 8
922    }
923
924    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
925        let mut b = w.basic_box(self.len(), *b"pasp")?;
926        b.u32(self.h_spacing)?;
927        b.u32(self.v_spacing)
928    }
929}
930
931/// ColourInformationBox with ICC profile (colour_type = 'prof' or 'rICC').
932#[derive(Debug, Clone)]
933#[non_exhaustive]
934pub struct ColrIccBox {
935    pub icc_data: Vec<u8>,
936}
937
938impl ColrIccBox {
939    pub fn new(icc_data: Vec<u8>) -> Self { Self { icc_data } }
940}
941
942impl MpegBox for ColrIccBox {
943    #[inline(always)]
944    fn len(&self) -> usize {
945        BASIC_BOX_SIZE + 4 + self.icc_data.len()
946    }
947
948    fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
949        let mut b = w.basic_box(self.len(), *b"colr")?;
950        // 'prof' for restricted ICC profile (sufficient for AVIF)
951        b.u32(u32::from_be_bytes(*b"prof"))?;
952        b.push(&self.icc_data)
953    }
954}
955
956#[derive(Debug, Clone)]
957pub struct MdatBox;
958
959impl MdatBox {
960    #[inline(always)]
961    fn len(&self, chunks: &IlocBox) -> usize {
962        BASIC_BOX_SIZE + chunks.items.iter().flat_map(|c| &c.extents).map(|d| d.data.len()).sum::<usize>()
963    }
964
965    fn write<B: WriterBackend>(&self, w: &mut Writer<B>, chunks: &IlocBox) -> Result<(), B::Error> {
966        let mut b = w.basic_box(self.len(chunks), *b"mdat")?;
967        for ch in chunks.items.iter().flat_map(|c| &c.extents) {
968            b.push(ch.data)?;
969        }
970        Ok(())
971    }
972}