mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-11 21:02:20 +00:00
Preserve frozen Object Mattes with pre-0.23 footage keys (#3430)
This commit is contained in:
1 parent
c30fb9aeed
commit
3694f07600
3 files changed
+67
-2
No files matched your search
@@ -219,7 +219,15 @@ fn fnv(s: &str) -> u64 {
|
||||
/// What the effect's input frames are made of: the layer's source and timing, its masks and the
|
||||
/// effects above the Roto Brush.
|
||||
pub fn input_key(p: &Project, comp: ItemId, layer: &Layer, uid: Uid) -> String {
|
||||
let mut s = crate::warp::signature(p, comp, layer);
|
||||
input_key_with_signature(crate::warp::signature(p, comp, layer), layer, uid)
|
||||
}
|
||||
|
||||
pub(crate) fn input_key_before_placed_page(p: &Project, comp: ItemId, layer: &Layer, uid: Uid) -> Option<String> {
|
||||
let signature = crate::warp::signature_before_placed_page(p, comp, layer)?;
|
||||
Some(input_key_with_signature(signature, layer, uid))
|
||||
}
|
||||
|
||||
fn input_key_with_signature(mut s: String, layer: &Layer, uid: Uid) -> String {
|
||||
if let Some(m) = layer.masks() {
|
||||
s.push_str(&format!("{m:?}"));
|
||||
}
|
||||
@@ -247,7 +255,9 @@ pub(crate) fn sync(before: &Project, after: &mut Project) -> Vec<(ItemId, LayerI
|
||||
for g in fxg.groups().filter(|g| is_roto(g) || is_object_matte(g)) {
|
||||
let key = input_key(after, *cid, l, g.uid);
|
||||
let old = before.comp(*cid).and_then(|bc| bc.layer(l.id)).and_then(|ol| ol.props.find_group(g.uid));
|
||||
if str_of(g, INPUT_KEY) != key {
|
||||
let saved_key = str_of(g, INPUT_KEY);
|
||||
// Keep a matching old key: the frozen cache itself also hashes that key.
|
||||
if saved_key != key && input_key_before_placed_page(after, *cid, l, g.uid).as_deref() != Some(saved_key.as_str()) {
|
||||
set.push((*cid, l.id, g.uid, key));
|
||||
todo.push((*cid, l.id, g.uid));
|
||||
} else if old.is_none_or(|o| guard_of(o) != guard_of(g)) {
|
||||
|
||||
@@ -119,6 +119,20 @@ pub fn signature(p: &Project, comp: ItemId, layer: &Layer) -> String {
|
||||
},
|
||||
_ => String::new(),
|
||||
};
|
||||
signature_with_source(p, comp, layer, &src)
|
||||
}
|
||||
|
||||
/// Before `Footage::placed_page` was added in 0.23, the persisted signature hashed the
|
||||
/// same Debug representation without that last field. Only ordinary footage can match it.
|
||||
pub(crate) fn signature_before_placed_page(p: &Project, comp: ItemId, layer: &Layer) -> Option<String> {
|
||||
let LayerSource::Footage { item } = &layer.source else { return None };
|
||||
let ItemKind::Footage(footage) = &p.item(*item)?.kind else { return None };
|
||||
let src = format!("{footage:?}");
|
||||
let old = src.strip_suffix(", placed_page: false }")?;
|
||||
Some(signature_with_source(p, comp, layer, &format!("{old} }}")))
|
||||
}
|
||||
|
||||
fn signature_with_source(p: &Project, comp: ItemId, layer: &Layer, src: &str) -> String {
|
||||
let rate = p.comp(comp).map(|c| format!("{:?}", c.frame_rate)).unwrap_or_default();
|
||||
let s = format!(
|
||||
"{:?}|{src}|{:?}|{:?}|{:?}|{}|{:?}|{rate}",
|
||||
|
||||
@@ -603,6 +603,47 @@ fn object_matte_background_job_cancel_and_freeze() {
|
||||
assert_eq!(status(&mut s)["frozen"], true);
|
||||
}
|
||||
|
||||
/// A queue edit preserves a frozen matte made before the footage signature gained placed_page.
|
||||
#[test]
|
||||
fn render_queue_keeps_frozen_object_matte_with_legacy_footage_key() {
|
||||
use effectcraft_effects::{object_matte, roto::{FROZEN, FrozenCache, INPUT_KEY}};
|
||||
use effectcraft_keyframe::Value::Str;
|
||||
use std::sync::Arc;
|
||||
|
||||
hold_roto_cache_test_lock();
|
||||
let (mut s, clip, _) = setup(|f| draw(center(f)));
|
||||
let cid = s.active_comp_id().unwrap();
|
||||
let r = s.execute_checked("objectMatte.apply", json!({"layer": clip.0, "point": center(0), "before": 0, "after": 2})).unwrap();
|
||||
let uid = r["effect"].as_u64().unwrap();
|
||||
s.execute_checked("objectMatte.freeze", json!({"layer": clip.0, "wait": true})).unwrap();
|
||||
|
||||
let layer = s.project.comp(cid).unwrap().layer(clip).unwrap();
|
||||
let legacy = crate::roto::input_key_before_placed_page(&s.project, cid, layer, uid).unwrap();
|
||||
assert_ne!(legacy, crate::roto::input_key(&s.project, cid, layer, uid));
|
||||
let fps = s.project.comp(cid).unwrap().frame_rate.as_f64();
|
||||
// Recreate the two keys serialized by 0.22, leaving the real frozen alpha data alone.
|
||||
let p = Arc::make_mut(&mut s.project);
|
||||
let g = p.comp_mut(cid).unwrap().layer_mut(clip).unwrap().props.find_group_mut(uid).unwrap();
|
||||
g.get_mut(INPUT_KEY).unwrap().value = Str(legacy.clone());
|
||||
let params = crate::roto::params_static(g);
|
||||
let Some(Str(value)) = params.get(FROZEN) else { panic!("missing frozen matte") };
|
||||
let mut frozen: FrozenCache = serde_json::from_str(value).unwrap();
|
||||
assert_eq!(frozen.frames.len(), 3);
|
||||
frozen.key = object_matte::frozen_key(¶ms, [f64::from(W), f64::from(H)], fps, None);
|
||||
let saved = Str(serde_json::to_string(&frozen).unwrap());
|
||||
g.get_mut(FROZEN).unwrap().value = saved.clone();
|
||||
|
||||
s.execute_checked("renderQueue.add", json!({"comp": cid.0, "format": "png", "output": "matte.png"})).unwrap();
|
||||
let params = |s: &Session| {
|
||||
crate::roto::params_static(s.project.comp(cid).unwrap().layer(clip).unwrap().props.find_group(uid).unwrap())
|
||||
};
|
||||
assert_eq!(params(&s).get(INPUT_KEY), Some(&Str(legacy)));
|
||||
assert_eq!(params(&s).get(FROZEN), Some(&saved));
|
||||
|
||||
s.execute_checked("layer.timing", json!({"layer": clip.0, "out": 0.5})).unwrap();
|
||||
assert_eq!(params(&s).get(FROZEN), Some(&Str(String::new())));
|
||||
}
|
||||
|
||||
/// Computed Object Matte segmentations go to the persistent disk cache: a second session on the
|
||||
/// same folder and project gets them back without segmenting (its frames cannot even be read
|
||||
/// here), and another prompt (other keys) finds nothing.
|
||||
|
||||
Reference in new issue
Block a user