1use crate::constants::ColorPrimaries;
2use crate::constants::MatrixCoefficients;
3use crate::constants::TransferCharacteristics;
4use crate::writer::Writer;
5use crate::writer::WriterBackend;
6use crate::writer::IO;
7use arrayvec::ArrayVec;
8use std::fmt;
9use std::io;
10use std::io::Write;
11
12pub trait MpegBox {
13 fn len(&self) -> usize;
14 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error>;
15}
16
17#[derive(Copy, Clone)]
18pub struct FourCC(pub [u8; 4]);
19
20impl fmt::Debug for FourCC {
21 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
22 match std::str::from_utf8(&self.0) {
23 Ok(s: &str) => s.fmt(f),
24 Err(_) => self.0.fmt(f),
25 }
26 }
27}
28
29#[derive(Debug, Clone)]
30pub struct AvifFile<'data> {
31 pub ftyp: FtypBox,
32 pub meta: MetaBox,
33 pub mdat: MdatBox<'data>,
34}
35
36impl AvifFile<'_> {
37 /// Where the primary data starts inside the `mdat` box, for `iloc`'s offset
38 fn mdat_payload_start_offset(&self) -> u32 {
39 (self.ftyp.len() + self.meta.len()
40 + BASIC_BOX_SIZE) as u32 // mdat head
41 }
42
43 /// `iloc` is mostly unnecssary, high risk of out-of-buffer accesses in parsers that don't pay attention,
44 /// and also awkward to serialize, because its content depends on its own serialized byte size.
45 fn fix_iloc_positions(&mut self) {
46 let start_offset = self.mdat_payload_start_offset();
47 for iloc_item in self.meta.iloc.items.iter_mut() {
48 for ex in iloc_item.extents.iter_mut() {
49 let abs = match ex.offset {
50 IlocOffset::Relative(n) => n as u32 + start_offset,
51 IlocOffset::Absolute(_) => continue,
52 };
53 ex.offset = IlocOffset::Absolute(abs);
54 }
55 }
56 }
57
58 pub fn write<W: Write>(&mut self, mut out: W) -> io::Result<()> {
59 self.fix_iloc_positions();
60
61 let mut tmp = Vec::with_capacity(self.ftyp.len() + self.meta.len());
62 let mut w = Writer::new(&mut tmp);
63 let _ = self.ftyp.write(&mut w);
64 let _ = self.meta.write(&mut w);
65 drop(w);
66 out.write_all(&tmp)?;
67 drop(tmp);
68
69 let mut out = IO(out);
70 let mut w = Writer::new(&mut out);
71 self.mdat.write(&mut w)?;
72 Ok(())
73 }
74}
75
76const BASIC_BOX_SIZE: usize = 8;
77const FULL_BOX_SIZE: usize = BASIC_BOX_SIZE + 4;
78
79#[derive(Debug, Clone)]
80pub struct FtypBox {
81 pub major_brand: FourCC,
82 pub minor_version: u32,
83 pub compatible_brands: ArrayVec<FourCC, 2>,
84}
85
86/// File Type box (chunk)
87impl MpegBox for FtypBox {
88 #[inline(always)]
89 fn len(&self) -> usize {
90 BASIC_BOX_SIZE
91 + 4 // brand
92 + 4 // ver
93 + 4 * self.compatible_brands.len()
94 }
95
96 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
97 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
98 b.basic_box(*b"ftyp")?;
99 b.push(&self.major_brand.0)?;
100 b.u32(self.minor_version)?;
101 for cb: &FourCC in &self.compatible_brands {
102 b.push(&cb.0)?;
103 }
104 Ok(())
105 }
106}
107
108/// Metadata box
109#[derive(Debug, Clone)]
110pub struct MetaBox {
111 pub hdlr: HdlrBox,
112 pub iloc: IlocBox,
113 pub iinf: IinfBox,
114 pub pitm: PitmBox,
115 pub iprp: IprpBox,
116 pub iref: ArrayVec<IrefBox, 2>,
117}
118
119impl MpegBox for MetaBox {
120 #[inline]
121 fn len(&self) -> usize {
122 FULL_BOX_SIZE
123 + self.hdlr.len()
124 + self.pitm.len()
125 + self.iloc.len()
126 + self.iinf.len()
127 + self.iprp.len()
128 + IrefBox2 {
129 entries: self.iref.iter().map(|e| e.entry).collect(),
130 }.len()
131 }
132
133 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
134 let mut b = w.new_box(self.len());
135 b.full_box(*b"meta", 0)?;
136 self.hdlr.write(&mut b)?;
137 self.pitm.write(&mut b)?;
138 self.iloc.write(&mut b)?;
139 self.iinf.write(&mut b)?;
140 let iref_fixed = IrefBox2 {
141 entries: self.iref.iter().map(|e| e.entry).collect(),
142 };
143 iref_fixed.write(&mut b)?;
144 self.iprp.write(&mut b)
145 }
146}
147
148/// Item Info box
149#[derive(Debug, Clone)]
150pub struct IinfBox {
151 pub items: ArrayVec<InfeBox, 2>,
152}
153
154impl MpegBox for IinfBox {
155 #[inline]
156 fn len(&self) -> usize {
157 FULL_BOX_SIZE
158 + 2 // num items u16
159 + self.items.iter().map(|item: &InfeBox| item.len()).sum::<usize>()
160 }
161
162 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
163 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
164 b.full_box(*b"iinf", version:0)?;
165 b.u16(self.items.len() as _)?;
166 for infe: &InfeBox in self.items.iter() {
167 infe.write(&mut b)?;
168 }
169 Ok(())
170 }
171}
172
173/// Item Info Entry box
174#[derive(Debug, Copy, Clone)]
175pub struct InfeBox {
176 pub id: u16,
177 pub typ: FourCC,
178 pub name: &'static str,
179}
180
181impl MpegBox for InfeBox {
182 #[inline(always)]
183 fn len(&self) -> usize {
184 FULL_BOX_SIZE
185 + 2 // id
186 + 2 // item_protection_index
187 + 4 // type
188 + self.name.as_bytes().len() + 1 // nul-terminated
189 }
190
191 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
192 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
193 b.full_box(*b"infe", version:2)?;
194 b.u16(self.id)?;
195 b.u16(val:0)?;
196 b.push(&self.typ.0)?;
197 b.push(self.name.as_bytes())?;
198 b.u8(val:0)
199 }
200}
201
202#[derive(Debug, Clone)]
203pub struct HdlrBox {
204}
205
206impl MpegBox for HdlrBox {
207 #[inline(always)]
208 fn len(&self) -> usize {
209 FULL_BOX_SIZE + 4 + 4 + 13
210 }
211
212 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
213 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
214 // because an image format needs to be told it's an image format,
215 // and it does it the way classic MacOS used to, because Quicktime.
216 b.full_box(*b"hdlr", version:0)?;
217 b.u32(val:0)?; // old MacOS file type handler
218 b.push(data:b"pict")?; // MacOS Quicktime subtype
219 b.u32(val:0)?; // Firefox 92 wants all 0 here
220 b.u32(val:0)?; // Reserved
221 b.u32(val:0)?; // Reserved
222 b.u8(val:0)?; // Pascal string for component name
223 Ok(())
224 }
225}
226
227/// Item properties + associations
228#[derive(Debug, Clone)]
229pub struct IprpBox {
230 pub ipco: IpcoBox,
231 pub ipma: IpmaBox,
232}
233
234impl MpegBox for IprpBox {
235 #[inline(always)]
236 fn len(&self) -> usize {
237 BASIC_BOX_SIZE
238 + self.ipco.len()
239 + self.ipma.len()
240 }
241
242 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
243 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
244 b.basic_box(*b"iprp")?;
245 self.ipco.write(&mut b)?;
246 self.ipma.write(&mut b)
247 }
248}
249
250#[derive(Debug, Clone)]
251#[non_exhaustive]
252pub enum IpcoProp {
253 Av1C(Av1CBox),
254 Pixi(PixiBox),
255 Ispe(IspeBox),
256 AuxC(AuxCBox),
257 Colr(ColrBox),
258}
259
260impl IpcoProp {
261 pub fn len(&self) -> usize {
262 match self {
263 Self::Av1C(p: &Av1CBox) => p.len(),
264 Self::Pixi(p: &PixiBox) => p.len(),
265 Self::Ispe(p: &IspeBox) => p.len(),
266 Self::AuxC(p: &AuxCBox) => p.len(),
267 Self::Colr(p: &ColrBox) => p.len(),
268 }
269 }
270
271 pub fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
272 match self {
273 Self::Av1C(p: &Av1CBox) => p.write(w),
274 Self::Pixi(p: &PixiBox) => p.write(w),
275 Self::Ispe(p: &IspeBox) => p.write(w),
276 Self::AuxC(p: &AuxCBox) => p.write(w),
277 Self::Colr(p: &ColrBox) => p.write(w),
278 }
279 }
280}
281
282/// Item Property Container box
283#[derive(Debug, Clone)]
284pub struct IpcoBox {
285 props: ArrayVec<IpcoProp, 7>,
286}
287
288impl IpcoBox {
289 pub fn new() -> Self {
290 Self { props: ArrayVec::new() }
291 }
292
293 pub fn push(&mut self, prop: IpcoProp) -> u8 {
294 self.props.push(element:prop);
295 self.props.len() as u8 // the spec wants them off by one
296 }
297}
298
299impl MpegBox for IpcoBox {
300 #[inline]
301 fn len(&self) -> usize {
302 BASIC_BOX_SIZE
303 + self.props.iter().map(|a: &IpcoProp| a.len()).sum::<usize>()
304 }
305
306 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
307 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
308 b.basic_box(*b"ipco")?;
309 for p: &IpcoProp in self.props.iter() {
310 p.write(&mut b)?;
311 }
312 Ok(())
313 }
314}
315
316#[derive(Debug, Copy, Clone)]
317pub struct AuxCBox {
318 pub urn: &'static str,
319}
320
321impl AuxCBox {
322 pub fn len(&self) -> usize {
323 FULL_BOX_SIZE + self.urn.len() + 1
324 }
325
326 pub fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
327 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
328 b.full_box(*b"auxC", version:0)?;
329 b.push(self.urn.as_bytes())?;
330 b.u8(val:0)
331 }
332}
333
334/// Pixies, I guess.
335#[derive(Debug, Copy, Clone)]
336pub struct PixiBox {
337 pub depth: u8,
338 pub channels: u8,
339}
340
341impl PixiBox {
342 pub fn len(&self) -> usize {
343 FULL_BOX_SIZE
344 + 1 + self.channels as usize
345 }
346
347 pub fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
348 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
349 b.full_box(*b"pixi", version:0)?;
350 b.u8(self.channels)?;
351 for _ in 0..self.channels {
352 b.u8(self.depth)?;
353 }
354 Ok(())
355 }
356}
357
358/// This is HEVC-specific and not for AVIF, but Chrome wants it :(
359#[derive(Debug, Copy, Clone)]
360pub struct IspeBox {
361 pub width: u32,
362 pub height: u32,
363}
364
365impl MpegBox for IspeBox {
366 #[inline(always)]
367 fn len(&self) -> usize {
368 FULL_BOX_SIZE + 4 + 4
369 }
370
371 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
372 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
373 b.full_box(*b"ispe", version:0)?;
374 b.u32(self.width)?;
375 b.u32(self.height)
376 }
377}
378
379/// Property→image associations
380#[derive(Debug, Clone)]
381pub struct IpmaEntry {
382 pub item_id: u16,
383 pub prop_ids: ArrayVec<u8, 5>,
384}
385
386#[derive(Debug, Clone)]
387pub struct IpmaBox {
388 pub entries: ArrayVec<IpmaEntry, 2>,
389}
390
391impl MpegBox for IpmaBox {
392 #[inline]
393 fn len(&self) -> usize {
394 FULL_BOX_SIZE + 4 + self.entries.iter().map(|e: &IpmaEntry| 2 + 1 + e.prop_ids.len()).sum::<usize>()
395 }
396
397 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
398 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
399 b.full_box(*b"ipma", version:0)?;
400 b.u32(self.entries.len() as _)?; // entry count
401
402 for e: &IpmaEntry in &self.entries {
403 b.u16(val:e.item_id)?;
404 b.u8(val:e.prop_ids.len() as u8)?; // assoc count
405 for &p: u8 in e.prop_ids.iter() {
406 b.u8(val:p)?;
407 }
408 }
409 Ok(())
410 }
411}
412
413/// Item Reference box
414#[derive(Debug, Copy, Clone)]
415pub struct IrefEntryBox {
416 pub from_id: u16,
417 pub to_id: u16,
418 pub typ: FourCC,
419}
420
421impl MpegBox for IrefEntryBox {
422 #[inline(always)]
423 fn len(&self) -> usize {
424 BASIC_BOX_SIZE
425 + 2 // from
426 + 2 // refcount
427 + 2 // to
428 }
429
430 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
431 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
432 b.basic_box(self.typ.0)?;
433 b.u16(self.from_id)?;
434 b.u16(val:1)?;
435 b.u16(self.to_id)
436 }
437}
438
439#[derive(Debug, Copy, Clone)]
440#[repr(transparent)]
441pub struct IrefBox {
442 pub entry: IrefEntryBox,
443}
444
445impl MpegBox for IrefBox {
446 #[inline(always)]
447 fn len(&self) -> usize {
448 FULL_BOX_SIZE + self.entry.len()
449 }
450
451 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
452 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
453 b.full_box(*b"iref", version:0)?;
454 self.entry.write(&mut b)
455 }
456}
457
458#[derive(Debug, Clone)]
459struct IrefBox2 {
460 pub entries: ArrayVec<IrefEntryBox, 2>,
461}
462
463impl MpegBox for IrefBox2 {
464 #[inline(always)]
465 fn len(&self) -> usize {
466 FULL_BOX_SIZE + self.entries.iter().map(|e: &IrefEntryBox| e.len()).sum::<usize>()
467 }
468
469 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
470 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
471 b.full_box(*b"iref", version:0)?;
472 for entry: &IrefEntryBox in &self.entries {
473 entry.write(&mut b)?
474 }
475 Ok(())
476 }
477}
478
479/// Auxiliary item (alpha or depth map)
480#[derive(Debug, Copy, Clone)]
481pub struct AuxlBox {}
482
483impl MpegBox for AuxlBox {
484 #[inline(always)]
485 fn len(&self) -> usize {
486 FULL_BOX_SIZE
487 }
488
489 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
490 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
491 b.full_box(*b"auxl", version:0)
492 }
493}
494
495/// ColourInformationBox
496#[derive(Debug, Copy, Clone, PartialEq)]
497pub struct ColrBox {
498 pub color_primaries: ColorPrimaries,
499 pub transfer_characteristics: TransferCharacteristics,
500 pub matrix_coefficients: MatrixCoefficients,
501 pub full_range_flag: bool, // u1 + u7
502}
503
504impl Default for ColrBox {
505 fn default() -> Self {
506 Self {
507 color_primaries: ColorPrimaries::Bt709,
508 transfer_characteristics: TransferCharacteristics::Srgb,
509 matrix_coefficients: MatrixCoefficients::Bt601,
510 full_range_flag: true,
511 }
512 }
513}
514
515impl MpegBox for ColrBox {
516 #[inline(always)]
517 fn len(&self) -> usize {
518 BASIC_BOX_SIZE + 4 + 2 + 2 + 2 + 1
519 }
520
521 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
522 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
523 b.basic_box(*b"colr")?;
524 b.u32(val:u32::from_be_bytes(*b"nclx"))?;
525 b.u16(self.color_primaries as u16)?;
526 b.u16(self.transfer_characteristics as u16)?;
527 b.u16(self.matrix_coefficients as u16)?;
528 b.u8(val:if self.full_range_flag { 1 << 7 } else { 0 })
529 }
530}
531#[derive(Debug, Copy, Clone)]
532pub struct Av1CBox {
533 pub seq_profile: u8,
534 pub seq_level_idx_0: u8,
535 pub seq_tier_0: bool,
536 pub high_bitdepth: bool,
537 pub twelve_bit: bool,
538 pub monochrome: bool,
539 pub chroma_subsampling_x: bool,
540 pub chroma_subsampling_y: bool,
541 pub chroma_sample_position: u8,
542}
543
544impl MpegBox for Av1CBox {
545 #[inline(always)]
546 fn len(&self) -> usize {
547 BASIC_BOX_SIZE + 4
548 }
549
550 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
551 let mut b = w.new_box(self.len());
552 b.basic_box(*b"av1C")?;
553 let flags1 =
554 u8::from(self.seq_tier_0) << 7 |
555 u8::from(self.high_bitdepth) << 6 |
556 u8::from(self.twelve_bit) << 5 |
557 u8::from(self.monochrome) << 4 |
558 u8::from(self.chroma_subsampling_x) << 3 |
559 u8::from(self.chroma_subsampling_y) << 2 |
560 self.chroma_sample_position;
561
562 b.push(&[
563 0x81, // marker and version
564 (self.seq_profile << 5) | self.seq_level_idx_0, // x2d == 45
565 flags1,
566 0,
567 ])
568 }
569}
570
571#[derive(Debug, Copy, Clone)]
572pub struct PitmBox(pub u16);
573
574impl MpegBox for PitmBox {
575 #[inline(always)]
576 fn len(&self) -> usize {
577 FULL_BOX_SIZE + 2
578 }
579
580 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
581 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
582 b.full_box(*b"pitm", version:0)?;
583 b.u16(self.0)
584 }
585}
586
587#[derive(Debug, Clone)]
588pub struct IlocBox {
589 pub items: ArrayVec<IlocItem, 2>,
590}
591
592#[derive(Debug, Clone)]
593pub struct IlocItem {
594 pub id: u16,
595 pub extents: ArrayVec<IlocExtent, 1>,
596}
597
598#[derive(Debug, Copy, Clone, PartialEq)]
599pub enum IlocOffset {
600 Relative(usize),
601 Absolute(u32),
602}
603
604#[derive(Debug, Copy, Clone)]
605pub struct IlocExtent {
606 pub offset: IlocOffset,
607 pub len: usize,
608}
609
610impl MpegBox for IlocBox {
611 #[inline(always)]
612 fn len(&self) -> usize {
613 FULL_BOX_SIZE
614 + 1 // offset_size, length_size
615 + 1 // base_offset_size, reserved
616 + 2 // num items
617 + self.items.iter().map(|i| ( // for each item
618 2 // id
619 + 2 // dat ref idx
620 + 0 // base_offset_size
621 + 2 // extent count
622 + i.extents.len() * ( // for each extent
623 4 // extent_offset
624 + 4 // extent_len
625 )
626 )).sum::<usize>()
627 }
628
629 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
630 let mut b = w.new_box(self.len());
631 b.full_box(*b"iloc", 0)?;
632 b.push(&[4 << 4 | 4, 0])?; // offset and length are 4 bytes
633
634 b.u16(self.items.len() as _)?; // num items
635 for item in self.items.iter() {
636 b.u16(item.id)?;
637 b.u16(0)?;
638 b.u16(item.extents.len() as _)?; // num extents
639 for ex in &item.extents {
640 b.u32(match ex.offset {
641 IlocOffset::Absolute(val) => val,
642 IlocOffset::Relative(_) => panic!("absolute offset must be set"),
643 })?;
644 b.u32(ex.len as _)?;
645 }
646 }
647 Ok(())
648 }
649}
650
651#[derive(Debug, Clone)]
652pub struct MdatBox<'data> {
653 pub data_chunks: ArrayVec<&'data [u8], 4>,
654}
655
656impl MpegBox for MdatBox<'_> {
657 #[inline(always)]
658 fn len(&self) -> usize {
659 BASIC_BOX_SIZE + self.data_chunks.iter().map(|c: &&[u8]| c.len()).sum::<usize>()
660 }
661
662 fn write<B: WriterBackend>(&self, w: &mut Writer<B>) -> Result<(), B::Error> {
663 let mut b: Writer<'_, '_, B> = w.new_box(self.len());
664 b.basic_box(*b"mdat")?;
665 for ch: &&[u8] in &self.data_chunks {
666 b.push(data:ch)?;
667 }
668 Ok(())
669 }
670}
671