use crate::{FFramesContext, Frame, Svgr, Video};
use serde::Serialize;
use std::panic::{AssertUnwindSafe, catch_unwind};
#[derive(Debug, Clone, Serialize)]
pub struct FramePanic {
pub frame: usize,
pub seconds: f32,
pub scenes: Vec<String>,
pub message: String,
}
impl std::fmt::Display for FramePanic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"render_frame panicked at frame {} ({:.3}s)",
self.frame, self.seconds
)?;
if !self.scenes.is_empty() {
write!(f, " in scene {}", self.scenes.join(" + "))?;
}
write!(f, ": {}", self.message)
}
}
pub fn render_frame_guarded<'a, TVideo: Video>(
video: &'a TVideo,
frame: Frame,
ctx: &FFramesContext<'a, '_>,
) -> Result<Svgr<'a>, FramePanic> {
let index = frame.global_index;
let fps = frame.fps.max(1);
catch_unwind(AssertUnwindSafe(|| video.render_frame(frame, ctx))).map_err(|payload| {
let message = payload
.downcast_ref::<&str>()
.map(|s| s.to_string())
.or_else(|| payload.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "unknown panic payload".to_owned());
crate::diagnostics::report(crate::diagnostics::Diagnostic::Panic {
message: message.clone(),
});
FramePanic {
frame: index,
seconds: index as f32 / fps as f32,
scenes: ctx
.scenes_at(index)
.map(|(_, name)| crate::short_scene_name(name).to_owned())
.collect(),
message,
}
})
}