alpha2_extensions/
registry.rs1use std::panic::{AssertUnwindSafe, catch_unwind};
11use std::sync::Arc;
12
13use alpha2_model::ProviderSource;
14use alpha2_time::MediaTime;
15
16use crate::api::{
17 ExtensionError, ProbeReport, SharedResolver, VisualFrame, VisualProvider, VisualSource,
18};
19
20#[derive(Default)]
22pub struct Registry {
23 providers: Vec<Box<dyn VisualProvider>>,
24}
25
26impl Registry {
27 #[must_use]
30 pub fn with_defaults() -> Self {
31 #[allow(unused_mut)]
33 let mut registry = Self::default();
34 #[cfg(feature = "svg")]
35 registry.register(Box::new(crate::SvgProvider));
36 #[cfg(feature = "animated-image")]
37 registry.register(Box::new(crate::AnimatedImageProvider));
38 #[cfg(feature = "lottie")]
39 registry.register(Box::new(crate::LottieProvider));
40 #[cfg(feature = "exr")]
41 registry.register(Box::new(crate::ExrProvider));
42 registry
43 }
44
45 pub fn register(&mut self, provider: Box<dyn VisualProvider>) {
47 self.providers.push(provider);
48 }
49
50 #[must_use]
52 pub fn names(&self) -> Vec<&'static str> {
53 self.providers
54 .iter()
55 .map(|provider| provider.name())
56 .collect()
57 }
58
59 #[must_use]
63 pub fn probe(
64 &self,
65 bytes: &[u8],
66 ) -> Option<Result<(&'static str, ProbeReport), ExtensionError>> {
67 self.providers.iter().find_map(|provider| {
68 let name = provider.name();
69 contained(name, || Ok(provider.probe(bytes)))
70 .unwrap_or_else(|error| Some(Err(error)))
71 .map(|report| report.map(|report| (name, report)))
72 })
73 }
74
75 #[must_use]
77 pub fn supports(&self, source: &ProviderSource) -> bool {
78 match source {
79 ProviderSource::ImageSequence { .. } => Self::reads_image_sequences(),
80 other => self.named(provider_name(other)).is_some(),
81 }
82 }
83
84 #[must_use]
88 pub const fn reads_image_sequences() -> bool {
89 cfg!(feature = "image-sequence")
90 }
91
92 #[must_use]
96 pub fn open_needs(&self, source: &ProviderSource) -> Vec<String> {
97 match source {
98 ProviderSource::ImageSequence { frames, .. } => {
99 frames.first().cloned().flatten().into_iter().collect()
100 }
101 _ => Vec::new(),
102 }
103 }
104
105 #[cfg_attr(not(feature = "image-sequence"), allow(clippy::needless_pass_by_value))]
122 pub fn open(
123 &self,
124 source: &ProviderSource,
125 bytes: Option<Arc<[u8]>>,
126 resolver: SharedResolver,
127 ) -> Result<Box<dyn VisualSource>, ExtensionError> {
128 let name = provider_name(source);
129 let opened = match source {
130 #[cfg(feature = "image-sequence")]
131 ProviderSource::ImageSequence {
132 frames,
133 frame_rate,
134 missing,
135 primaries,
136 ..
137 } => contained(name, || {
138 crate::open_sequence(frames, *frame_rate, *missing, *primaries, resolver)
139 })?,
140 other => {
141 let provider = self.named(name).ok_or_else(|| {
142 ExtensionError::Unsupported(format!("this build has no {name} provider"))
143 })?;
144 let bytes = bytes.ok_or_else(|| {
145 ExtensionError::MissingResource("the source's own bytes".into())
146 })?;
147 contained(name, || provider.open(bytes, other, &*resolver))?
150 }
151 };
152 Ok(Box::new(Contained {
153 name,
154 source: opened,
155 failed: None,
156 }))
157 }
158
159 fn named(&self, name: &str) -> Option<&dyn VisualProvider> {
160 self.providers
161 .iter()
162 .find(|provider| provider.name() == name)
163 .map(AsRef::as_ref)
164 }
165}
166
167struct Contained {
169 name: &'static str,
170 source: Box<dyn VisualSource>,
171 failed: Option<ExtensionError>,
173}
174
175impl VisualSource for Contained {
176 fn report(&self) -> &ProbeReport {
177 self.source.report()
178 }
179
180 fn render(&mut self, time: MediaTime, fit: (u32, u32)) -> Result<VisualFrame, ExtensionError> {
181 if let Some(error) = &self.failed {
182 return Err(error.clone());
183 }
184 let source = &mut self.source;
185 catch_unwind(AssertUnwindSafe(|| source.render(time, fit))).unwrap_or_else(|payload| {
186 let error = panicked(self.name, payload.as_ref());
187 self.failed = Some(error.clone());
188 Err(error)
189 })
190 }
191
192 fn needs(&self, time: MediaTime) -> Vec<String> {
193 if self.failed.is_some() {
194 return Vec::new();
195 }
196 catch_unwind(AssertUnwindSafe(|| self.source.needs(time))).unwrap_or_default()
197 }
198
199 fn resident_bytes(&self) -> usize {
200 catch_unwind(AssertUnwindSafe(|| self.source.resident_bytes())).unwrap_or_default()
201 }
202
203 fn reader(&self) -> Option<Box<dyn VisualSource>> {
206 if self.failed.is_some() {
207 return None;
208 }
209 let source = catch_unwind(AssertUnwindSafe(|| self.source.reader())).ok()??;
210 Some(Box::new(Self {
211 name: self.name,
212 source,
213 failed: None,
214 }))
215 }
216
217 fn shared_bytes(&self) -> usize {
218 catch_unwind(AssertUnwindSafe(|| self.source.shared_bytes())).unwrap_or_default()
219 }
220}
221
222fn contained<T>(
224 provider: &str,
225 call: impl FnOnce() -> Result<T, ExtensionError>,
226) -> Result<T, ExtensionError> {
227 catch_unwind(AssertUnwindSafe(call))
228 .unwrap_or_else(|payload| Err(panicked(provider, payload.as_ref())))
229}
230
231fn panicked(provider: &str, payload: &(dyn std::any::Any + Send)) -> ExtensionError {
232 let message = payload
233 .downcast_ref::<&str>()
234 .copied()
235 .or_else(|| payload.downcast_ref::<String>().map(String::as_str))
236 .unwrap_or("no message");
237 ExtensionError::Malformed(format!(
238 "the {provider} provider failed on this file (it panicked: {message})"
239 ))
240}
241
242fn provider_name(source: &ProviderSource) -> &'static str {
244 match source {
245 ProviderSource::Svg => "svg",
246 ProviderSource::AnimatedImage => "animated-image",
247 ProviderSource::Lottie { .. } => "lottie",
248 ProviderSource::Exr { .. } => "exr",
249 _ => "image-sequence",
250 }
251}