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 fn mdat_payload_start_offset(&self) -> u32 {
35 (self.ftyp.len() + self.meta.len()
36 + BASIC_BOX_SIZE) as u32 }
38
39 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
97impl MpegBox for FtypBox {
99 #[inline(always)]
100 fn len(&self) -> usize {
101 BASIC_BOX_SIZE
102 + 4 + 4 + 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#[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#[derive(Debug, Clone)]
156pub struct IinfBox {
157 pub items: ArrayVec<InfeBox, 3>,
158}
159
160impl MpegBox for IinfBox {
161 #[inline]
162 fn len(&self) -> usize {
163 FULL_BOX_SIZE
164 + 2 + 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#[derive(Debug, Copy, Clone)]
180pub struct InfeBox {
181 pub id: u16,
182 pub typ: FourCC,
183 pub name: &'static str,
184}
185
186impl MpegBox for InfeBox {
187 #[inline(always)]
188 fn len(&self) -> usize {
189 FULL_BOX_SIZE
190 + 2 + 2 + 4 + self.name.len() + 1 }
195
196 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
197 let mut b = w.full_box(self.len(), *b"infe", 2)?;
198 b.u16(self.id)?;
199 b.u16(0)?;
200 b.push(&self.typ.0)?;
201 b.push(self.name.as_bytes())?;
202 b.u8(0)
203 }
204}
205
206#[derive(Debug, Clone)]
207pub struct HdlrBox {
208}
209
210impl MpegBox for HdlrBox {
211 #[inline(always)]
212 fn len(&self) -> usize {
213 FULL_BOX_SIZE + 4 + 4 + 13
214 }
215
216 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
217 let mut b = w.full_box(self.len(), *b"hdlr", 0)?;
220 b.u32(0)?; b.push(b"pict")?; b.u32(0)?; b.u32(0)?; b.u32(0)?; b.u8(0)?; Ok(())
227 }
228}
229
230#[derive(Debug, Clone)]
232pub struct IprpBox {
233 pub ipco: IpcoBox,
234 pub ipma: IpmaBox,
235}
236
237impl MpegBox for IprpBox {
238 #[inline(always)]
239 fn len(&self) -> usize {
240 BASIC_BOX_SIZE
241 + self.ipco.len()
242 + self.ipma.len()
243 }
244
245 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
246 let mut b = w.basic_box(self.len(), *b"iprp")?;
247 self.ipco.write(&mut b)?;
248 self.ipma.write(&mut b)
249 }
250}
251
252#[derive(Debug, Clone)]
253#[non_exhaustive]
254pub enum IpcoProp {
255 Av1C(Av1CBox),
256 Pixi(PixiBox),
257 Ispe(IspeBox),
258 AuxC(AuxCBox),
259 Colr(ColrBox),
260 Clli(ClliBox),
261 Mdcv(MdcvBox),
262}
263
264impl IpcoProp {
265 pub fn len(&self) -> usize {
266 match self {
267 Self::Av1C(p) => p.len(),
268 Self::Pixi(p) => p.len(),
269 Self::Ispe(p) => p.len(),
270 Self::AuxC(p) => p.len(),
271 Self::Colr(p) => p.len(),
272 Self::Clli(p) => p.len(),
273 Self::Mdcv(p) => p.len(),
274 }
275 }
276
277 pub fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
278 match self {
279 Self::Av1C(p) => p.write(w),
280 Self::Pixi(p) => p.write(w),
281 Self::Ispe(p) => p.write(w),
282 Self::AuxC(p) => p.write(w),
283 Self::Colr(p) => p.write(w),
284 Self::Clli(p) => p.write(w),
285 Self::Mdcv(p) => p.write(w),
286 }
287 }
288}
289
290#[derive(Debug, Clone)]
292pub struct IpcoBox {
293 props: ArrayVec<IpcoProp, 9>,
294}
295
296impl IpcoBox {
297 pub fn new() -> Self {
298 Self { props: ArrayVec::new() }
299 }
300
301 #[must_use]
302 pub fn push(&mut self, prop: IpcoProp) -> Option<u8> {
303 self.props.try_push(prop).ok()?;
304 Some(self.props.len() as u8) }
306
307 pub(crate) fn ispe(&self) -> Option<&IspeBox> {
308 self.props.iter().find_map(|b| match b {
309 IpcoProp::Ispe(i) => Some(i),
310 _ => None,
311 })
312 }
313}
314
315impl MpegBox for IpcoBox {
316 #[inline]
317 fn len(&self) -> usize {
318 BASIC_BOX_SIZE
319 + self.props.iter().map(|a| a.len()).sum::<usize>()
320 }
321
322 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
323 let mut b = w.basic_box(self.len(), *b"ipco")?;
324 for p in &self.props {
325 p.write(&mut b)?;
326 }
327 Ok(())
328 }
329}
330
331#[derive(Debug, Copy, Clone)]
332pub struct AuxCBox {
333 pub urn: &'static str,
334}
335
336impl AuxCBox {
337 pub fn len(&self) -> usize {
338 FULL_BOX_SIZE + self.urn.len() + 1
339 }
340
341 pub fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
342 let mut b = w.full_box(self.len(), *b"auxC", 0)?;
343 b.push(self.urn.as_bytes())?;
344 b.u8(0)
345 }
346}
347
348#[derive(Debug, Copy, Clone)]
350pub struct PixiBox {
351 pub depth: u8,
352 pub channels: u8,
353}
354
355impl PixiBox {
356 pub fn len(self) -> usize {
357 FULL_BOX_SIZE
358 + 1 + self.channels as usize
359 }
360
361 pub fn write<B: WriterBackend>(self, w: &mut Writer<B>) -> Result<(), B::Error> {
362 let mut b = w.full_box(self.len(), *b"pixi", 0)?;
363 b.u8(self.channels)?;
364 for _ in 0..self.channels {
365 b.u8(self.depth)?;
366 }
367 Ok(())
368 }
369}
370
371#[derive(Debug, Copy, Clone)]
373pub struct IspeBox {
374 pub width: u32,
375 pub height: u32,
376}
377
378impl MpegBox for IspeBox {
379 #[inline(always)]
380 fn len(&self) -> usize {
381 FULL_BOX_SIZE + 4 + 4
382 }
383
384 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
385 let mut b = w.full_box(self.len(), *b"ispe", 0)?;
386 b.u32(self.width)?;
387 b.u32(self.height)
388 }
389}
390
391#[derive(Debug, Clone)]
393pub struct IpmaEntry {
394 pub item_id: u16,
395 pub prop_ids: ArrayVec<u8, 7>,
396}
397
398#[derive(Debug, Clone)]
399pub struct IpmaBox {
400 pub entries: ArrayVec<IpmaEntry, 2>,
401}
402
403impl MpegBox for IpmaBox {
404 #[inline]
405 fn len(&self) -> usize {
406 FULL_BOX_SIZE + 4 + self.entries.iter().map(|e| 2 + 1 + e.prop_ids.len()).sum::<usize>()
407 }
408
409 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
410 let mut b = w.full_box(self.len(), *b"ipma", 0)?;
411 b.u32(self.entries.len() as _)?; for e in &self.entries {
414 b.u16(e.item_id)?;
415 b.u8(e.prop_ids.len() as u8)?; for &p in &e.prop_ids {
417 b.u8(p)?;
418 }
419 }
420 Ok(())
421 }
422}
423
424#[derive(Debug, Copy, Clone)]
426pub struct IrefEntryBox {
427 pub from_id: u16,
428 pub to_id: u16,
429 pub typ: FourCC,
430}
431
432impl MpegBox for IrefEntryBox {
433 #[inline(always)]
434 fn len(&self) -> usize {
435 BASIC_BOX_SIZE
436 + 2 + 2 + 2 }
440
441 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
442 let mut b = w.basic_box(self.len(), self.typ.0)?;
443 b.u16(self.from_id)?;
444 b.u16(1)?;
445 b.u16(self.to_id)
446 }
447}
448
449#[derive(Debug, Clone)]
450pub struct IrefBox {
451 pub entries: ArrayVec<IrefEntryBox, 3>,
452}
453
454impl IrefBox {
455 pub fn is_empty(&self) -> bool {
456 self.entries.is_empty()
457 }
458}
459
460impl MpegBox for IrefBox {
461 #[inline(always)]
462 fn len(&self) -> usize {
463 FULL_BOX_SIZE + self.entries.iter().map(|e| e.len()).sum::<usize>()
464 }
465
466 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
467 let mut b = w.full_box(self.len(), *b"iref", 0)?;
468 for entry in &self.entries {
469 entry.write(&mut b)?;
470 }
471 Ok(())
472 }
473}
474
475#[derive(Debug, Copy, Clone)]
477#[allow(unused)]
478pub struct AuxlBox {}
479
480impl MpegBox for AuxlBox {
481 #[inline(always)]
482 fn len(&self) -> usize {
483 FULL_BOX_SIZE
484 }
485
486 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
487 w.full_box(self.len(), *b"auxl", 0)?;
488 Ok(())
489 }
490}
491
492#[derive(Debug, Copy, Clone, PartialEq)]
494pub struct ColrBox {
495 pub color_primaries: ColorPrimaries,
496 pub transfer_characteristics: TransferCharacteristics,
497 pub matrix_coefficients: MatrixCoefficients,
498 pub full_range_flag: bool, }
500
501impl Default for ColrBox {
502 fn default() -> Self {
503 Self {
504 color_primaries: ColorPrimaries::Bt709,
505 transfer_characteristics: TransferCharacteristics::Srgb,
506 matrix_coefficients: MatrixCoefficients::Bt601,
507 full_range_flag: true,
508 }
509 }
510}
511
512impl MpegBox for ColrBox {
513 #[inline(always)]
514 fn len(&self) -> usize {
515 BASIC_BOX_SIZE + 4 + 2 + 2 + 2 + 1
516 }
517
518 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
519 let mut b = w.basic_box(self.len(), *b"colr")?;
520 b.u32(u32::from_be_bytes(*b"nclx"))?;
521 b.u16(self.color_primaries as u16)?;
522 b.u16(self.transfer_characteristics as u16)?;
523 b.u16(self.matrix_coefficients as u16)?;
524 b.u8(if self.full_range_flag { 1 << 7 } else { 0 })
525 }
526}
527
528#[derive(Debug, Copy, Clone, PartialEq)]
533pub struct ClliBox {
534 pub max_content_light_level: u16,
536 pub max_pic_average_light_level: u16,
538}
539
540impl MpegBox for ClliBox {
541 #[inline(always)]
542 fn len(&self) -> usize {
543 BASIC_BOX_SIZE + 4
544 }
545
546 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
547 let mut b = w.basic_box(self.len(), *b"clli")?;
548 b.u16(self.max_content_light_level)?;
549 b.u16(self.max_pic_average_light_level)
550 }
551}
552
553#[derive(Debug, Copy, Clone, PartialEq)]
558pub struct MdcvBox {
559 pub primaries: [(u16, u16); 3],
563 pub white_point: (u16, u16),
565 pub max_luminance: u32,
568 pub min_luminance: u32,
571}
572
573impl MpegBox for MdcvBox {
574 #[inline(always)]
575 fn len(&self) -> usize {
576 BASIC_BOX_SIZE + 24
577 }
578
579 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
580 let mut b = w.basic_box(self.len(), *b"mdcv")?;
581 for &(x, y) in &self.primaries {
582 b.u16(x)?;
583 b.u16(y)?;
584 }
585 b.u16(self.white_point.0)?;
586 b.u16(self.white_point.1)?;
587 b.u32(self.max_luminance)?;
588 b.u32(self.min_luminance)
589 }
590}
591
592#[derive(Debug, Copy, Clone)]
593pub struct Av1CBox {
594 pub seq_profile: u8,
595 pub seq_level_idx_0: u8,
596 pub seq_tier_0: bool,
597 pub high_bitdepth: bool,
598 pub twelve_bit: bool,
599 pub monochrome: bool,
600 pub chroma_subsampling_x: bool,
601 pub chroma_subsampling_y: bool,
602 pub chroma_sample_position: u8,
603}
604
605impl MpegBox for Av1CBox {
606 #[inline(always)]
607 fn len(&self) -> usize {
608 BASIC_BOX_SIZE + 4
609 }
610
611 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
612 let mut b = w.basic_box(self.len(), *b"av1C")?;
613 let flags1 =
614 u8::from(self.seq_tier_0) << 7 |
615 u8::from(self.high_bitdepth) << 6 |
616 u8::from(self.twelve_bit) << 5 |
617 u8::from(self.monochrome) << 4 |
618 u8::from(self.chroma_subsampling_x) << 3 |
619 u8::from(self.chroma_subsampling_y) << 2 |
620 self.chroma_sample_position;
621
622 b.push(&[
623 0x81, (self.seq_profile << 5) | self.seq_level_idx_0, flags1,
626 0,
627 ])
628 }
629}
630
631#[derive(Debug, Copy, Clone)]
632pub struct PitmBox(pub u16);
633
634impl MpegBox for PitmBox {
635 #[inline(always)]
636 fn len(&self) -> usize {
637 FULL_BOX_SIZE + 2
638 }
639
640 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
641 let mut b = w.full_box(self.len(), *b"pitm", 0)?;
642 b.u16(self.0)
643 }
644}
645
646#[derive(Debug, Clone)]
647pub struct IlocBox<'data> {
648 pub absolute_offset_start: Option<NonZeroU32>,
650 pub items: ArrayVec<IlocItem<'data>, 3>,
651}
652
653#[derive(Debug, Clone)]
654pub struct IlocItem<'data> {
655 pub id: u16,
656 pub extents: [IlocExtent<'data>; 1],
657}
658
659#[derive(Debug, Copy, Clone)]
660pub struct IlocExtent<'data> {
661 pub data: &'data [u8],
663}
664
665impl MpegBox for IlocBox<'_> {
666 #[inline(always)]
667 #[allow(unused_parens)]
668 fn len(&self) -> usize {
669 FULL_BOX_SIZE
670 + 1 + 1 + 2 + self.items.iter().map(|i| ( 2 + 2 + 0 + 2 + i.extents.len() * ( 4 + 4 )
682 )).sum::<usize>()
683 }
684
685 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
686 let mut b = w.full_box(self.len(), *b"iloc", 0)?;
687 b.push(&[4 << 4 | 4, 0])?; b.u16(self.items.len() as _)?; let mut next_start = if let Some(ok) = self.absolute_offset_start { ok.get() } else {
691 debug_assert!(false);
692 !0
693 };
694 for item in &self.items {
695 b.u16(item.id)?;
696 b.u16(0)?;
697 b.u16(item.extents.len() as _)?; for ex in &item.extents {
699 let len = ex.data.len() as u32;
700 b.u32(next_start)?;
701 next_start += len;
702 b.u32(len)?;
703 }
704 }
705 Ok(())
706 }
707}
708
709#[derive(Debug, Clone)]
710pub struct MdatBox;
711
712impl MdatBox {
713 #[inline(always)]
714 fn len(&self, chunks: &IlocBox) -> usize {
715 BASIC_BOX_SIZE + chunks.items.iter().flat_map(|c| &c.extents).map(|d| d.data.len()).sum::<usize>()
716 }
717
718 fn write<B: WriterBackend>(&self, w: &mut Writer<B>, chunks: &IlocBox) -> Result<(), B::Error> {
719 let mut b = w.basic_box(self.len(chunks), *b"mdat")?;
720 for ch in chunks.items.iter().flat_map(|c| &c.extents) {
721 b.push(ch.data)?;
722 }
723 Ok(())
724 }
725}