From 6780c65671f70ca0828a9b584b0c33695927e910 Mon Sep 17 00:00:00 2001 From: arnav-hiwarkar Date: Thu, 12 Nov 2026 10:44:57 +0530 Subject: [PATCH 1/4] #3456: write the footage in analysis signatures field by field The Warp Stabilizer, 3D Camera Tracker and Roto Brush signatures hashed the footage item's derived Debug text, so every new Footage field changed every signature and dropped every saved analysis (placed_page did in 0.23). Write the same text from a named list of fields instead: a new field no longer compiles until it is decided there. Pin the text in a test. --- crates/engine/src/analysis/warp.rs | 97 ++++++++++++++++++- crates/engine/src/tests.rs | 1 + .../engine/src/tests/legacy_analysis_keys.rs | 22 +++++ 3 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 crates/engine/src/tests/legacy_analysis_keys.rs diff --git a/crates/engine/src/analysis/warp.rs b/crates/engine/src/analysis/warp.rs index 0ab576d2c..b88d9df38 100644 --- a/crates/engine/src/analysis/warp.rs +++ b/crates/engine/src/analysis/warp.rs @@ -17,7 +17,7 @@ use std::sync::{Arc, Mutex}; use effectcraft_effects::warp_stab::{self, ANALYSIS, ANALYSIS_KEY}; use effectcraft_keyframe::Value; -use effectcraft_project::{GroupKind, ItemId, ItemKind, Layer, LayerId, LayerSource, Project, PropGroup, Uid}; +use effectcraft_project::{Footage, GroupKind, ItemId, ItemKind, Layer, LayerId, LayerSource, Project, PropGroup, Uid}; use effectcraft_raster::Image; use effectcraft_render::{EvalCtx, ExprHost, FootageSource, LayerCache, Renderer}; use effectcraft_time::Tick; @@ -112,7 +112,7 @@ fn fnv(s: &str) -> u64 { pub fn signature(p: &Project, comp: ItemId, layer: &Layer) -> String { let src = match &layer.source { LayerSource::Footage { item } | LayerSource::Solid { item } | LayerSource::Comp { item } => match p.item(*item).map(|i| &i.kind) { - Some(ItemKind::Footage(f)) => format!("{f:?}"), + Some(ItemKind::Footage(f)) => footage_text(f, true), Some(ItemKind::Solid(s)) => format!("{s:?}"), Some(ItemKind::Comp(c)) => format!("{}x{}@{:?}/{:?}", c.width, c.height, c.frame_rate, c.duration), _ => String::new(), @@ -127,9 +127,96 @@ pub fn signature(p: &Project, comp: ItemId, layer: &Layer) -> String { pub(crate) fn signature_before_placed_page(p: &Project, comp: ItemId, layer: &Layer) -> Option { 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} }}"))) + if footage.placed_page { + return None; + } + Some(signature_with_source(p, comp, layer, &footage_text(footage, false))) +} + +/// A footage item in a signature: its fields as `Debug` wrote them in 0.23, the text saved +/// analysis keys were hashed from (without `placed_page` before 0.23). It is written out here so +/// that a new `Footage` field doesn't change every signature and drop every saved analysis. +pub(crate) fn footage_text(f: &Footage, placed_page: bool) -> String { + format!("{:?}", FootageText { f, placed_page }) +} + +struct FootageText<'a> { + f: &'a Footage, + placed_page: bool, +} + +impl std::fmt::Debug for FootageText<'_> { + fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Every field is named, so a new one doesn't compile until it is decided here. A new field + // that changes the frames goes last, written only when it isn't its default, so keys saved + // before it still match. A new field that doesn't change the frames stays out. + let Footage { + path, + kind, + width, + height, + pixel_aspect, + frame_rate, + native_rate, + duration, + has_video, + has_audio, + alpha, + premul_color, + loop_count, + codec, + missing, + sequence, + color_profile, + layer, + fields, + invert_alpha, + linear_light, + preserve_rgb, + cineon, + data, + page, + alphabetical, + start_timecode, + file_timecode, + markers, + placed_page, + } = self.f; + let mut d = out.debug_struct("Footage"); + d.field("path", path) + .field("kind", kind) + .field("width", width) + .field("height", height) + .field("pixel_aspect", pixel_aspect) + .field("frame_rate", frame_rate) + .field("native_rate", native_rate) + .field("duration", duration) + .field("has_video", has_video) + .field("has_audio", has_audio) + .field("alpha", alpha) + .field("premul_color", premul_color) + .field("loop_count", loop_count) + .field("codec", codec) + .field("missing", missing) + .field("sequence", sequence) + .field("color_profile", color_profile) + .field("layer", layer) + .field("fields", fields) + .field("invert_alpha", invert_alpha) + .field("linear_light", linear_light) + .field("preserve_rgb", preserve_rgb) + .field("cineon", cineon) + .field("data", data) + .field("page", page) + .field("alphabetical", alphabetical) + .field("start_timecode", start_timecode) + .field("file_timecode", file_timecode) + .field("markers", markers); + if self.placed_page { + d.field("placed_page", placed_page); + } + d.finish() + } } fn signature_with_source(p: &Project, comp: ItemId, layer: &Layer, src: &str) -> String { diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 19953053e..8d33ee3d4 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -27,6 +27,7 @@ mod key_metadata_order; mod key_time; mod keyframe_text; mod keylight; +mod legacy_analysis_keys; mod limits; mod lottie; mod m137; diff --git a/crates/engine/src/tests/legacy_analysis_keys.rs b/crates/engine/src/tests/legacy_analysis_keys.rs new file mode 100644 index 000000000..f4e442050 --- /dev/null +++ b/crates/engine/src/tests/legacy_analysis_keys.rs @@ -0,0 +1,22 @@ +//! Warp Stabilizer and 3D Camera Tracker analyses saved by an earlier release keep matching +//! their footage after an edit that doesn't change it (#3456), and a real change still clears them. + +/// The footage text analysis keys are hashed from, as 0.23 saved it. A new `Footage` field must +/// not change it (or every saved analysis is dropped again). +#[test] +fn footage_signature_text_is_pinned() { + use effectcraft_project::{Footage, FootageKind}; + let f = Footage { + path: "clip.mp4".into(), + kind: FootageKind::Video, + width: 160, + height: 120, + invert_alpha: true, + page: 2, + placed_page: true, + ..Footage::default() + }; + let fields = r#"path: "clip.mp4", kind: Video, width: 160, height: 120, pixel_aspect: 1.0, frame_rate: FrameRate { num: 30, den: 1 }, native_rate: None, duration: Tick(0), has_video: false, has_audio: false, alpha: Straight, premul_color: [0.0, 0.0, 0.0], loop_count: 1, codec: "", missing: false, sequence: [], color_profile: None, layer: None, fields: Off, invert_alpha: true, linear_light: false, preserve_rgb: false, cineon: CineonSettings { log_conversion: false, converted_black: 0.0, converted_white: 1023.0, black: 95.0, white: 685.0, gamma: 1.7, rolloff: 20.0, units: TenBit }, data: None, page: 2, alphabetical: false, start_timecode: None, file_timecode: None, markers: []"#; + assert_eq!(crate::warp::footage_text(&f, true), format!("Footage {{ {fields}, placed_page: true }}")); + assert_eq!(crate::warp::footage_text(&f, false), format!("Footage {{ {fields} }}")); +} From cd08781aab3210103793549b3a4565d1aa7a63d7 Mon Sep 17 00:00:00 2001 From: arnav-hiwarkar Date: Thu, 12 Nov 2026 10:56:39 +0530 Subject: [PATCH 2/4] #3456: keep Warp Stabilizer and 3D Camera Tracker analyses saved before 0.23 Their keys were compared only with today's signature, so the first edit of a project saved by 0.22 (even adding it to the render queue, as effectcraft-cli render does) cleared both analyses and queued them again, and lost them when the footage was offline. One check, warp::key_matches, now accepts a key made from today's signature or from the one 0.22 wrote for the same frames, and keeps it. Warp Stabilizer, the 3D Camera Tracker and Roto Brush all use it. The regression test opens a project saved by the released 0.22.0 CLI. --- ATTRIBUTION.md | 1 + crates/engine/src/analysis/camera_track.rs | 13 +- crates/engine/src/analysis/roto.rs | 8 +- crates/engine/src/analysis/warp.rs | 14 +- .../src/tests/fixtures/tracked-0.22.ecproj | 1593 +++++++++++++++++ .../fixtures/tracked-0.22.ecproj.attribution | 8 + .../engine/src/tests/legacy_analysis_keys.rs | 76 + 7 files changed, 1700 insertions(+), 13 deletions(-) create mode 100644 crates/engine/src/tests/fixtures/tracked-0.22.ecproj create mode 100644 crates/engine/src/tests/fixtures/tracked-0.22.ecproj.attribution diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index b463e79ea..3aa429a53 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -134,6 +134,7 @@ configuration from these Apache-2.0 projects: | Asset | Author | Source | Licence | |---|---|---|---| | `crates/engine/src/tests/fixtures/object-matte-0.22.ecproj` | lisn0 | Original work, synthetic project saved by EffectCraft 0.22.0 | MIT OR Apache-2.0 | +| `crates/engine/src/tests/fixtures/tracked-0.22.ecproj` | arnav-hiwarkar | Original work, synthetic project saved by EffectCraft 0.22.0 | MIT OR Apache-2.0 | ## First-party brand marks diff --git a/crates/engine/src/analysis/camera_track.rs b/crates/engine/src/analysis/camera_track.rs index 29961f533..d6a79b791 100644 --- a/crates/engine/src/analysis/camera_track.rs +++ b/crates/engine/src/analysis/camera_track.rs @@ -124,8 +124,17 @@ fn str_of(g: &PropGroup, m: &str) -> String { /// What a tracker's tracks are made from: the layer's frames and Detailed Analysis. fn tracks_key(project: &Project, cid: ItemId, l: &Layer, g: &PropGroup) -> String { + tracks_key_of(signature(project, cid, l), g) +} + +fn tracks_key_of(signature: String, g: &PropGroup) -> String { let detailed = effectcraft_effects::warp_stab::param(&static_params(g), "advanced/detailedAnalysis").is_some_and(Value::as_bool); - format!("{}{}", signature(project, cid, l), if detailed { "+detailed" } else { "" }) + format!("{signature}{}", if detailed { "+detailed" } else { "" }) +} + +/// Whether the tracker's saved tracks are made from the layer's current frames and settings. +fn tracks_match(project: &Project, cid: ItemId, l: &Layer, g: &PropGroup) -> bool { + crate::warp::key_matches(project, cid, l, &str_of(g, TRACKS_KEY), |s| tracks_key_of(s, g)) } /// Parameters of an instance from static values (the tracker's settings are not animated). @@ -165,7 +174,7 @@ pub(crate) fn invalidate(before: &Project, after: &mut Project) -> Vec<(ItemId, let old_layer = before.comp(*cid).and_then(|bc| bc.layer(l.id)); for g in fx.groups().filter(|g| is_tracker(g)) { let tracked = !str_of(g, TRACKS).is_empty(); - if tracked && str_of(g, TRACKS_KEY) != tracks_key(after, *cid, l, g) { + if tracked && !tracks_match(after, *cid, l, g) { clears.push((*cid, l.id, g.uid, true)); todo.push((*cid, l.id, g.uid)); } else if tracked && str_of(g, SOLVE_KEY) != ct::solve_key(&ct::settings(&static_params(g))) { diff --git a/crates/engine/src/analysis/roto.rs b/crates/engine/src/analysis/roto.rs index 246c8899c..89d3e1609 100644 --- a/crates/engine/src/analysis/roto.rs +++ b/crates/engine/src/analysis/roto.rs @@ -222,12 +222,6 @@ pub fn input_key(p: &Project, comp: ItemId, layer: &Layer, uid: Uid) -> String { input_key_with_signature(crate::warp::signature(p, comp, layer), layer, uid) } -/// Recognize the input key serialized before 0.23 without re-keying its frozen matte. -fn input_key_before_placed_page(p: &Project, comp: ItemId, layer: &Layer, uid: Uid) -> Option { - 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:?}")); @@ -258,7 +252,7 @@ pub(crate) fn sync(before: &Project, after: &mut Project) -> Vec<(ItemId, LayerI let old = before.comp(*cid).and_then(|bc| bc.layer(l.id)).and_then(|ol| ol.props.find_group(g.uid)); 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()) { + if !crate::warp::key_matches(after, *cid, l, &saved_key, |s| input_key_with_signature(s, l, g.uid)) { 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)) { diff --git a/crates/engine/src/analysis/warp.rs b/crates/engine/src/analysis/warp.rs index b88d9df38..20b88d45d 100644 --- a/crates/engine/src/analysis/warp.rs +++ b/crates/engine/src/analysis/warp.rs @@ -122,9 +122,16 @@ pub fn signature(p: &Project, comp: ItemId, layer: &Layer) -> String { signature_with_source(p, comp, layer, &src) } +/// Whether `saved`, a key an analysis of `layer` was saved with, is for the layer's current frames. +/// `key` makes such a key from a signature. A key made from the signature an earlier release wrote +/// for the same frames matches too: keep it rather than rewrite it (analysing writes today's). +pub(crate) fn key_matches(p: &Project, comp: ItemId, layer: &Layer, saved: &str, key: impl Fn(String) -> String) -> bool { + key(signature(p, comp, layer)) == saved || signature_before_placed_page(p, comp, layer).is_some_and(|s| key(s) == saved) +} + /// 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 { +/// same footage text without that last field. Only ordinary footage can match it. +fn signature_before_placed_page(p: &Project, comp: ItemId, layer: &Layer) -> Option { let LayerSource::Footage { item } = &layer.source else { return None }; let ItemKind::Footage(footage) = &p.item(*item)?.kind else { return None }; if footage.placed_page { @@ -256,11 +263,10 @@ pub(crate) fn invalidate(before: &Project, after: &mut Project) -> Vec<(ItemId, if !fx.groups().any(is_warp) { continue; } - let sig = signature(after, *cid, l); let old_layer = before.comp(*cid).and_then(|bc| bc.layer(l.id)); for g in fx.groups().filter(|g| is_warp(g)) { let analysed = !str_of(g, ANALYSIS).is_empty(); - if analysed && str_of(g, ANALYSIS_KEY) != sig { + if analysed && !key_matches(after, *cid, l, &str_of(g, ANALYSIS_KEY), |s| s) { clear.push((*cid, l.id, g.uid)); todo.push((*cid, l.id, g.uid)); } else if !analysed && old_layer.and_then(|ol| ol.props.find_group(g.uid)).is_none() { diff --git a/crates/engine/src/tests/fixtures/tracked-0.22.ecproj b/crates/engine/src/tests/fixtures/tracked-0.22.ecproj new file mode 100644 index 000000000..8e1d21b40 --- /dev/null +++ b/crates/engine/src/tests/fixtures/tracked-0.22.ecproj @@ -0,0 +1,1593 @@ +{ + "savedBy": "0.22.0", + "activeComp": 2, + "openComps": [ + 2 + ], + "schema": 2, + "settings": { + "bit_depth": "Bpc8", + "linearize": false, + "working_space": null, + "blend_linear": false, + "time_display": "Timecode", + "frame_start": 0, + "audio_sample_rate": 48000, + "gpu_acceleration": true, + "color_engine": "Adobe", + "hdr": "Clip", + "output_space": null, + "working_gamma": 2.2 + }, + "items": { + "1": { + "id": 1, + "name": "effectcraft-3456-shake.mp4", + "label": "Aqua", + "comment": "", + "parent": null, + "kind": { + "type": "Footage", + "path": "/tmp/effectcraft-3456-shake.mp4", + "kind": "Video", + "width": 160, + "height": 120, + "pixel_aspect": 1.0, + "frame_rate": { + "num": 12, + "den": 1 + }, + "native_rate": null, + "duration": 254016000000, + "has_video": true, + "has_audio": false, + "alpha": "Ignore", + "premul_color": [ + 0.0, + 0.0, + 0.0 + ], + "loop_count": 1, + "codec": "H.264 (High)", + "missing": false, + "color_profile": "Rec709" + } + }, + "2": { + "id": 2, + "name": "effectcraft-3456-shake", + "label": "Sandstone", + "comment": "", + "parent": null, + "kind": { + "type": "Comp", + "width": 160, + "height": 120, + "pixel_aspect": 1.0, + "frame_rate": { + "num": 12, + "den": 1 + }, + "duration": 254016000000, + "display_start": 0, + "background": [ + 0.0, + 0.0, + 0.0 + ], + "work_area": [ + 0, + 254016000000 + ], + "layers": [ + { + "id": 3, + "name": "effectcraft-3456-shake.mp4", + "source": { + "type": "Footage", + "item": 1 + }, + "label": "Aqua", + "comment": "", + "start_time": 0, + "in_point": 0, + "out_point": 254016000000, + "stretch": 100.0, + "switches": { + "video": true, + "audio": true, + "solo": false, + "locked": false, + "shy": false, + "collapse": false, + "quality": "Best", + "sampling": "Bilinear", + "effects": true, + "frame_blend": "Off", + "motion_blur": false, + "adjustment": false, + "three_d": false, + "guide": false + }, + "blend_mode": "Normal", + "preserve_transparency": false, + "track_matte": null, + "parent": null, + "markers": [], + "auto_orient": "Off", + "props": { + "uid": 4, + "match": "layer", + "name": "effectcraft-3456-shake.mp4", + "kind": { + "kind": "Plain" + }, + "enabled": true, + "children": [ + { + "node": "Group", + "uid": 5, + "match": "masks", + "name": "Masks", + "kind": { + "kind": "Plain" + }, + "enabled": true, + "children": [] + }, + { + "node": "Group", + "uid": 6, + "match": "effects", + "name": "Effects", + "kind": { + "kind": "Plain" + }, + "enabled": true, + "children": [ + { + "node": "Group", + "uid": 26, + "match": "ec.distort.warpstabilizer", + "name": "Warp Stabilizer", + "kind": { + "kind": "Effect", + "effect": "ec.distort.warpstabilizer" + }, + "enabled": true, + "children": [ + { + "node": "Group", + "uid": 28, + "match": "stabilization", + "name": "Stabilization", + "kind": { + "kind": "Plain" + }, + "enabled": true, + "children": [ + { + "node": "Prop", + "uid": 27, + "match": "result", + "name": "Result", + "value": { + "t": "Enum", + "v": 0 + }, + "ui": { + "kind": "Popup", + "options": [ + "Smooth Motion", + "No Motion" + ] + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 29, + "match": "smoothness", + "name": "Smoothness", + "value": { + "t": "Scalar", + "v": 50.0 + }, + "ui": { + "kind": "Slider", + "min": 0.0, + "max": 1000.0, + "slider_min": 0.0, + "slider_max": 100.0, + "decimals": 0 + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 30, + "match": "method", + "name": "Method", + "value": { + "t": "Enum", + "v": 3 + }, + "ui": { + "kind": "Popup", + "options": [ + "Position", + "Position, Scale, Rotation", + "Perspective", + "Subspace Warp" + ] + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 31, + "match": "preserveScale", + "name": "Preserve Scale", + "value": { + "t": "Bool", + "v": false + }, + "ui": { + "kind": "Checkbox" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + } + ] + }, + { + "node": "Group", + "uid": 33, + "match": "borders", + "name": "Borders", + "kind": { + "kind": "Plain" + }, + "enabled": true, + "children": [ + { + "node": "Prop", + "uid": 32, + "match": "framing", + "name": "Framing", + "value": { + "t": "Enum", + "v": 2 + }, + "ui": { + "kind": "Popup", + "options": [ + "Stabilize Only", + "Stabilize, Crop", + "Stabilize, Crop, Auto-scale", + "Stabilize, Synthesize Edges" + ] + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Group", + "uid": 35, + "match": "autoScale", + "name": "Auto-scale", + "kind": { + "kind": "Plain" + }, + "enabled": true, + "children": [ + { + "node": "Prop", + "uid": 34, + "match": "maximumScale", + "name": "Maximum Scale", + "value": { + "t": "Scalar", + "v": 150.0 + }, + "ui": { + "kind": "Slider", + "min": 100.0, + "max": 1000.0, + "slider_min": 100.0, + "slider_max": 200.0, + "decimals": 0 + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 36, + "match": "actionSafeMargin", + "name": "Action-safe Margin", + "value": { + "t": "Scalar", + "v": 0.0 + }, + "ui": { + "kind": "Slider", + "min": 0.0, + "max": 45.0, + "slider_min": 0.0, + "slider_max": 20.0, + "decimals": 0 + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + } + ] + }, + { + "node": "Prop", + "uid": 37, + "match": "additionalScale", + "name": "Additional Scale", + "value": { + "t": "Scalar", + "v": 100.0 + }, + "ui": { + "kind": "Slider", + "min": 1.0, + "max": 1000.0, + "slider_min": 50.0, + "slider_max": 150.0, + "decimals": 0 + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + } + ] + }, + { + "node": "Group", + "uid": 39, + "match": "advanced", + "name": "Advanced", + "kind": { + "kind": "Plain" + }, + "enabled": true, + "children": [ + { + "node": "Prop", + "uid": 38, + "match": "detailedAnalysis", + "name": "Detailed Analysis", + "value": { + "t": "Bool", + "v": false + }, + "ui": { + "kind": "Checkbox" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 40, + "match": "rollingShutterRipple", + "name": "Rolling Shutter Ripple", + "value": { + "t": "Enum", + "v": 0 + }, + "ui": { + "kind": "Popup", + "options": [ + "Automatic Reduction", + "Enhanced Reduction" + ] + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 41, + "match": "cropLessSmoothMore", + "name": "Crop Less <-> Smooth More", + "value": { + "t": "Scalar", + "v": 50.0 + }, + "ui": { + "kind": "Slider", + "min": 0.0, + "max": 100.0, + "slider_min": 0.0, + "slider_max": 100.0, + "decimals": 0 + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 42, + "match": "synthesizeInputRange", + "name": "Synthesis Input Range (seconds)", + "value": { + "t": "Scalar", + "v": 1.0 + }, + "ui": { + "kind": "Slider", + "min": 0.0, + "max": 10.0, + "slider_min": 0.0, + "slider_max": 5.0, + "decimals": 2 + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 43, + "match": "synthesizeEdgeFeather", + "name": "Synthesis Edge Feather", + "value": { + "t": "Scalar", + "v": 0.0 + }, + "ui": { + "kind": "Slider", + "min": 0.0, + "max": 100.0, + "slider_min": 0.0, + "slider_max": 100.0, + "decimals": 0 + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Group", + "uid": 45, + "match": "edgeCropping", + "name": "Synthesis Edge Cropping", + "kind": { + "kind": "Plain" + }, + "enabled": true, + "children": [ + { + "node": "Prop", + "uid": 44, + "match": "left", + "name": "Left", + "value": { + "t": "Scalar", + "v": 0.0 + }, + "ui": { + "kind": "Slider", + "min": 0.0, + "max": 1000.0, + "slider_min": 0.0, + "slider_max": 50.0, + "decimals": 0 + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 46, + "match": "top", + "name": "Top", + "value": { + "t": "Scalar", + "v": 0.0 + }, + "ui": { + "kind": "Slider", + "min": 0.0, + "max": 1000.0, + "slider_min": 0.0, + "slider_max": 50.0, + "decimals": 0 + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 47, + "match": "right", + "name": "Right", + "value": { + "t": "Scalar", + "v": 0.0 + }, + "ui": { + "kind": "Slider", + "min": 0.0, + "max": 1000.0, + "slider_min": 0.0, + "slider_max": 50.0, + "decimals": 0 + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 48, + "match": "bottom", + "name": "Bottom", + "value": { + "t": "Scalar", + "v": 0.0 + }, + "ui": { + "kind": "Slider", + "min": 0.0, + "max": 1000.0, + "slider_min": 0.0, + "slider_max": 50.0, + "decimals": 0 + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + } + ] + }, + { + "node": "Prop", + "uid": 49, + "match": "hideWarningBanner", + "name": "Hide Warning Banner", + "value": { + "t": "Bool", + "v": false + }, + "ui": { + "kind": "Checkbox" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 50, + "match": "showTrackPoints", + "name": "Show Track Points", + "value": { + "t": "Bool", + "v": false + }, + "ui": { + "kind": "Checkbox" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 51, + "match": "trackPointSize", + "name": "Track Point Size", + "value": { + "t": "Scalar", + "v": 100.0 + }, + "ui": { + "kind": "Slider", + "min": 0.0, + "max": 1000.0, + "slider_min": 0.0, + "slider_max": 200.0, + "decimals": 0 + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + } + ] + }, + { + "node": "Prop", + "uid": 52, + "match": "analysis", + "name": "Analysis", + "value": { + "t": "Str", + "v": "{\"version\":2,\"start\":0.0,\"frameDuration\":0.08333333333333333,\"size\":[160.0,120.0],\"detailed\":false,\"frames\":[{\"t\":[0.0,0.0],\"s\":[1.0,0.0,0.0,0.0],\"h\":[1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0],\"inliers\":0,\"features\":0},{\"t\":[-22.388798410890686,30.01868418985143],\"s\":[0.9277781961008736,-0.04422418749279422,-15.523833932787909,35.6091856999425],\"h\":[0.9277781961008736,0.04422418749279422,-15.523833932787909,-0.04422418749279422,0.9277781961008736,35.6091856999425,0.0,0.0],\"inliers\":9,\"features\":15,\"points\":[[92.3,107.9],[95.2,95.7],[92.2,103.7],[74.2,53.6],[74.3,37.4],[99.3,90.7],[82.6,41.5],[82.5,55.5],[88.5,43.5]]},{\"t\":[23.98122532957055,-1.9084399677775892],\"s\":[0.9063620953625465,-0.04409531016401132,30.797313797392007,5.599646102270199],\"h\":[0.7965841302132126,0.29995334448297317,20.168498543438005,-0.0740592055495628,1.046481410433727,-0.473260319309297,-0.002014895402579317,0.0023197947752658875],\"inliers\":10,\"features\":24,\"points\":[[118.4,97.1],[124.8,102.9],[100.5,51.5],[127.3,63.9],[127.5,45.0],[106.1,39.6],[106.2,53.7],[113.7,56.7],[52.5,35.6],[116.8,43.6]]},{\"t\":[11.687620662467452,-7.932031814223215],\"s\":[0.9951636472613027,0.0038103578382923703,12.438092774450126,-8.118641076572445],\"h\":[1.643484923435082,-0.7137632860918511,-2.5905208024072124,0.3548180400361871,0.46037059943190983,-10.538467107984474,0.004219805542554225,-0.0059212698862967],\"inliers\":9,\"features\":18,\"points\":[[132.6,73.5],[131.5,85.1],[106.5,41.5],[138.4,70.5],[143.4,74.0],[112.0,43.6],[122.5,47.5],[130.2,49.8],[149.3,58.5]]},{\"t\":[-30.310403914516613,33.907848152443364],\"s\":[1.007168616747288,0.0037069147029014813,-31.056396618707396,33.11324659331262],\"h\":[0.6198845159470996,0.0005618559126509149,-3.178267007922358,-0.13009530252511717,0.7726770220834841,43.55009577470153,-0.001720398359559105,0.00004737533435711085],\"inliers\":6,\"features\":6,\"points\":[[83.4,78.4],[111.5,91.5],[111.5,72.5],[89.0,80.3],[94.7,83.3],[92.1,67.5]]},{\"t\":[-5.583259809628714,-21.883441966142215],\"s\":[1.0031742036915483,-0.0014807972400717364,-6.0689262496146625,-22.023221544860448],\"h\":[1.1420296883900174,-0.04739597390990014,-10.605302345116598,0.09741208494601904,0.9834838974219691,-26.601790074159226,0.0009113645792746124,-0.0004613682942006241],\"inliers\":12,\"features\":26,\"points\":[[44.4,0.4],[95.7,91.4],[102.5,90.5],[96.9,82.3],[102.8,85.5],[102.6,81.2],[97.1,78.5],[127.8,90.7],[128.5,80.1],[108.6,91.2],[108.5,79.5],[105.0,77.7]]},{\"t\":[27.686733462643232,8.049769635251877],\"s\":[0.929460928303922,0.035973851378530715,35.6798165298027,9.23027450987749],\"h\":[1.008918148822829,0.1261308536278953,23.55178846805191,-0.030309790827892834,1.108836841709476,6.4896286706810855,-0.0005830225271129425,0.0013957761568033347],\"inliers\":9,\"features\":24,\"points\":[[55.5,112.5],[100.6,55.5],[100.6,69.5],[106.2,57.5],[106.1,71.6],[113.5,74.5],[76.4,61.6],[76.6,45.5],[116.6,61.6]]},{\"t\":[-2.1632428252979565,18.033956762750776],\"s\":[0.9823494564274133,-0.03858067520697906,-3.292485287213367,25.146037618301847],\"h\":[0.9579598760613574,-0.13114734456280325,2.830816183351523,0.009276438160611545,0.8429238585075404,27.816268384517308,0.00010866570427004524,-0.0010986404477924933],\"inliers\":9,\"features\":25,\"points\":[[10.2,63.8],[4.5,45.5],[53.4,60.9],[106.6,83.4],[106.6,97.6],[58.5,80.5],[71.6,85.4],[76.7,86.4],[112.4,85.5]]},{\"t\":[-30.66640840418232,-21.989852679037867],\"s\":[1.0156367307051672,0.004551071962152525,-31.763785052482092,-23.691591888755113],\"h\":[1.0156367307051672,-0.004551071962152525,-31.763785052482092,0.004551071962152525,1.0156367307051672,-23.691591888755113,0.0,0.0],\"inliers\":6,\"features\":9,\"points\":[[65.6,55.5],[68.5,58.4],[76.3,61.6],[58.5,55.9],[78.5,77.7],[43.4,48.3]]},{\"t\":[5.708740901092206,24.075281194659254],\"s\":[1.0135826886534363,-0.10515588147251018,-1.1163514746928627,27.59825643107368],\"h\":[1.1505001196625904,-0.004492526279963958,-0.5319258967829654,-0.025738957694540945,0.966887539263372,28.15190307627249,0.0007845902586792284,-0.00031935492918067597],\"inliers\":9,\"features\":24,\"points\":[[104.6,97.7],[104.8,101.6],[108.5,88.8],[103.9,89.0],[116.1,92.7],[112.4,87.8],[113.2,96.3],[47.3,72.5],[46.4,87.5]]},{\"t\":[27.779201319161004,9.926771642811133],\"s\":[0.9890321818477387,0.0013711446087583204,28.575284295787874,10.772618168795788],\"h\":[1.061498205619746,0.009950102807178602,26.602191497678987,0.03562744518371215,1.0470570180393335,7.3128067380884545,0.00040746752749711983,0.00013875206582740715],\"inliers\":19,\"features\":31,\"points\":[[101.5,91.5],[100.5,107.5],[95.4,104.6],[93.2,89.4],[62.5,94.4],[108.2,95.5],[108.2,109.3],[114.0,97.5],[89.2,102.5],[61.5,75.5],[70.5,80.5],[89.3,88.3],[114.9,111.1],[69.5,95.5],[66.5,78.5],[82.3,100.6],[76.5,82.5],[82.5,84.4],[119.1,112.0]]},{\"t\":[-18.08353148330616,-15.925984647541327],\"s\":[0.9665330472023047,-0.007772653084006622,-14.505725243709165,-11.14686987633948],\"h\":[1.396312496581232,-0.10268053165320767,-33.15101241390654,0.021275151660258673,1.173514724589782,-24.185902320422414,0.001847437675107323,-0.0003034312788189646],\"inliers\":9,\"features\":23,\"points\":[[119.3,75.5],[116.5,84.0],[121.6,82.9],[128.5,87.4],[127.6,83.0],[124.8,80.6],[128.7,71.5],[71.6,86.5],[58.4,66.5]]}],\"tracks\":[{\"id\":0,\"start\":0,\"pts\":[[121.5,16.5],[101.4,48.2],[127.2,46.6],[141.2,40.2],[111.2,74.3],[105.1,58.7],[135.1,68.5]]},{\"id\":1,\"start\":0,\"pts\":[[107.5,26.5],[85.4,56.6],[108.9,54.9],[121.0,46.9],[90.3,80.7],[82.3,64.9],[109.6,73.1],[105.7,97.1]]}]}" + }, + "ui": { + "kind": "Hidden" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 53, + "match": "analysisKey", + "name": "Analysis Key", + "value": { + "t": "Str", + "v": "f87b8df2e353c28e" + }, + "ui": { + "kind": "Hidden" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + } + ] + }, + { + "node": "Group", + "uid": 54, + "match": "ec.perspective.cameratracker", + "name": "3D Camera Tracker", + "kind": { + "kind": "Effect", + "effect": "ec.perspective.cameratracker" + }, + "enabled": true, + "children": [ + { + "node": "Prop", + "uid": 55, + "match": "shotType", + "name": "Shot Type", + "value": { + "t": "Enum", + "v": 0 + }, + "ui": { + "kind": "Popup", + "options": [ + "Fixed Angle of View", + "Variable Zoom", + "Specify Angle of View" + ] + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 56, + "match": "horizontalAngleOfView", + "name": "Horizontal Angle of View", + "value": { + "t": "Scalar", + "v": 40.0 + }, + "ui": { + "kind": "Slider", + "min": 1.0, + "max": 170.0, + "slider_min": 10.0, + "slider_max": 120.0, + "decimals": 1 + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 57, + "match": "showTrackPoints", + "name": "Show Track Points", + "value": { + "t": "Enum", + "v": 1 + }, + "ui": { + "kind": "Popup", + "options": [ + "2D Source", + "3D Solved" + ] + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 58, + "match": "renderTrackPoints", + "name": "Render Track Points", + "value": { + "t": "Bool", + "v": false + }, + "ui": { + "kind": "Checkbox" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 59, + "match": "trackPointSize", + "name": "Track Point Size", + "value": { + "t": "Scalar", + "v": 100.0 + }, + "ui": { + "kind": "Slider", + "min": 0.0, + "max": 1000.0, + "slider_min": 0.0, + "slider_max": 300.0, + "decimals": 0 + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 60, + "match": "targetSize", + "name": "Target Size", + "value": { + "t": "Scalar", + "v": 100.0 + }, + "ui": { + "kind": "Slider", + "min": 1.0, + "max": 1000.0, + "slider_min": 10.0, + "slider_max": 300.0, + "decimals": 0 + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Group", + "uid": 62, + "match": "advanced", + "name": "Advanced", + "kind": { + "kind": "Plain" + }, + "enabled": true, + "children": [ + { + "node": "Prop", + "uid": 61, + "match": "solveMethod", + "name": "Solve Method", + "value": { + "t": "Enum", + "v": 0 + }, + "ui": { + "kind": "Popup", + "options": [ + "Auto Detect", + "Typical", + "Mostly Flat Scene", + "Tripod Pan" + ] + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 63, + "match": "methodUsed", + "name": "Method Used", + "value": { + "t": "Str", + "v": "Tripod Pan" + }, + "ui": { + "kind": "Hidden" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 64, + "match": "averageError", + "name": "Average Error", + "value": { + "t": "Scalar", + "v": 0.48427435472856495 + }, + "ui": { + "kind": "Hidden" + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 65, + "match": "detailedAnalysis", + "name": "Detailed Analysis", + "value": { + "t": "Bool", + "v": false + }, + "ui": { + "kind": "Checkbox" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 66, + "match": "lensDistortion", + "name": "Solve Lens Distortion", + "value": { + "t": "Bool", + "v": false + }, + "ui": { + "kind": "Checkbox" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 67, + "match": "undistort", + "name": "Undistort Footage", + "value": { + "t": "Bool", + "v": false + }, + "ui": { + "kind": "Checkbox" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 68, + "match": "autoDeletePoints", + "name": "Auto-delete Points Across Time", + "value": { + "t": "Bool", + "v": true + }, + "ui": { + "kind": "Checkbox" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 69, + "match": "hideWarningBanner", + "name": "Hide Warning Banner", + "value": { + "t": "Bool", + "v": false + }, + "ui": { + "kind": "Checkbox" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + } + ] + }, + { + "node": "Prop", + "uid": 70, + "match": "tracks", + "name": "Tracks", + "value": { + "t": "Str", + "v": "{\"version\":1,\"size\":[160.0,120.0],\"start\":0.0,\"frameDuration\":0.08333333333333333,\"frames\":12,\"factor\":1.0,\"detailed\":false,\"tracks\":[{\"id\":0,\"start\":0,\"pts\":[[77.5,21.5],[63.0,42.02],[84.44,46.2],[94.51,43.46],[76.29,65.76],[77.07,73.7],[83.0,80.68],[85.69,85.86],[87.92,90.96],[89.4,99.49],[90.13,103.45],[90.97,109.78]]},{\"id\":1,\"start\":0,\"pts\":[[137.5,41.5],[125.34,65.53],[144.58,68.06],[155.96,64.9],[137.55,87.17]]},{\"id\":2,\"start\":0,\"pts\":[[103.5,9.5],[85.74,29.12],[104.73,32.21],[110.44,21.58],[92.15,43.67]]},{\"id\":3,\"start\":0,\"pts\":[[104.5,29.5],[85.9,50.12],[103.93,52.29],[109.88,48.47],[92.18,70.78]]},{\"id\":4,\"start\":0,\"pts\":[[135.5,18.5],[123.93,41.77],[143.14,45.58],[156.62,36.13],[137.86,58.99],[138.18,64.4],[142.35,72.76]]},{\"id\":5,\"start\":0,\"pts\":[[145.5,43.5],[136.49,68.62],[152.24,70.07]]},{\"id\":6,\"start\":0,\"pts\":[[68.5,16.5],[54.6,38.04],[75.81,40.97],[85.89,39.76],[67.52,62.45],[65.27,69.0]]},{\"id\":7,\"start\":0,\"pts\":[[126.5,18.5],[111.0,39.74],[128.76,42.63],[136.89,32.11],[117.68,54.39]]},{\"id\":8,\"start\":1,\"pts\":[[62.5,17.5],[85.58,22.87],[94.22,13.25],[75.42,35.02],[75.53,43.03],[82.19,51.59],[83.63,56.75],[86.42,64.01],[88.28,72.0],[89.17,76.16],[88.59,83.65]]},{\"id\":9,\"start\":1,\"pts\":[[114.5,106.5],[136.85,100.86],[147.03,92.4]]},{\"id\":10,\"start\":1,\"pts\":[[72.5,23.5],[97.31,29.7],[103.74,19.02],[85.16,40.96],[87.87,50.25],[90.99,56.84],[95.55,64.14],[94.95,69.09],[98.79,80.06],[99.53,84.17],[96.16,89.83]]},{\"id\":11,\"start\":1,\"pts\":[[105.5,56.5],[123.21,58.51],[130.67,54.61],[112.22,76.63],[107.86,82.64],[110.99,88.56],[110.29,92.35],[107.8,96.22],[105.42,105.58],[106.34,109.09],[103.49,114.69]]},{\"id\":12,\"start\":1,\"pts\":[[48.5,14.5],[74.56,20.81],[80.17,10.47],[60.5,31.52]]},{\"id\":13,\"start\":1,\"pts\":[[56.5,17.5],[79.51,23.22],[88.21,13.13],[69.38,34.7],[69.5,43.04],[76.12,50.88],[77.6,55.88],[80.34,63.05],[82.34,69.83],[83.23,74.02],[82.59,80.86]]},{\"id\":14,\"start\":1,\"pts\":[[111.5,56.5],[129.28,58.38],[136.92,54.94]]},{\"id\":15,\"start\":2,\"pts\":[[111.5,35.5],[116.71,24.74],[98.61,46.94],[95.39,52.62],[97.78,59.24]]},{\"id\":16,\"start\":2,\"pts\":[[111.5,54.5],[116.45,50.61],[98.85,72.82],[95.85,78.52],[98.15,84.77]]},{\"id\":17,\"start\":2,\"pts\":[[35.5,25.5],[51.37,25.98],[29.35,47.25],[34.79,58.33],[37.82,64.46],[35.7,68.67]]},{\"id\":18,\"start\":2,\"pts\":[[65.5,37.5],[73.16,34.37],[53.32,56.61]]},{\"id\":19,\"start\":3,\"pts\":[[139.5,91.5],[121.67,103.33],[122.29,98.68],[128.91,94.42],[130.04,88.08],[132.48,82.73],[132.69,85.66],[135.56,80.73],[133.86,80.34]]},{\"id\":20,\"start\":3,\"pts\":[[128.5,27.5],[109.77,49.81],[105.68,55.33]]},{\"id\":21,\"start\":4,\"pts\":[[132.5,108.5],[134.61,103.06],[139.18,98.97]]},{\"id\":22,\"start\":4,\"pts\":[[12.5,44.5],[15.77,54.38],[21.97,62.49],[23.19,67.31],[25.95,73.59],[32.15,76.64],[31.59,80.8],[37.46,83.52]]},{\"id\":23,\"start\":5,\"pts\":[[88.5,76.5],[91.53,82.49],[96.99,88.67],[96.31,92.35]]},{\"id\":24,\"start\":5,\"pts\":[[23.5,26.5],[34.16,36.76],[31.29,40.39]]},{\"id\":25,\"start\":5,\"pts\":[[29.5,29.5],[41.15,39.45],[37.02,43.33],[44.0,51.94]]},{\"id\":26,\"start\":5,\"pts\":[[16.5,25.5],[25.23,35.61],[24.0,39.41],[27.82,47.95],[34.62,50.88],[34.62,55.8],[39.37,61.06]]},{\"id\":27,\"start\":5,\"pts\":[[12.5,21.5],[20.78,30.91],[20.0,35.48],[23.13,42.77],[30.29,46.76],[30.38,51.79],[34.65,56.39]]},{\"id\":28,\"start\":5,\"pts\":[[115.5,82.5],[118.64,88.43],[118.09,93.19],[117.39,96.49],[114.83,106.78],[114.88,110.08]]},{\"id\":29,\"start\":5,\"pts\":[[122.5,61.5],[124.31,67.9],[123.88,72.08],[123.44,76.68],[120.82,86.9],[120.84,90.61],[117.85,97.98]]},{\"id\":30,\"start\":5,\"pts\":[[120.5,66.5],[122.86,72.98],[122.45,77.21],[121.98,81.49]]},{\"id\":31,\"start\":7,\"pts\":[[130.5,110.5],[132.07,103.93],[132.1,107.25],[134.31,101.57],[134.08,101.95]]},{\"id\":32,\"start\":7,\"pts\":[[144.5,87.5],[147.64,81.13],[147.29,84.43]]},{\"id\":33,\"start\":7,\"pts\":[[149.5,87.5],[151.62,81.15],[151.6,83.45]]},{\"id\":34,\"start\":7,\"pts\":[[141.5,109.5],[142.65,102.86],[144.73,106.45]]},{\"id\":35,\"start\":7,\"pts\":[[130.5,105.5],[132.21,97.98],[132.16,101.56]]},{\"id\":36,\"start\":7,\"pts\":[[46.5,46.5],[46.28,52.04],[53.71,58.71],[53.81,63.25]]},{\"id\":37,\"start\":7,\"pts\":[[63.5,78.5],[60.78,82.81],[61.38,87.33],[61.18,91.39],[60.13,92.98]]},{\"id\":38,\"start\":7,\"pts\":[[147.5,79.5],[148.79,73.74],[148.89,76.93],[152.13,71.83],[150.74,73.15]]},{\"id\":39,\"start\":8,\"pts\":[[110.5,73.5],[108.69,82.44],[109.4,86.46],[105.45,92.92]]},{\"id\":40,\"start\":8,\"pts\":[[37.5,75.5],[39.63,77.92],[39.24,82.09],[46.3,85.66]]},{\"id\":41,\"start\":9,\"pts\":[[72.5,92.5],[72.19,96.59],[71.1,99.0]]},{\"id\":42,\"start\":9,\"pts\":[[71.5,66.5],[71.58,70.65],[69.4,74.61]]},{\"id\":43,\"start\":9,\"pts\":[[42.5,52.5],[42.63,57.29],[48.81,63.22]]},{\"id\":44,\"start\":9,\"pts\":[[65.5,63.5],[65.9,67.71],[62.84,71.25]]},{\"id\":45,\"start\":9,\"pts\":[[54.5,84.5],[54.33,88.51],[54.49,89.47]]},{\"id\":46,\"start\":9,\"pts\":[[93.5,108.5],[94.19,112.24],[95.37,117.47]]}]}" + }, + "ui": { + "kind": "Hidden" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 71, + "match": "tracksKey", + "name": "Tracks Key", + "value": { + "t": "Str", + "v": "f87b8df2e353c28e" + }, + "ui": { + "kind": "Hidden" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 72, + "match": "solve", + "name": "Solve", + "value": { + "t": "Str", + "v": "{\"version\":1,\"size\":[160.0,120.0],\"start\":0.0,\"frameDuration\":0.08333333333333333,\"shot\":\"fixedAngle\",\"methodUsed\":\"tripodPan\",\"averageError\":0.48427435472856495,\"frames\":[{\"rot\":[0.0,0.0,0.0],\"center\":[0.0,0.0,0.0],\"focal\":1307.4062971457863,\"solved\":true},{\"rot\":[-0.020706432791589117,-0.008075142299053078,-0.06077723052316193],\"center\":[0.0,0.0,0.0],\"focal\":1307.4062971457863,\"solved\":true},{\"rot\":[-0.023015120128512003,0.005766803085062508,-0.05310541732098104],\"center\":[0.0,0.0,0.0],\"focal\":1307.4062971457863,\"solved\":true},{\"rot\":[-0.0170200841043793,0.014154804924310056,-0.050762146721755434],\"center\":[0.0,0.0,0.0],\"focal\":1307.4062971457863,\"solved\":true},{\"rot\":[-0.03356753265765158,-0.00032501028659040183,-0.05939089867356119],\"center\":[0.0,0.0,0.0],\"focal\":1307.4062971457863,\"solved\":true},{\"rot\":[-0.03943129932097289,0.0005848011077747351,-0.08866812978617226],\"center\":[0.0,0.0,0.0],\"focal\":1307.4062971457863,\"solved\":true},{\"rot\":[-0.04587385659719372,0.00556317017360105,-0.0922903070481816],\"center\":[0.0,0.0,0.0],\"focal\":1307.4062971457863,\"solved\":true},{\"rot\":[-0.04943028885690781,0.006586719185689197,-0.1030177435734751],\"center\":[0.0,0.0,0.0],\"focal\":1307.4062971457863,\"solved\":true},{\"rot\":[-0.05420648867299307,0.008981748111204299,-0.12219539780340982],\"center\":[0.0,0.0,0.0],\"focal\":1307.4062971457863,\"solved\":true},{\"rot\":[-0.06013167434751454,0.009061063842010806,-0.02749230328481726],\"center\":[0.0,0.0,0.0],\"focal\":1307.4062971457863,\"solved\":true},{\"rot\":[-0.06324873745045556,0.009255262692673143,-0.0355321639175462],\"center\":[0.0,0.0,0.0],\"focal\":1307.4062971457863,\"solved\":true},{\"rot\":[-0.06618656945718411,0.009319774678589094,-0.015189163276848288],\"center\":[0.0,0.0,0.0],\"focal\":1307.4062971457863,\"solved\":true}],\"points\":[{\"id\":0,\"pos\":[-0.001884543346613208,-0.029037452307042782,0.9806051370981879],\"error\":0.56470084,\"first\":0,\"last\":11},{\"id\":1,\"pos\":[0.04710854544038102,-0.015334858559503525,1.0773487523110807],\"error\":0.4844214,\"first\":0,\"last\":4},{\"id\":2,\"pos\":[0.015277830948334362,-0.04530698241035739,1.0393293228690133],\"error\":0.3392279,\"first\":0,\"last\":4},{\"id\":3,\"pos\":[0.013625157539074177,-0.028048508549162222,1.005205908477461],\"error\":0.27458376,\"first\":0,\"last\":4},{\"id\":4,\"pos\":[0.04631029390834158,-0.03435434088491794,1.0740657193502177],\"error\":0.58201736,\"first\":0,\"last\":6},{\"id\":5,\"pos\":[0.053943894461564695,-0.013610066623604573,1.082119350918587],\"error\":0.33030823,\"first\":0,\"last\":2},{\"id\":6,\"pos\":[-0.008886582549498147,-0.03423365291632946,1.0500072858529375],\"error\":0.5071384,\"first\":0,\"last\":5},{\"id\":7,\"pos\":[0.03502079415367347,-0.03650999899856251,1.0590501262399414],\"error\":0.3427207,\"first\":0,\"last\":4},{\"id\":8,\"pos\":[-0.0010752140340087664,-0.05237350807489353,0.9970760865042246],\"error\":0.35491616,\"first\":1,\"last\":11},{\"id\":9,\"pos\":[0.04045532180900739,0.011287908043101045,1.1118602867055563],\"error\":0.35821888,\"first\":1,\"last\":3},{\"id\":12,\"pos\":[-0.012247058383356772,-0.05711454082770741,1.026095977383411],\"error\":0.7576372,\"first\":1,\"last\":4},{\"id\":13,\"pos\":[-0.0057782770174717585,-0.05312155593792027,1.002454847850506],\"error\":0.543182,\"first\":1,\"last\":11},{\"id\":14,\"pos\":[0.03444063369446883,-0.02322488536327569,1.0525377480568132],\"error\":0.5363785,\"first\":1,\"last\":3},{\"id\":15,\"pos\":[0.016549051463405264,-0.04448675703801009,1.039611880351983],\"error\":0.31473064,\"first\":2,\"last\":6},{\"id\":16,\"pos\":[0.015561268129555224,-0.024177980474891878,1.0505609541603724],\"error\":0.448273,\"first\":2,\"last\":6},{\"id\":17,\"pos\":[-0.037387500298151244,-0.046978366490905726,1.0292596184739917],\"error\":0.5814284,\"first\":2,\"last\":7},{\"id\":18,\"pos\":[-0.018963958767977365,-0.03840654941654895,1.029868839109744],\"error\":0.62771547,\"first\":2,\"last\":4},{\"id\":20,\"pos\":[0.025824410036239483,-0.04221331533850395,1.051265936102142],\"error\":0.2203965,\"first\":3,\"last\":5},{\"id\":22,\"pos\":[-0.04851356310948073,-0.04922371159135405,1.0148030992016972],\"error\":0.60021263,\"first\":4,\"last\":11},{\"id\":23,\"pos\":[0.0036124978633337353,-0.027413664985930772,0.9671987963193897],\"error\":0.2763749,\"first\":5,\"last\":8},{\"id\":24,\"pos\":[-0.040597577069465214,-0.0703196502868751,1.0235859186232383],\"error\":0.20348576,\"first\":5,\"last\":7},{\"id\":26,\"pos\":[-0.04416469175733074,-0.0698384544617488,1.0211428377131564],\"error\":0.7978787,\"first\":5,\"last\":11},{\"id\":27,\"pos\":[-0.04818954744569584,-0.07416243876625056,1.013378662747375],\"error\":0.6526291,\"first\":5,\"last\":11},{\"id\":36,\"pos\":[-0.02865646311969018,-0.06261471940209673,1.0103266984710924],\"error\":0.47229937,\"first\":7,\"last\":10},{\"id\":37,\"pos\":[-0.022363582487320836,-0.038494993762751006,0.9688013952945587],\"error\":0.5830236,\"first\":7,\"last\":11},{\"id\":39,\"pos\":[0.012856414034624068,-0.040629816490058715,0.9569374661053799],\"error\":0.26666406,\"first\":8,\"last\":11},{\"id\":40,\"pos\":[-0.039487212277480116,-0.04718518517554586,0.9962321113853498],\"error\":0.48472655,\"first\":8,\"last\":11},{\"id\":41,\"pos\":[-0.014270376399443,-0.03389203058278074,0.9480752077612952],\"error\":0.79931283,\"first\":9,\"last\":11},{\"id\":42,\"pos\":[-0.014596706636024855,-0.05446828640367654,0.9808373411559306],\"error\":0.021755833,\"first\":9,\"last\":11},{\"id\":43,\"pos\":[-0.03666075088242092,-0.06654420263172509,0.9970944504474955],\"error\":0.20362298,\"first\":9,\"last\":11},{\"id\":44,\"pos\":[-0.01908451035407613,-0.057350036080083884,0.9896103813509345],\"error\":0.18076603,\"first\":9,\"last\":11},{\"id\":45,\"pos\":[-0.028179725825267236,-0.04186664641926184,0.9918891096090983],\"error\":0.24652581,\"first\":9,\"last\":11},{\"id\":46,\"pos\":[0.0010670642752641536,-0.018463010859931878,0.8064992130555431],\"error\":0.7289736,\"first\":9,\"last\":11}]}" + }, + "ui": { + "kind": "Hidden" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 73, + "match": "solveKey", + "name": "Solve Key", + "value": { + "t": "Str", + "v": "FixedAngle||Auto|[]" + }, + "ui": { + "kind": "Hidden" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 74, + "match": "deleted", + "name": "Deleted Points", + "value": { + "t": "Str", + "v": "" + }, + "ui": { + "kind": "Hidden" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 75, + "match": "camera", + "name": "Camera Layer", + "value": { + "t": "Scalar", + "v": 0.0 + }, + "ui": { + "kind": "Hidden" + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + } + ] + } + ] + }, + { + "node": "Group", + "uid": 15, + "match": "transform", + "name": "Transform", + "kind": { + "kind": "Plain" + }, + "enabled": true, + "children": [ + { + "node": "Prop", + "uid": 7, + "match": "anchor", + "name": "Anchor Point", + "value": { + "t": "Vec3", + "v": [ + 80.0, + 60.0, + 0.0 + ] + }, + "ui": { + "kind": "Point" + }, + "spatial": true, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 2, + "static_only": false + }, + { + "node": "Prop", + "uid": 8, + "match": "position", + "name": "Position", + "value": { + "t": "Vec3", + "v": [ + 80.0, + 60.0, + 0.0 + ] + }, + "ui": { + "kind": "Point" + }, + "spatial": true, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 2, + "static_only": false + }, + { + "node": "Prop", + "uid": 9, + "match": "scale", + "name": "Scale", + "value": { + "t": "Vec3", + "v": [ + 100.0, + 100.0, + 100.0 + ] + }, + "ui": { + "kind": "Percent" + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 2, + "static_only": false + }, + { + "node": "Prop", + "uid": 10, + "match": "orientation", + "name": "Orientation", + "value": { + "t": "Vec3", + "v": [ + 0.0, + 0.0, + 0.0 + ] + }, + "ui": { + "kind": "Angle" + }, + "spatial": false, + "hold_only": false, + "three_d_only": true, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 11, + "match": "rotationX", + "name": "X Rotation", + "value": { + "t": "Scalar", + "v": 0.0 + }, + "ui": { + "kind": "Angle" + }, + "spatial": false, + "hold_only": false, + "three_d_only": true, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 12, + "match": "rotationY", + "name": "Y Rotation", + "value": { + "t": "Scalar", + "v": 0.0 + }, + "ui": { + "kind": "Angle" + }, + "spatial": false, + "hold_only": false, + "three_d_only": true, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 13, + "match": "rotation", + "name": "Rotation", + "value": { + "t": "Scalar", + "v": 0.0 + }, + "ui": { + "kind": "Angle" + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 14, + "match": "opacity", + "name": "Opacity", + "value": { + "t": "Scalar", + "v": 100.0 + }, + "ui": { + "kind": "Slider", + "min": 0.0, + "max": 100.0, + "slider_min": 0.0, + "slider_max": 100.0, + "decimals": 0 + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + } + ] + }, + { + "node": "Group", + "uid": 16, + "match": "materialOptions", + "name": "Material Options", + "kind": { + "kind": "Plain" + }, + "enabled": true, + "children": [ + { + "node": "Prop", + "uid": 17, + "match": "castsShadows", + "name": "Casts Shadows", + "value": { + "t": "Enum", + "v": 0 + }, + "ui": { + "kind": "Popup", + "options": [ + "Off", + "On", + "Only" + ] + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 18, + "match": "lightTransmission", + "name": "Light Transmission", + "value": { + "t": "Scalar", + "v": 0.0 + }, + "ui": { + "kind": "Percent" + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 19, + "match": "acceptsShadows", + "name": "Accepts Shadows", + "value": { + "t": "Enum", + "v": 1 + }, + "ui": { + "kind": "Popup", + "options": [ + "Off", + "On", + "Only" + ] + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 20, + "match": "acceptsLights", + "name": "Accepts Lights", + "value": { + "t": "Bool", + "v": true + }, + "ui": { + "kind": "Checkbox" + }, + "spatial": false, + "hold_only": true, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 21, + "match": "ambient", + "name": "Ambient", + "value": { + "t": "Scalar", + "v": 100.0 + }, + "ui": { + "kind": "Percent" + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 22, + "match": "diffuse", + "name": "Diffuse", + "value": { + "t": "Scalar", + "v": 50.0 + }, + "ui": { + "kind": "Percent" + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 23, + "match": "specularIntensity", + "name": "Specular Intensity", + "value": { + "t": "Scalar", + "v": 50.0 + }, + "ui": { + "kind": "Percent" + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 24, + "match": "specularShininess", + "name": "Specular Shininess", + "value": { + "t": "Scalar", + "v": 5.0 + }, + "ui": { + "kind": "Percent" + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + { + "node": "Prop", + "uid": 25, + "match": "metal", + "name": "Metal", + "value": { + "t": "Scalar", + "v": 100.0 + }, + "ui": { + "kind": "Percent" + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + } + ] + } + ] + } + } + ], + "markers": [], + "shutter_angle": 180.0, + "shutter_phase": -90.0, + "motion_blur_samples": 16, + "motion_blur_adaptive_limit": 128, + "renderer": "Classic3D", + "hide_shy": false, + "enable_motion_blur": true, + "enable_frame_blending": true, + "draft_3d": false, + "preserve_frame_rate": false, + "preserve_resolution": false, + "poster_time": 0, + "global_light": { + "angle": { + "uid": 0, + "match": "globalLightAngle", + "name": "Global Light Angle", + "value": { + "t": "Scalar", + "v": 120.0 + }, + "ui": { + "kind": "Angle" + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + }, + "altitude": { + "uid": 0, + "match": "globalLightAltitude", + "name": "Global Light Altitude", + "value": { + "t": "Scalar", + "v": 30.0 + }, + "ui": { + "kind": "Angle" + }, + "spatial": false, + "hold_only": false, + "three_d_only": false, + "two_d_only": false, + "shown_dims": 0, + "static_only": false + } + } + } + } + }, + "next_id": 76 +} \ No newline at end of file diff --git a/crates/engine/src/tests/fixtures/tracked-0.22.ecproj.attribution b/crates/engine/src/tests/fixtures/tracked-0.22.ecproj.attribution new file mode 100644 index 000000000..611f620f9 --- /dev/null +++ b/crates/engine/src/tests/fixtures/tracked-0.22.ecproj.attribution @@ -0,0 +1,8 @@ +asset: tracked-0.22.ecproj +title: One clip with an analysed Warp Stabilizer and 3D Camera Tracker from EffectCraft 0.22.0 +author: arnav-hiwarkar +source: original work +license: MIT OR Apache-2.0 +license-file: ../../../../../LICENSE-MIT +added: 2026-11-12 by arnav-hiwarkar +notes: Saved by the released 0.22.0 CLI after importing a generated 160x120, 12-frame shaking crop of an ffmpeg testsrc2 pattern, applying both effects and analysing them. The source path is a generic test path; the video is not needed by the tests, which only check that the saved analyses are kept or cleared. diff --git a/crates/engine/src/tests/legacy_analysis_keys.rs b/crates/engine/src/tests/legacy_analysis_keys.rs index f4e442050..95f789968 100644 --- a/crates/engine/src/tests/legacy_analysis_keys.rs +++ b/crates/engine/src/tests/legacy_analysis_keys.rs @@ -1,6 +1,82 @@ //! Warp Stabilizer and 3D Camera Tracker analyses saved by an earlier release keep matching //! their footage after an edit that doesn't change it (#3456), and a real change still clears them. +use effectcraft_effects::camera_tracker::{TRACKS, TRACKS_KEY}; +use effectcraft_effects::warp_stab::{ANALYSIS, ANALYSIS_KEY}; +use effectcraft_keyframe::Value; +use effectcraft_project::{ItemId, LayerId, Project}; +use serde_json::json; + +use crate::Session; + +const FOOTAGE: ItemId = ItemId(1); +const COMP: ItemId = ItemId(2); +const CLIP: LayerId = LayerId(3); +const WARP: u64 = 26; +const TRACKER: u64 = 54; + +/// The analysis (as its length and a hash: it's long) and key of the Warp Stabilizer and the +/// 3D Camera Tracker. +fn saved(s: &Session) -> [(usize, u64, String); 2] { + use std::hash::{Hash, Hasher}; + let layer = s.project.comp(COMP).unwrap().layer(CLIP).unwrap(); + let get = |uid: u64, data: &str, key: &str| { + let params = crate::camera_track::static_params(layer.props.find_group(uid).unwrap()); + let text = |id: &str| match params.get(id) { + Some(Value::Str(s)) => s.clone(), + other => panic!("{id}: {other:?}"), + }; + let analysis = text(data); + let mut h = std::collections::hash_map::DefaultHasher::new(); + analysis.hash(&mut h); + (analysis.len(), h.finish(), text(key)) + }; + [get(WARP, ANALYSIS, ANALYSIS_KEY), get(TRACKER, TRACKS, TRACKS_KEY)] +} + +fn open(json: &str) -> Session { + let mut s = Session::default(); + s.replace_project(Project::from_json(json).unwrap(), None); + s.open_comp(COMP); + s +} + +#[test] +fn analyses_saved_by_0_22_survive_edits_that_keep_their_frames() { + // Saved by the released 0.22.0 CLI after analysing both effects; its footage is offline here. + let mut s = open(include_str!("fixtures/tracked-0.22.ecproj")); + let before = saved(&s); + let layer = s.project.comp(COMP).unwrap().layer(CLIP).unwrap(); + for (len, _, key) in &before { + assert!(*len > 0); + // The key predates Footage::placed_page, so it isn't today's signature. + assert_ne!(key, &crate::warp::signature(&s.project, COMP, layer)); + } + + // Adding to the render queue edits the project, as `effectcraft-cli render` does. + s.execute_checked("renderQueue.add", json!({"comp": COMP.0, "format": "png", "output": "frame.png"})).unwrap(); + assert_eq!(saved(&s), before); + s.execute_checked("edit.undo", json!({})).unwrap(); + assert_eq!(saved(&s), before); + + // Saved and reopened (by this version), the old keys are kept and still match. + let mut s = open(&s.project.to_json()); + s.execute_checked("renderQueue.add", json!({"comp": COMP.0, "format": "png", "output": "frame.png"})).unwrap(); + assert_eq!(saved(&s), before); + + // Other frames, from other timing or a reinterpreted footage item: both analyses are + // cleared, and undo brings them back. + let cleared = |s: &Session| saved(s).iter().all(|(len, _, key)| *len == 0 && key.is_empty()); + s.execute_checked("layer.timing", json!({"layer": CLIP.0, "out": 0.5})).unwrap(); + assert!(cleared(&s)); + s.execute_checked("edit.undo", json!({})).unwrap(); + assert_eq!(saved(&s), before); + s.execute_checked("file.interpretFootage", json!({"items": [FOOTAGE.0], "invertAlpha": true})).unwrap(); + assert!(cleared(&s)); + s.execute_checked("edit.undo", json!({})).unwrap(); + assert_eq!(saved(&s), before); +} + /// The footage text analysis keys are hashed from, as 0.23 saved it. A new `Footage` field must /// not change it (or every saved analysis is dropped again). #[test] From e36e25512d16d802a16cd164c0ce193912854544 Mon Sep 17 00:00:00 2001 From: arnav-hiwarkar Date: Thu, 12 Nov 2026 10:56:40 +0530 Subject: [PATCH 3/4] #3456: solve again from 3D Camera Tracker tracks saved before 0.23 Analysing a tracker reused its saved tracks only when their key was today's, so solving a 0.22 project again (after a Solve Method change) tracked the footage again, and failed when it was offline. --- crates/engine/src/analysis/camera_track.rs | 2 +- .../engine/src/tests/legacy_analysis_keys.rs | 22 ++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/analysis/camera_track.rs b/crates/engine/src/analysis/camera_track.rs index d6a79b791..378f42dd1 100644 --- a/crates/engine/src/analysis/camera_track.rs +++ b/crates/engine/src/analysis/camera_track.rs @@ -334,7 +334,7 @@ impl Session { let key = tracks_key(&self.project, comp, l, g); let settings = ct::settings(¶ms); let solve_key = ct::solve_key(&settings); - let tracks = ct::tracks(¶ms).filter(|_| str_of(g, TRACKS_KEY) == key); + let tracks = ct::tracks(¶ms).filter(|_| tracks_match(&self.project, comp, l, g)); let n = times.len(); let work = CamWork { project: self.project.clone(), diff --git a/crates/engine/src/tests/legacy_analysis_keys.rs b/crates/engine/src/tests/legacy_analysis_keys.rs index 95f789968..291188a17 100644 --- a/crates/engine/src/tests/legacy_analysis_keys.rs +++ b/crates/engine/src/tests/legacy_analysis_keys.rs @@ -1,7 +1,7 @@ //! Warp Stabilizer and 3D Camera Tracker analyses saved by an earlier release keep matching //! their footage after an edit that doesn't change it (#3456), and a real change still clears them. -use effectcraft_effects::camera_tracker::{TRACKS, TRACKS_KEY}; +use effectcraft_effects::camera_tracker::{SOLVE, TRACKS, TRACKS_KEY}; use effectcraft_effects::warp_stab::{ANALYSIS, ANALYSIS_KEY}; use effectcraft_keyframe::Value; use effectcraft_project::{ItemId, LayerId, Project}; @@ -77,6 +77,26 @@ fn analyses_saved_by_0_22_survive_edits_that_keep_their_frames() { assert_eq!(saved(&s), before); } +/// Solving again reuses the 0.22 tracks rather than tracking the (here offline) footage again. +#[test] +fn camera_tracker_solves_again_from_tracks_saved_by_0_22() { + let mut s = open(include_str!("fixtures/tracked-0.22.ecproj")); + let tracks = saved(&s)[1].clone(); + let solve = |s: &Session| { + let g = s.project.comp(COMP).unwrap().layer(CLIP).unwrap().props.find_group(TRACKER).unwrap(); + crate::camera_track::static_params(g).get(SOLVE).cloned().unwrap() + }; + // Another solve method, and back, clears the solve and keeps the tracks. + for method in [1, 0] { + s.execute_checked("prop.set", json!({"layer": CLIP.0, "path": "effects/#2/advanced/solveMethod", "value": method})).unwrap(); + } + assert_eq!(solve(&s), Value::Str(String::new())); + assert_eq!(saved(&s)[1], tracks); + s.execute_checked("camera.analyze", json!({"layer": CLIP.0, "effect": TRACKER, "wait": true})).unwrap(); + assert!(matches!(solve(&s), Value::Str(v) if !v.is_empty())); + assert_eq!(saved(&s)[1], tracks); +} + /// The footage text analysis keys are hashed from, as 0.23 saved it. A new `Footage` field must /// not change it (or every saved analysis is dropped again). #[test] From 58fcfeb54dad13c0b62a8f80f8a8fc7c5008e083 Mon Sep 17 00:00:00 2001 From: arnav-hiwarkar Date: Thu, 12 Nov 2026 11:04:21 +0530 Subject: [PATCH 4/4] #3456: test that placed-page footage doesn't match a 0.22 key 0.22 keys are hashed without placed_page, so only footage that isn't placed as a page may match them. --- crates/engine/src/tests/legacy_analysis_keys.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/engine/src/tests/legacy_analysis_keys.rs b/crates/engine/src/tests/legacy_analysis_keys.rs index 291188a17..06a05cc77 100644 --- a/crates/engine/src/tests/legacy_analysis_keys.rs +++ b/crates/engine/src/tests/legacy_analysis_keys.rs @@ -75,6 +75,15 @@ fn analyses_saved_by_0_22_survive_edits_that_keep_their_frames() { assert!(cleared(&s)); s.execute_checked("edit.undo", json!({})).unwrap(); assert_eq!(saved(&s), before); + // Footage framed as a placed page has other frames too, though 0.22 had no such field. + s.edit("place page", None, |p, _| { + if let Some(effectcraft_project::ItemKind::Footage(f)) = p.item_mut(FOOTAGE).map(|i| &mut i.kind) { + f.placed_page = true; + } + Ok(()) + }) + .unwrap(); + assert!(cleared(&s)); } /// Solving again reuses the 0.22 tracks rather than tracking the (here offline) footage again.