diff --git a/crates/engine/src/commands/comp.rs b/crates/engine/src/commands/comp.rs index 57bd4855..8114cb73 100644 --- a/crates/engine/src/commands/comp.rs +++ b/crates/engine/src/commands/comp.rs @@ -12,6 +12,14 @@ pub(crate) fn rate_p(p: &Value) -> Option { f_p(p, "frameRate").or(f_p(p, "fps")).map(FrameRate::from_f64) } +/// A zero, negative or tiny rate would make a zero-length frame (and divide by zero later). +fn check_rate(p: &Value, cmd: &str) -> Result<()> { + match f_p(p, "frameRate").or(f_p(p, "fps")) { + Some(f) if !(0.001..=1000.0).contains(&f) => Err(bad(cmd, "frameRate: fps from 0.001 to 1000")), + _ => Ok(()), + } +} + fn apply_settings(c: &mut Comp, p: &Value) { let (ow, oh) = (c.width, c.height); if let Some(w) = p.get("width").and_then(Value::as_u64) { @@ -99,6 +107,7 @@ fn apply_settings(c: &mut Comp, p: &Value) { } fn new_comp(s: &mut Session, p: &Value) -> Result { + check_rate(p, "comp.new")?; let name = str_p(p, "name").unwrap_or("Comp 1").to_string(); let rate = rate_p(p).unwrap_or(FrameRate::FPS_29_97); let mut c = Comp::new(1920, 1080, rate, rate.snap_nearest(Tick::from_seconds_f64(10.0))); @@ -125,6 +134,7 @@ fn new_comp(s: &mut Session, p: &Value) -> Result { } fn settings(s: &mut Session, p: &Value) -> Result { + check_rate(p, "comp.settings")?; let cid = comp_id(s, p)?; s.edit("Composition Settings", None, |proj, _| { if let Some(n) = str_p(p, "name") diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index c18d957e..a0acbd1a 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -301,6 +301,17 @@ fn comp_settings_anchor_start_timecode_renderer() { assert_eq!(c.renderer, crate::project::Renderer::Advanced3D); } +#[test] +fn comp_rejects_zero_and_negative_frame_rates() { + let mut s = Session::default(); + for fps in [0.0, -30.0, 0.0001] { + assert!(s.execute("comp.new", json!({"frameRate": fps})).is_err(), "{fps}"); + } + s.execute("comp.new", json!({"frameRate": 30, "duration": 1})).unwrap(); + assert!(s.execute("comp.settings", json!({"frameRate": 0})).is_err()); + assert_eq!(s.active_comp().unwrap().frame_rate, crate::time::FrameRate::FPS_30); +} + #[test] fn render_queue_add_accepts_documented_settings_when_checked() { let mut s = demo();