Skip to main content

mp4_atom/moov/trak/mdia/minf/stbl/stsd/
sv3d.rs

1use crate::*;
2
3use super::spatial::{
4    decode_children, decode_slot, decode_variant, encode_kept, ignore_trailing, spatial_children,
5};
6
7/// Spherical Video Box, [Spherical Video V2](https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md#spherical-video-box-sv3d).
8///
9/// The spec requires `svhd` and `proj`; they are optional here, as a parser
10/// does not validate. A writer sets both, and `prhd` in `proj`: a reader may
11/// expect `svhd`, `proj` and `prhd` in that order, which is how they are
12/// written, and ignore the projection when one is missing.
13#[derive(Debug, Clone, PartialEq, Default)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[cfg_attr(feature = "serde", serde(default))]
16pub struct Sv3d {
17    pub svhd: Option<Svhd>,
18    pub proj: Option<Proj>,
19    /// Children this crate does not model, repeats, and children that did not decode.
20    pub unknown: Vec<Any>,
21}
22
23impl Eq for Sv3d {}
24
25impl Atom for Sv3d {
26    const KIND: FourCC = FourCC::new(b"sv3d");
27
28    spatial_children! { svhd: Svhd, proj: Proj }
29}
30
31/// Spherical Video Header, [Spherical Video V2](https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md#spherical-video-header-svhd).
32#[derive(Debug, Clone, PartialEq, Eq, Default)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34pub struct Svhd {
35    pub metadata_source: String,
36}
37
38impl AtomExt for Svhd {
39    type Ext = FullBoxV0;
40
41    const KIND_EXT: FourCC = FourCC::new(b"svhd");
42
43    fn decode_body_ext<B: Buf>(buf: &mut B, _ext: FullBoxV0) -> Result<Self> {
44        let metadata_source = String::decode(buf)?;
45        ignore_trailing(Self::KIND_EXT, buf);
46        Ok(Svhd { metadata_source })
47    }
48
49    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<FullBoxV0> {
50        self.metadata_source.as_str().encode(buf)?;
51        Ok(FullBoxV0)
52    }
53}
54
55/// Projection Box, [Spherical Video V2](https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md#projection-box-proj).
56///
57/// `kind` is `None` when the box holds no projection this crate models; a
58/// projection data box of any other type is kept in `unknown`.
59#[derive(Debug, Clone, PartialEq, Default)]
60#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
61#[cfg_attr(feature = "serde", serde(default))]
62pub struct Proj {
63    pub prhd: Option<Prhd>,
64    pub kind: Option<ProjKind>,
65    /// Children this crate does not model, repeats, and children that did not decode.
66    pub unknown: Vec<Any>,
67}
68
69impl Eq for Proj {}
70
71impl Atom for Proj {
72    const KIND: FourCC = FourCC::new(b"proj");
73
74    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
75        let mut proj = Proj::default();
76        // "There must not be more than one subclass of a ProjectionDataBox in a
77        // given `proj` box", so a second projection is kept as a repeat.
78        decode_children(buf, |kind, body| match kind {
79            Prhd::KIND => decode_slot(Self::KIND, &mut proj.prhd, body, &mut proj.unknown),
80            Equi::KIND => decode_variant(
81                Self::KIND,
82                &mut proj.kind,
83                body,
84                &mut proj.unknown,
85                ProjKind::Equi,
86            ),
87            Cbmp::KIND => decode_variant(
88                Self::KIND,
89                &mut proj.kind,
90                body,
91                &mut proj.unknown,
92                ProjKind::Cbmp,
93            ),
94            Mshp::KIND => decode_variant(
95                Self::KIND,
96                &mut proj.kind,
97                body,
98                &mut proj.unknown,
99                ProjKind::Mshp,
100            ),
101            kind => proj.unknown.push(Any::Unknown(kind, body.to_vec())),
102        })?;
103        Ok(proj)
104    }
105
106    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
107        self.prhd.encode(buf)?;
108        encode_kept(&self.unknown, |kind| kind == Prhd::KIND, buf)?;
109        match &self.kind {
110            Some(ProjKind::Equi(atom)) => atom.encode(buf)?,
111            Some(ProjKind::Cbmp(atom)) => atom.encode(buf)?,
112            Some(ProjKind::Mshp(atom)) => atom.encode(buf)?,
113            None => {}
114        }
115        encode_kept(
116            &self.unknown,
117            |kind| matches!(kind, Equi::KIND | Cbmp::KIND | Mshp::KIND),
118            buf,
119        )?;
120        encode_kept(
121            &self.unknown,
122            |kind| !matches!(kind, Prhd::KIND | Equi::KIND | Cbmp::KIND | Mshp::KIND),
123            buf,
124        )
125    }
126}
127
128/// Projection Header, [Spherical Video V2](https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md#projection-header-box-prhd):
129/// the pose, in 16.16 fixed-point degrees.
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
131#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
132pub struct Prhd {
133    pub pose_yaw_degrees: i32,
134    pub pose_pitch_degrees: i32,
135    pub pose_roll_degrees: i32,
136}
137
138impl AtomExt for Prhd {
139    type Ext = FullBoxV0;
140
141    const KIND_EXT: FourCC = FourCC::new(b"prhd");
142
143    fn decode_body_ext<B: Buf>(buf: &mut B, _ext: FullBoxV0) -> Result<Self> {
144        let prhd = Prhd {
145            pose_yaw_degrees: i32::decode(buf)?,
146            pose_pitch_degrees: i32::decode(buf)?,
147            pose_roll_degrees: i32::decode(buf)?,
148        };
149        ignore_trailing(Self::KIND_EXT, buf);
150        Ok(prhd)
151    }
152
153    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<FullBoxV0> {
154        self.pose_yaw_degrees.encode(buf)?;
155        self.pose_pitch_degrees.encode(buf)?;
156        self.pose_roll_degrees.encode(buf)?;
157        Ok(FullBoxV0)
158    }
159}
160
161#[derive(Debug, Clone, PartialEq, Eq)]
162#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
163#[non_exhaustive]
164pub enum ProjKind {
165    Equi(Equi),
166    Cbmp(Cbmp),
167    Mshp(Mshp),
168}
169
170/// Equirectangular Projection, [Spherical Video V2](https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md#equirectangular-projection-box-equi).
171/// The bounds are 0.32 fixed-point fractions of the projection, measured from
172/// each edge, that the frame does not cover; all zero for a full 360° frame.
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
174#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
175pub struct Equi {
176    pub projection_bounds_top: u32,
177    pub projection_bounds_bottom: u32,
178    pub projection_bounds_left: u32,
179    pub projection_bounds_right: u32,
180}
181
182impl AtomExt for Equi {
183    type Ext = FullBoxV0;
184
185    const KIND_EXT: FourCC = FourCC::new(b"equi");
186
187    fn decode_body_ext<B: Buf>(buf: &mut B, _ext: FullBoxV0) -> Result<Self> {
188        let equi = Equi {
189            projection_bounds_top: u32::decode(buf)?,
190            projection_bounds_bottom: u32::decode(buf)?,
191            projection_bounds_left: u32::decode(buf)?,
192            projection_bounds_right: u32::decode(buf)?,
193        };
194        ignore_trailing(Self::KIND_EXT, buf);
195        Ok(equi)
196    }
197
198    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<FullBoxV0> {
199        self.projection_bounds_top.encode(buf)?;
200        self.projection_bounds_bottom.encode(buf)?;
201        self.projection_bounds_left.encode(buf)?;
202        self.projection_bounds_right.encode(buf)?;
203        Ok(FullBoxV0)
204    }
205}
206
207/// Cubemap Projection, [Spherical Video V2](https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md#cubemap-projection-box-cbmp).
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
209#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
210pub struct Cbmp {
211    pub layout: u32,
212    pub padding: u32,
213}
214
215impl AtomExt for Cbmp {
216    type Ext = FullBoxV0;
217
218    const KIND_EXT: FourCC = FourCC::new(b"cbmp");
219
220    fn decode_body_ext<B: Buf>(buf: &mut B, _ext: FullBoxV0) -> Result<Self> {
221        let cbmp = Cbmp {
222            layout: u32::decode(buf)?,
223            padding: u32::decode(buf)?,
224        };
225        ignore_trailing(Self::KIND_EXT, buf);
226        Ok(cbmp)
227    }
228
229    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<FullBoxV0> {
230        self.layout.encode(buf)?;
231        self.padding.encode(buf)?;
232        Ok(FullBoxV0)
233    }
234}
235
236/// Mesh Projection, [Spherical Video V2](https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md#mesh-projection-box-mshp).
237/// `data` holds the `mesh` boxes and any boxes after them, compressed with raw
238/// DEFLATE (RFC 1951, no zlib or gzip header) when `encoding_four_cc` is
239/// `dfl8`, or as-is when it is `raw `. `crc` is the CRC-32 of every byte after
240/// it.
241#[derive(Debug, Clone, PartialEq, Eq)]
242#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
243pub struct Mshp {
244    pub crc: u32,
245    pub encoding_four_cc: FourCC,
246    pub data: Vec<u8>,
247}
248
249impl AtomExt for Mshp {
250    type Ext = FullBoxV0;
251
252    const KIND_EXT: FourCC = FourCC::new(b"mshp");
253
254    fn decode_body_ext<B: Buf>(buf: &mut B, _ext: FullBoxV0) -> Result<Self> {
255        Ok(Mshp {
256            crc: u32::decode(buf)?,
257            encoding_four_cc: FourCC::decode(buf)?,
258            data: Vec::decode(buf)?,
259        })
260    }
261
262    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<FullBoxV0> {
263        self.crc.encode(buf)?;
264        self.encoding_four_cc.encode(buf)?;
265        self.data.encode(buf)?;
266        Ok(FullBoxV0)
267    }
268}