use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::Arc;
use alpha2_model::ProviderSource;
use alpha2_time::MediaTime;
use crate::api::{
ExtensionError, ProbeReport, SharedResolver, VisualFrame, VisualProvider, VisualSource,
};
#[derive(Default)]
pub struct Registry {
providers: Vec<Box<dyn VisualProvider>>,
}
impl Registry {
#[must_use]
pub fn with_defaults() -> Self {
#[allow(unused_mut)]
let mut registry = Self::default();
#[cfg(feature = "svg")]
registry.register(Box::new(crate::SvgProvider));
#[cfg(feature = "animated-image")]
registry.register(Box::new(crate::AnimatedImageProvider));
#[cfg(feature = "lottie")]
registry.register(Box::new(crate::LottieProvider));
#[cfg(feature = "exr")]
registry.register(Box::new(crate::ExrProvider));
registry
}
pub fn register(&mut self, provider: Box<dyn VisualProvider>) {
self.providers.push(provider);
}
#[must_use]
pub fn names(&self) -> Vec<&'static str> {
self.providers
.iter()
.map(|provider| provider.name())
.collect()
}
#[must_use]
pub fn probe(
&self,
bytes: &[u8],
) -> Option<Result<(&'static str, ProbeReport), ExtensionError>> {
self.providers.iter().find_map(|provider| {
let name = provider.name();
contained(name, || Ok(provider.probe(bytes)))
.unwrap_or_else(|error| Some(Err(error)))
.map(|report| report.map(|report| (name, report)))
})
}
#[must_use]
pub fn supports(&self, source: &ProviderSource) -> bool {
match source {
ProviderSource::ImageSequence { .. } => Self::reads_image_sequences(),
other => self.named(provider_name(other)).is_some(),
}
}
#[must_use]
pub const fn reads_image_sequences() -> bool {
cfg!(feature = "image-sequence")
}
#[must_use]
pub fn open_needs(&self, source: &ProviderSource) -> Vec<String> {
match source {
ProviderSource::ImageSequence { frames, .. } => {
frames.first().cloned().flatten().into_iter().collect()
}
_ => Vec::new(),
}
}
#[cfg_attr(not(feature = "image-sequence"), allow(clippy::needless_pass_by_value))]
pub fn open(
&self,
source: &ProviderSource,
bytes: Option<Arc<[u8]>>,
resolver: SharedResolver,
) -> Result<Box<dyn VisualSource>, ExtensionError> {
let name = provider_name(source);
let opened = match source {
#[cfg(feature = "image-sequence")]
ProviderSource::ImageSequence {
frames,
frame_rate,
missing,
primaries,
..
} => contained(name, || {
crate::open_sequence(frames, *frame_rate, *missing, *primaries, resolver)
})?,
other => {
let provider = self.named(name).ok_or_else(|| {
ExtensionError::Unsupported(format!("this build has no {name} provider"))
})?;
let bytes = bytes.ok_or_else(|| {
ExtensionError::MissingResource("the source's own bytes".into())
})?;
contained(name, || provider.open(bytes, other, &*resolver))?
}
};
Ok(Box::new(Contained {
name,
source: opened,
failed: None,
}))
}
fn named(&self, name: &str) -> Option<&dyn VisualProvider> {
self.providers
.iter()
.find(|provider| provider.name() == name)
.map(AsRef::as_ref)
}
}
struct Contained {
name: &'static str,
source: Box<dyn VisualSource>,
failed: Option<ExtensionError>,
}
impl VisualSource for Contained {
fn report(&self) -> &ProbeReport {
self.source.report()
}
fn render(&mut self, time: MediaTime, fit: (u32, u32)) -> Result<VisualFrame, ExtensionError> {
if let Some(error) = &self.failed {
return Err(error.clone());
}
let source = &mut self.source;
catch_unwind(AssertUnwindSafe(|| source.render(time, fit))).unwrap_or_else(|payload| {
let error = panicked(self.name, payload.as_ref());
self.failed = Some(error.clone());
Err(error)
})
}
fn needs(&self, time: MediaTime) -> Vec<String> {
if self.failed.is_some() {
return Vec::new();
}
catch_unwind(AssertUnwindSafe(|| self.source.needs(time))).unwrap_or_default()
}
fn resident_bytes(&self) -> usize {
catch_unwind(AssertUnwindSafe(|| self.source.resident_bytes())).unwrap_or_default()
}
fn reader(&self) -> Option<Box<dyn VisualSource>> {
if self.failed.is_some() {
return None;
}
let source = catch_unwind(AssertUnwindSafe(|| self.source.reader())).ok()??;
Some(Box::new(Self {
name: self.name,
source,
failed: None,
}))
}
fn shared_bytes(&self) -> usize {
catch_unwind(AssertUnwindSafe(|| self.source.shared_bytes())).unwrap_or_default()
}
}
fn contained<T>(
provider: &str,
call: impl FnOnce() -> Result<T, ExtensionError>,
) -> Result<T, ExtensionError> {
catch_unwind(AssertUnwindSafe(call))
.unwrap_or_else(|payload| Err(panicked(provider, payload.as_ref())))
}
fn panicked(provider: &str, payload: &(dyn std::any::Any + Send)) -> ExtensionError {
let message = payload
.downcast_ref::<&str>()
.copied()
.or_else(|| payload.downcast_ref::<String>().map(String::as_str))
.unwrap_or("no message");
ExtensionError::Malformed(format!(
"the {provider} provider failed on this file (it panicked: {message})"
))
}
fn provider_name(source: &ProviderSource) -> &'static str {
match source {
ProviderSource::Svg => "svg",
ProviderSource::AnimatedImage => "animated-image",
ProviderSource::Lottie { .. } => "lottie",
ProviderSource::Exr { .. } => "exr",
_ => "image-sequence",
}
}