#2679: effects.setParam refuses values outside the parameter's range (#2709)

Reject float effect parameter values outside the declared inclusive range without changing project state.
This commit is contained in:
skylar taylor-barrick
2026-10-22 22:52:46 -06:00
committed by GitHub
2 changed files with 31 additions and 0 deletions
+7
View File
@@ -3077,6 +3077,13 @@ fn build() -> Vec<CommandSpec> {
let (keyframes, v) = {
let prm = crate::masks::target_param(e, &pq, &pid).ok_or_else(|| bad("effects.setParam", format!("no param `{pid}`")))?;
let v = json_to_param(&prm.value, &val).ok_or_else(|| bad("effects.setParam", "value has the wrong type"))?;
// the parameter's hard range, which `effects.list` publishes: a value outside it
// (a factor where a percentage is meant, #2679) is refused, not saved
if let (Some(filmcraft_project::ParamKind::Float { min, max, .. }), ParamValue::Float(x)) = (def.map(|d| &d.kind), &v)
&& !(*min..=*max).contains(x)
{
return Err(bad("effects.setParam", format!("`{pid}` must be between {min} and {max}, got {x}")));
}
let keyframes = if keyframe {
// the stopwatch and the value in one step
prm.put_keyframe(mt, v.clone());
+24
View File
@@ -128,3 +128,27 @@ fn ultra_key_setting_writes_aggressive_parameters() {
assert_eq!(e.param("choke").unwrap().value.as_f64(), Some(10.0));
assert_eq!(e.param("contrast").unwrap().value.as_f64(), Some(10.0));
}
/// #2679: a value outside a parameter's range (the `min`/`max` `effects.list` publishes) is
/// refused with the range, not saved: gamma 1.1 meant as a factor turned the frame black.
#[test]
fn set_param_refuses_values_outside_the_range() {
let mut s = demo();
let clip = s.active_sequence().unwrap().video_tracks[0].items[0].id.0;
s.execute("effects.apply", json!({"clips": [clip], "effect": "levels"})).unwrap();
s.execute("effects.apply", json!({"clips": [clip], "effect": "lumetri"})).unwrap();
let before = s.project.to_json();
let undo = s.history.undo.len();
for (effect, param, value) in
[("levels", "gamma", 1.1), ("lumetri", "saturation", 500.0), ("lumetri", "vibrance", -300.0), ("opacity", "opacity", 250.0), ("motion", "scale", -50.0)]
{
let e = s.execute("effects.setParam", json!({"clip": clip, "effect": effect, "param": param, "value": value})).unwrap_err().to_string();
assert!(e.contains("must be between"), "{effect}.{param} = {value}: {e}");
}
assert_eq!(s.project.to_json(), before, "nothing was saved");
assert_eq!(s.history.undo.len(), undo);
// the ends of the range and values inside it still go in
s.execute("effects.setParam", json!({"clip": clip, "effect": "levels", "param": "gamma", "value": 110})).unwrap();
s.execute("effects.setParam", json!({"clip": clip, "effect": "opacity", "param": "opacity", "value": 100})).unwrap();
s.execute("effects.setParam", json!({"clip": clip, "effect": "opacity", "param": "opacity", "value": 0})).unwrap();
}