diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 3fc73c92..4b170ba6 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -52,6 +52,12 @@ procedural demo footage (`crates/media/src/generators.rs`); no external media. R | `crates/golden/goldens/mask_color.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `mask_color`) from procedurally generated media | MIT OR Apache-2.0 | | `crates/golden/goldens/mask_inverted.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `mask_inverted`) from procedurally generated media | MIT OR Apache-2.0 | | `crates/golden/goldens/mask_opacity.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `mask_opacity`) from procedurally generated media | MIT OR Apache-2.0 | +| `crates/golden/goldens/vfx_distort.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `vfx_distort`) from procedurally generated media | MIT OR Apache-2.0 | +| `crates/golden/goldens/vfx_ultra_key.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `vfx_ultra_key`) from procedurally generated media | MIT OR Apache-2.0 | +| `crates/golden/goldens/vfx_glows.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `vfx_glows`) from procedurally generated media | MIT OR Apache-2.0 | +| `crates/golden/goldens/vfx_spacer_stroke_shadow.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `vfx_spacer_stroke_shadow`) from procedurally generated media | MIT OR Apache-2.0 | +| `crates/golden/goldens/vfx_vr_rotate.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `vfx_vr_rotate`) from procedurally generated media | MIT OR Apache-2.0 | +| `crates/golden/goldens/vfx_stylize.png` | FilmCraft contributors | Original work: golden reference rendered by FilmCraft (crates/golden/tests/golden.rs, scene `vfx_stylize`) from procedurally generated media | MIT OR Apache-2.0 | ## Third-party media shown in screenshots diff --git a/README.md b/README.md index 60f0871c..b27c8c22 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ FilmCraft is a non-linear editor for people who know Premiere: the same panels,

NASA's Earth Views from the ISS with a look applied, and the Effects browser open on the dissolves.

-- **Around 55 video effects and 30 transitions.** Blurs, keys, distortions, stylize and colour effects. Cross dissolve, dip to black or white, film dissolve, wipes, irises, pushes, slides, zooms, page peel, cube spin and more. +- **Premiere 26's full Video Effects bin (93 effects in 16 folders), the Legacy bin and the obsolete effects old projects use, plus around 30 transitions.** Blurs (Bokeh, Focus, Compound), keys (Ultra Key, Track Matte), distortions (Corner Pin, Turbulent Displace, Warp Stabilizer), Lights & Glows, Immersive Video (VR) effects on equirectangular footage, Posterize Time, Echo and more. Cross dissolve, dip to black or white, film dissolve, wipes, irises, pushes, slides, zooms, page peel, cube spin and more. - **Motion and opacity on every clip:** position, scale, rotation, anchor point and anti-flicker, plus 26 blend modes. - **Keyframes like Premiere's:** linear, Bezier, auto and continuous Bezier, hold, ease in and ease out. Effect Controls shows a keyframe lane for every parameter, and each animated parameter opens into **value and velocity graphs** with draggable influence handles. - **A GPU compositor** built on wgpu (Metal, Vulkan, DirectX 12, WebGPU). It samples YUV straight from the decoder with footprint supersampling and blends in linear light. A CPU path renders the same frames, and the two are tested against each other. diff --git a/crates/golden/goldens/vfx_distort.png b/crates/golden/goldens/vfx_distort.png new file mode 100644 index 00000000..abadc419 Binary files /dev/null and b/crates/golden/goldens/vfx_distort.png differ diff --git a/crates/golden/goldens/vfx_distort.png.attribution b/crates/golden/goldens/vfx_distort.png.attribution new file mode 100644 index 00000000..c55ba227 --- /dev/null +++ b/crates/golden/goldens/vfx_distort.png.attribution @@ -0,0 +1,8 @@ +asset: vfx_distort.png +title: Corner Pin over Turbulent Displace +author: FilmCraft contributors +source: original work (rendered by FilmCraft's own renderer from a procedurally generated project; no external media) +license: MIT OR Apache-2.0 +license-file: +added: 2026-10-02 by FilmCraft contributors (FILMCRAFT_BLESS=1) +notes: Golden reference image generated by crates/golden/tests/golden.rs (vfx_distort). Regenerate with FILMCRAFT_BLESS=1. Contains no Adobe artwork. diff --git a/crates/golden/goldens/vfx_glows.png b/crates/golden/goldens/vfx_glows.png new file mode 100644 index 00000000..bd96ce11 Binary files /dev/null and b/crates/golden/goldens/vfx_glows.png differ diff --git a/crates/golden/goldens/vfx_glows.png.attribution b/crates/golden/goldens/vfx_glows.png.attribution new file mode 100644 index 00000000..0e408370 --- /dev/null +++ b/crates/golden/goldens/vfx_glows.png.attribution @@ -0,0 +1,8 @@ +asset: vfx_glows.png +title: Wonder Glow and Glint +author: FilmCraft contributors +source: original work (rendered by FilmCraft's own renderer from a procedurally generated project; no external media) +license: MIT OR Apache-2.0 +license-file: +added: 2026-10-02 by FilmCraft contributors (FILMCRAFT_BLESS=1) +notes: Golden reference image generated by crates/golden/tests/golden.rs (vfx_glows). Regenerate with FILMCRAFT_BLESS=1. Contains no Adobe artwork. diff --git a/crates/golden/goldens/vfx_spacer_stroke_shadow.png b/crates/golden/goldens/vfx_spacer_stroke_shadow.png new file mode 100644 index 00000000..b06ef5bf Binary files /dev/null and b/crates/golden/goldens/vfx_spacer_stroke_shadow.png differ diff --git a/crates/golden/goldens/vfx_spacer_stroke_shadow.png.attribution b/crates/golden/goldens/vfx_spacer_stroke_shadow.png.attribution new file mode 100644 index 00000000..31793fb1 --- /dev/null +++ b/crates/golden/goldens/vfx_spacer_stroke_shadow.png.attribution @@ -0,0 +1,8 @@ +asset: vfx_spacer_stroke_shadow.png +title: Spacer, Stroke and Long Shadow over a Gradient +author: FilmCraft contributors +source: original work (rendered by FilmCraft's own renderer from a procedurally generated project; no external media) +license: MIT OR Apache-2.0 +license-file: +added: 2026-10-02 by FilmCraft contributors (FILMCRAFT_BLESS=1) +notes: Golden reference image generated by crates/golden/tests/golden.rs (vfx_spacer_stroke_shadow). Regenerate with FILMCRAFT_BLESS=1. Contains no Adobe artwork. diff --git a/crates/golden/goldens/vfx_stylize.png b/crates/golden/goldens/vfx_stylize.png new file mode 100644 index 00000000..a13c31c0 Binary files /dev/null and b/crates/golden/goldens/vfx_stylize.png differ diff --git a/crates/golden/goldens/vfx_stylize.png.attribution b/crates/golden/goldens/vfx_stylize.png.attribution new file mode 100644 index 00000000..6efb300c --- /dev/null +++ b/crates/golden/goldens/vfx_stylize.png.attribution @@ -0,0 +1,8 @@ +asset: vfx_stylize.png +title: Channel Mix, Color Emboss and Rounded Crop +author: FilmCraft contributors +source: original work (rendered by FilmCraft's own renderer from a procedurally generated project; no external media) +license: MIT OR Apache-2.0 +license-file: +added: 2026-10-02 by FilmCraft contributors (FILMCRAFT_BLESS=1) +notes: Golden reference image generated by crates/golden/tests/golden.rs (vfx_stylize). Regenerate with FILMCRAFT_BLESS=1. Contains no Adobe artwork. diff --git a/crates/golden/goldens/vfx_ultra_key.png b/crates/golden/goldens/vfx_ultra_key.png new file mode 100644 index 00000000..095901c6 Binary files /dev/null and b/crates/golden/goldens/vfx_ultra_key.png differ diff --git a/crates/golden/goldens/vfx_ultra_key.png.attribution b/crates/golden/goldens/vfx_ultra_key.png.attribution new file mode 100644 index 00000000..3cebf092 --- /dev/null +++ b/crates/golden/goldens/vfx_ultra_key.png.attribution @@ -0,0 +1,8 @@ +asset: vfx_ultra_key.png +title: Ultra Key on colour bars over footage +author: FilmCraft contributors +source: original work (rendered by FilmCraft's own renderer from a procedurally generated project; no external media) +license: MIT OR Apache-2.0 +license-file: +added: 2026-10-02 by FilmCraft contributors (FILMCRAFT_BLESS=1) +notes: Golden reference image generated by crates/golden/tests/golden.rs (vfx_ultra_key). Regenerate with FILMCRAFT_BLESS=1. Contains no Adobe artwork. diff --git a/crates/golden/goldens/vfx_vr_rotate.png b/crates/golden/goldens/vfx_vr_rotate.png new file mode 100644 index 00000000..91844a4b Binary files /dev/null and b/crates/golden/goldens/vfx_vr_rotate.png differ diff --git a/crates/golden/goldens/vfx_vr_rotate.png.attribution b/crates/golden/goldens/vfx_vr_rotate.png.attribution new file mode 100644 index 00000000..0ba6c637 --- /dev/null +++ b/crates/golden/goldens/vfx_vr_rotate.png.attribution @@ -0,0 +1,8 @@ +asset: vfx_vr_rotate.png +title: VR Rotate Sphere +author: FilmCraft contributors +source: original work (rendered by FilmCraft's own renderer from a procedurally generated project; no external media) +license: MIT OR Apache-2.0 +license-file: +added: 2026-10-02 by FilmCraft contributors (FILMCRAFT_BLESS=1) +notes: Golden reference image generated by crates/golden/tests/golden.rs (vfx_vr_rotate). Regenerate with FILMCRAFT_BLESS=1. Contains no Adobe artwork. diff --git a/crates/golden/tests/golden.rs b/crates/golden/tests/golden.rs index a3552efe..fbbc74db 100644 --- a/crates/golden/tests/golden.rs +++ b/crates/golden/tests/golden.rs @@ -460,6 +460,87 @@ fn mask_opacity() -> Scene { b.at(12) } +/// M5.11 Distort: Corner Pin (a demo picture pinned into a quad) over Turbulent-Displaced bars. +fn vfx_distort() -> Scene { + let mut b = Builder::new(2); + let bars = b.media(Generator::BarsAndTone); + let fg = b.demo(DemoScene::Dunes); + let c = b.place(0, bars, 0, 48); + b.effect(c, "turbulent_displace", &[("amount", fl(40.0)), ("size", fl(80.0)), ("complexity", fl(2.0))]); + let top = b.place(1, fg, 0, 48); + b.effect( + top, + "corner_pin", + &[("upper_left", pt(120.0, 60.0)), ("upper_right", pt(560.0, 20.0)), ("lower_left", pt(60.0, 330.0)), ("lower_right", pt(520.0, 300.0))], + ); + b.at(12) +} + +/// M5.11 Keying: Ultra Key removing the bars' green over a demo background, with spill suppression. +fn vfx_ultra_key() -> Scene { + let mut b = Builder::new(2); + let bg = b.demo(DemoScene::OceanSunset); + let bars = b.media(Generator::BarsAndTone); + b.place(0, bg, 0, 48); + let top = b.place(1, bars, 0, 48); + b.effect(top, "ultra_key", &[("key_color", ParamValue::Color([0.0, 0.75, 0.0, 1.0]))]); + b.at(12) +} + +/// M5.11 Lights & Glows: Wonder Glow and Glint on a night city. +fn vfx_glows() -> Scene { + let mut b = Builder::new(1); + let city = b.demo(DemoScene::CityNight); + let c = b.place(0, city, 0, 48); + b.effect(c, "wonder_glow", &[("threshold", fl(55.0)), ("radius", fl(20.0))]); + b.effect(c, "glint", &[("threshold", fl(80.0)), ("length", fl(40.0))]); + b.at(12) +} + +/// M5.11 Utility / Perspective: Spacer inset with rounded corners, an inside Stroke and a fading +/// Long Shadow, over a Gradient generator. +fn vfx_spacer_stroke_shadow() -> Scene { + let mut b = Builder::new(2); + let bg = b.media(Generator::BlackVideo); + let fg = b.demo(DemoScene::Forest); + let back = b.place(0, bg, 0, 48); + b.effect( + back, + "gradient", + &[ + ("start", pt(0.0, 0.0)), + ("end", pt(640.0, 360.0)), + ("start_color", ParamValue::Color([0.15, 0.2, 0.45, 1.0])), + ("end_color", ParamValue::Color([0.9, 0.6, 0.3, 1.0])), + ], + ); + let top = b.place(1, fg, 0, 48); + b.effect(top, "spacer", &[("left", fl(90.0)), ("radius", fl(30.0))]); + b.effect(top, "stroke", &[("width", fl(6.0)), ("position", ParamValue::Choice(2))]); + b.effect(top, "long_shadow", &[("length", fl(80.0)), ("opacity", fl(70.0))]); + b.at(12) +} + +/// M5.11 Immersive Video: VR Rotate Sphere (pan and tilt) on equirectangular-treated aurora. +fn vfx_vr_rotate() -> Scene { + let mut b = Builder::new(1); + let aur = b.demo(DemoScene::Aurora); + let c = b.place(0, aur, 0, 48); + b.effect(c, "vr_rotate_sphere", &[("pan", fl(90.0)), ("tilt", fl(25.0)), ("roll", fl(10.0))]); + b.at(12) +} + +/// M5.11 Stylize / Image Control: Rounded Crop, Color Emboss and Channel Mix on plasma. +fn vfx_stylize() -> Scene { + let mut b = Builder::new(1); + let pl = b.demo(DemoScene::Plasma); + let c = b.place(0, pl, 0, 48); + b.effect(c, "channel_mix", &[("rr", fl(0.0)), ("rb", fl(100.0)), ("bb", fl(0.0)), ("br", fl(100.0))]); + b.effect(c, "color_emboss", &[("relief", fl(3.0)), ("contrast", fl(150.0))]); + b.effect(c, "rounded_crop", &[("left", fl(8.0)), ("right", fl(8.0)), ("top", fl(8.0)), ("bottom", fl(8.0)), ("radius", fl(50.0)), ("border", fl(4.0))]); + b.at(12) +} + /// (name, title, scene). fn scenes() -> Vec<(&'static str, &'static str, fn() -> Scene)> { vec![ @@ -480,6 +561,12 @@ fn scenes() -> Vec<(&'static str, &'static str, fn() -> Scene)> { ("mask_color", "Masks: Black & White inside an expanded polygon minus a feathered Bezier mask", mask_color), ("mask_inverted", "Masks: Mosaic outside an inverted ellipse mask at 80% opacity", mask_inverted), ("mask_opacity", "Masks: feathered ellipse opacity mask on a rotated top layer", mask_opacity), + ("vfx_distort", "Corner Pin over Turbulent Displace", vfx_distort), + ("vfx_ultra_key", "Ultra Key on colour bars over footage", vfx_ultra_key), + ("vfx_glows", "Wonder Glow and Glint", vfx_glows), + ("vfx_spacer_stroke_shadow", "Spacer, Stroke and Long Shadow over a Gradient", vfx_spacer_stroke_shadow), + ("vfx_vr_rotate", "VR Rotate Sphere", vfx_vr_rotate), + ("vfx_stylize", "Channel Mix, Color Emboss and Rounded Crop", vfx_stylize), ] } @@ -538,7 +625,13 @@ goldens!( mask_blur, mask_color, mask_inverted, - mask_opacity + mask_opacity, + vfx_distort, + vfx_ultra_key, + vfx_glows, + vfx_spacer_stroke_shadow, + vfx_vr_rotate, + vfx_stylize ); /// Sanity: the scenes are not trivially empty or identical to each other. diff --git a/crates/ui-egui/src/panels/effect_controls.rs b/crates/ui-egui/src/panels/effect_controls.rs index 813f029a..e3d36b96 100644 --- a/crates/ui-egui/src/panels/effect_controls.rs +++ b/crates/ui-egui/src/panels/effect_controls.rs @@ -293,6 +293,22 @@ pub(crate) fn param_row( (ParamKind::Path, _) => { crate::panels::masks::path_value(app, &mut vui, clip, idx, mask, actions); } + (ParamKind::Text, ParamValue::Text(s)) if !pd.id.ends_with("_lut") => { + // edited in a buffer; committed (one undo step) when the field loses focus + let mut buf = vui.data_mut(|d| d.get_temp::(id)).unwrap_or_else(|| s.clone()); + let r = vui.add(egui::TextEdit::singleline(&mut buf).desired_width(160.0)); + app.auto.add(&format!("effectControls.text.{}.{}", idx, pd.id), r.rect, pd.label); + if r.lost_focus() { + if buf != *s { + set = Some(json!(buf)); + } + vui.data_mut(|d| d.remove::(id)); + } else if r.has_focus() { + vui.data_mut(|d| d.insert_temp(id, buf)); + } else { + vui.data_mut(|d| d.remove::(id)); + } + } _ => { vui.label(param_text(&app.session.project, pd.id, &value)); } diff --git a/crates/ui-egui/src/panels/effects.rs b/crates/ui-egui/src/panels/effects.rs index e6a9a737..22816543 100644 --- a/crates/ui-egui/src/panels/effects.rs +++ b/crates/ui-egui/src/panels/effects.rs @@ -21,7 +21,7 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) { let filter = q.to_ascii_lowercase(); let defs = filmcraft_project::effect_defs(); // folder tree: top-level categories in Premiere's order - let tops = ["Presets", "Lumetri Presets", "Audio Effects", "Audio Transitions", "Video Effects", "Video Transitions"]; + let tops = ["Presets", "Lumetri Presets", "Audio Effects", "Audio Transitions", "Video Effects", "Video Transitions", "Legacy"]; let mut apply: Option = None; let mut preset_action: Option<(String, String)> = None; egui::ScrollArea::vertical().id_salt("fx-scroll").auto_shrink([false, false]).show(&mut bui, |ui| { @@ -33,11 +33,15 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) { } continue; } - let items: Vec<_> = defs + let mut items: Vec<_> = defs .iter() .filter(|d| d.category.first() == Some(&top)) .filter(|d| filter.is_empty() || d.name.to_ascii_lowercase().contains(&filter)) .collect(); + // Premiere lists effect folders and effects alphabetically (obsolete ones last) + if top == "Video Effects" || top == "Legacy" { + items.sort_by_key(|d| (d.category.get(1) == Some(&"Obsolete"), d.category.get(1).copied().unwrap_or(""), d.name.to_ascii_lowercase())); + } if !filter.is_empty() && items.is_empty() { continue; } diff --git a/docs/architecture.md b/docs/architecture.md index 3402c345..e094af34 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -57,7 +57,7 @@ and `filmcraft-cli`. | `speech` | L2 | speech-to-text: `Transcriber` trait, Whisper model catalogue + verified downloader (feature `download`), pure-Rust Whisper inference on candle with word timestamps (feature `whisper`), speaker labelling ([transcripts.md](transcripts.md)) | | `codecs` | L2 | container + codec hub: MP4/MOV and MKV sources, GOP-aware seeking, decoder registry, audio decoding | | `interchange` | L2 | EDL, FCP7 XML, FCPXML and OTIO import/export (no file I/O) | -| `render` | L3 | sequence evaluation, CPU compositor, video effects, transitions, audio mix | +| `render` | L3 | sequence evaluation, CPU compositor, video effects (`effects`, `vfx`; effects needing other frames or tracks read them through `vfx::FxEnv`), transitions, audio mix | | `gpu` | L3 | wgpu compositor (WGSL) | | `golden` | L3 | test-only: golden-image tests of the CPU renderer and GPU-vs-CPU parity; empty library, dev-dependencies only | | `export` | L3 | render → encode → mux pipeline, progress/cancel | diff --git a/docs/testing.md b/docs/testing.md index 3a6c49bf..e68739bd 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -101,7 +101,11 @@ Each codec README has the full fixture matrix and the measured results. with `crates/golden/goldens/.png` by `filmcraft_testkit::golden`: **PSNR ≥ 45 dB, max abs ≤ 12, 99th-percentile per-pixel max channel difference ≤ 2** (8-bit sRGB levels; `Tolerance::RENDER`). On failure the actual frame and a ×8 difference image go to - `/target/golden-failures/`. `scenes_are_distinct` guards against blank or duplicate + `/target/golden-failures/`. Six `vfx_*` scenes cover the M5.11 effects (Corner Pin over Turbulent + Displace, Ultra Key, Wonder Glow + Glint, Spacer + Stroke + Long Shadow over Gradient, VR Rotate Sphere, + Channel Mix + Color Emboss + Rounded Crop); per-effect unit tests live in `crates/render/src/vfx/tests.rs` + (identity at neutral parameters, determinism, range, a known value each, temporal/track effects through + the compositor). `scenes_are_distinct` guards against blank or duplicate scenes. - **Blessing:** `FILMCRAFT_BLESS=1 cargo test -p filmcraft-golden` rewrites the references (and writes a `.attribution` sidecar for any new one; it prints the `ATTRIBUTION.md` row to add).