alpha2_model/provider.rs
1//! Sources whose media an extension provider supplies.
2
3use alpha2_time::FrameRate;
4use serde::{Deserialize, Serialize};
5
6use crate::MediaKind;
7
8/// Most frames an image sequence lists.
9pub const MAX_SEQUENCE_FRAMES: usize = 100_000;
10
11/// Largest dotLottie package a provider reads, in bytes. Only the shown animation (within
12/// [`MAX_LOTTIE_DOCUMENT_BYTES`]) and its images are unpacked. Larger files are refused
13/// before reading.
14pub const MAX_PROVIDER_DOCUMENT_BYTES: usize = 256 * 1024 * 1024;
15
16/// Largest Lottie animation a provider reads, in bytes (a JSON file or an unpacked dotLottie
17/// animation). Parsing can take about 45 times the document size, so this caps it near
18/// 750 MiB; larger documents are refused before parsing.
19pub const MAX_LOTTIE_DOCUMENT_BYTES: usize = 16 * 1024 * 1024;
20
21/// Largest SVG document a provider reads, in bytes. Parsing can take about 30 times the
22/// document size, so this caps it near 960 MiB; larger documents are refused before parsing.
23pub const MAX_SVG_DOCUMENT_BYTES: usize = 32 * 1024 * 1024;
24
25/// A source whose media an extension provider supplies instead of the host's video and image
26/// decoders.
27///
28/// A host picks the provider by probing the bytes at import, never by file name, and records
29/// it here so every host opens the source the same way. The engine does not read it.
30#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
31#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
32#[serde(tag = "format", rename_all = "snake_case", from = "ProviderSourceWire")]
33#[non_exhaustive]
34pub enum ProviderSource {
35 /// A static SVG document, rasterized at its shown size. The source's kind is `image`.
36 Svg,
37 /// A GIF, animated WebP, or APNG: one cycle of frames, each with its own duration.
38 /// Repetition is the clip's. The source's kind is `video`.
39 AnimatedImage,
40 /// A Lottie animation (JSON file or dotLottie package), evaluated at its own frames. The
41 /// source's kind is `video`.
42 Lottie {
43 /// The dotLottie animation shown, by ID. Absent means the package's first; always absent
44 /// for a Lottie JSON file.
45 #[serde(default, skip_serializing_if = "Option::is_none")]
46 animation: Option<String>,
47 },
48 /// Still images played in order at a fixed rate. The source's kind is `video`.
49 ImageSequence {
50 /// Each frame's locator, in play order. `null` marks a missing number, allowed only when
51 /// `missing` says how to show it.
52 frames: Vec<Option<String>>,
53 /// The rate the frames play at.
54 frame_rate: FrameRate,
55 /// The number of the first frame, as its file was numbered.
56 #[serde(default)]
57 start_number: u64,
58 /// How a missing frame is shown.
59 #[serde(default)]
60 missing: MissingFrames,
61 /// The primaries of OpenEXR frames' linear RGB, overriding the first frame's
62 /// chromaticities. Only for OpenEXR frames.
63 #[serde(default, skip_serializing_if = "Option::is_none")]
64 primaries: Option<LinearPrimaries>,
65 },
66 /// A still OpenEXR image: linear-light RGB(A), read at half precision. The source's kind is
67 /// `image`.
68 Exr {
69 /// The primaries of its linear RGB, overriding the file's chromaticities (BT.709 when the
70 /// file has none, as OpenEXR defines).
71 #[serde(default, skip_serializing_if = "Option::is_none")]
72 primaries: Option<LinearPrimaries>,
73 },
74}
75
76/// The wire form of [`ProviderSource`].
77///
78/// serde lets an internally tagged unit variant ignore extra fields even with
79/// `deny_unknown_fields`; empty struct variants reject them, as the published schema does.
80#[derive(Deserialize)]
81#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
82#[cfg_attr(feature = "schema", schemars(rename = "ProviderSource"))]
83#[serde(tag = "format", rename_all = "snake_case", deny_unknown_fields)]
84pub(crate) enum ProviderSourceWire {
85 /// A static SVG document, rasterized at its shown size. The source's kind is `image`.
86 Svg {},
87 /// A GIF, animated WebP, or APNG: one cycle of frames, each with its own duration.
88 /// Repetition is the clip's. The source's kind is `video`.
89 AnimatedImage {},
90 /// A Lottie animation (JSON file or dotLottie package), evaluated at its own frames. The
91 /// source's kind is `video`.
92 Lottie {
93 /// The dotLottie animation shown, by ID. Absent means the package's first; always absent
94 /// for a Lottie JSON file.
95 #[serde(default, skip_serializing_if = "Option::is_none")]
96 animation: Option<String>,
97 },
98 /// Still images played in order at a fixed rate. The source's kind is `video`.
99 ImageSequence {
100 /// Each frame's locator, in play order. `null` marks a missing number, allowed only when
101 /// `missing` says how to show it.
102 frames: Vec<Option<String>>,
103 /// The rate the frames play at.
104 frame_rate: FrameRate,
105 /// The number of the first frame, as its file was numbered.
106 #[serde(default)]
107 start_number: u64,
108 /// How a missing frame is shown.
109 #[serde(default)]
110 missing: MissingFrames,
111 /// The primaries of OpenEXR frames' linear RGB, overriding the first frame's
112 /// chromaticities. Only for OpenEXR frames.
113 #[serde(default, skip_serializing_if = "Option::is_none")]
114 primaries: Option<LinearPrimaries>,
115 },
116 /// A still OpenEXR image: linear-light RGB(A), read at half precision. The source's kind is
117 /// `image`.
118 Exr {
119 /// The primaries of its linear RGB, overriding the file's chromaticities (BT.709 when the
120 /// file has none, as OpenEXR defines).
121 #[serde(default, skip_serializing_if = "Option::is_none")]
122 primaries: Option<LinearPrimaries>,
123 },
124}
125
126impl From<ProviderSourceWire> for ProviderSource {
127 fn from(wire: ProviderSourceWire) -> Self {
128 match wire {
129 ProviderSourceWire::Svg {} => Self::Svg,
130 ProviderSourceWire::AnimatedImage {} => Self::AnimatedImage,
131 ProviderSourceWire::Lottie { animation } => Self::Lottie { animation },
132 ProviderSourceWire::ImageSequence {
133 frames,
134 frame_rate,
135 start_number,
136 missing,
137 primaries,
138 } => Self::ImageSequence {
139 frames,
140 frame_rate,
141 start_number,
142 missing,
143 primaries,
144 },
145 ProviderSourceWire::Exr { primaries } => Self::Exr { primaries },
146 }
147 }
148}
149
150/// Named RGB primaries linear-light media may be in, each with its own white point.
151#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
152#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
153#[serde(rename_all = "snake_case")]
154pub enum LinearPrimaries {
155 /// ITU-R BT.709 and sRGB, D65 white.
156 Bt709,
157 /// ITU-R BT.2020, D65 white.
158 Bt2020,
159 /// DCI-P3 primaries with a D65 white, as Display P3.
160 P3D65,
161 /// ACES AP0 (SMPTE ST 2065-1), the ACES white.
162 AcesAp0,
163 /// ACES AP1 (ACEScg), the ACES white.
164 AcesAp1,
165}
166
167/// How an image sequence shows a frame missing from its numbering.
168#[derive(Copy, Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
169#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
170#[serde(rename_all = "snake_case")]
171pub enum MissingFrames {
172 /// A sequence with a missing frame is refused.
173 #[default]
174 Reject,
175 /// The frame before it is shown.
176 Hold,
177 /// Nothing is shown: a transparent frame.
178 Blank,
179}
180
181impl ProviderSource {
182 /// The kind of source the provider's media is.
183 #[must_use]
184 pub const fn kind(&self) -> MediaKind {
185 match self {
186 Self::Svg | Self::Exr { .. } => MediaKind::Image,
187 Self::AnimatedImage | Self::Lottie { .. } | Self::ImageSequence { .. } => {
188 MediaKind::Video
189 }
190 }
191 }
192
193 /// Whether the settings are coherent: a Lottie animation ID is not empty, and an image
194 /// sequence lists at most [`MAX_SEQUENCE_FRAMES`] frames, the first present, no locator empty
195 /// or repeated, and a missing frame only with a policy for it.
196 #[must_use]
197 pub fn is_valid(&self) -> bool {
198 if let Self::Lottie {
199 animation: Some(animation),
200 } = self
201 {
202 return !animation.is_empty();
203 }
204 let Self::ImageSequence {
205 frames, missing, ..
206 } = self
207 else {
208 return true;
209 };
210 let present = frames.iter().flatten().collect::<Vec<_>>();
211 let unique = present
212 .iter()
213 .collect::<std::collections::HashSet<_>>()
214 .len()
215 == present.len();
216 !frames.is_empty()
217 && frames.len() <= MAX_SEQUENCE_FRAMES
218 && frames[0].is_some()
219 && unique
220 && present.iter().all(|locator| !locator.is_empty())
221 && (present.len() == frames.len() || *missing != MissingFrames::Reject)
222 }
223}