From a408d990b23d0b60a17186d31ea715c8e0be9437 Mon Sep 17 00:00:00 2001 From: skylar taylor-barrick Date: Thu, 5 Nov 2026 22:45:23 +0100 Subject: [PATCH] Export: use conformant ProRes alpha run termination (#2225) * #293: use conformant ProRes alpha run termination Pin the direct ProRes encoder to FilmCraft e4be1577 while preserving all other codec pins. Add a full MOV export regression for final alpha runs across both 4444 profiles, alpha modes and blur settings. * #293: document ProRes alpha export regression * export: repin FilmCraft to the merged ProRes alpha fix and move the round-trip regression into roundtrip.rs All FilmCraft deps pinned to d5ead4ba (squash merge of filmcraft #2991). H.264 pinned movie bytes move 104341 -> 104342: only the encoder version string in the SEI (0.8.0 -> 0.12.0) and five size fields change. --- Cargo.lock | 144 ++++++++++++++------------ Cargo.toml | 36 +++---- ROADMAP.md | 6 +- crates/export/tests/it/movie_bytes.rs | 5 +- crates/export/tests/it/roundtrip.rs | 62 ++++++++++- docs/file-format-parity.md | 5 +- 6 files changed, 170 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ad887965..6ebde6757 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1132,6 +1132,14 @@ dependencies = [ "writeable", ] +[[package]] +name = "craft-media" +version = "0.1.0" +source = "git+https://github.com/storytold/craft-libs?rev=f0ad6c1e1d297908915f4be315b0fcfc9be7b5d4#f0ad6c1e1d297908915f4be315b0fcfc9be7b5d4" +dependencies = [ + "thiserror 2.0.21", +] + [[package]] name = "craft-pdf" version = "0.1.0" @@ -2421,8 +2429,8 @@ dependencies = [ [[package]] name = "filmcraft-aac" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "filmcraft-bitstream", "thiserror 2.0.21", @@ -2430,16 +2438,16 @@ dependencies = [ [[package]] name = "filmcraft-ac3" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "thiserror 2.0.21", ] [[package]] name = "filmcraft-apv" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "filmcraft-bitstream", "rayon", @@ -2448,8 +2456,8 @@ dependencies = [ [[package]] name = "filmcraft-av1" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "filmcraft-bitstream", "rayon", @@ -2458,23 +2466,26 @@ dependencies = [ [[package]] name = "filmcraft-avi" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" [[package]] name = "filmcraft-bitstream" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" +dependencies = [ + "craft-media", +] [[package]] name = "filmcraft-cfb" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" [[package]] name = "filmcraft-codecs" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "filmcraft-aac", "filmcraft-ac3", @@ -2510,16 +2521,16 @@ dependencies = [ [[package]] name = "filmcraft-color" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "serde", ] [[package]] name = "filmcraft-dng" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "rayon", "thiserror 2.0.21", @@ -2527,8 +2538,8 @@ dependencies = [ [[package]] name = "filmcraft-dnx" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "filmcraft-bitstream", "rayon", @@ -2537,8 +2548,8 @@ dependencies = [ [[package]] name = "filmcraft-frame" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "filmcraft-color", "filmcraft-time", @@ -2548,16 +2559,16 @@ dependencies = [ [[package]] name = "filmcraft-geom" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "serde", ] [[package]] name = "filmcraft-h264" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "filmcraft-bitstream", "rayon", @@ -2566,8 +2577,8 @@ dependencies = [ [[package]] name = "filmcraft-h264enc" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "filmcraft-bitstream", "rayon", @@ -2576,8 +2587,8 @@ dependencies = [ [[package]] name = "filmcraft-hevc" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "filmcraft-bitstream", "rayon", @@ -2586,8 +2597,8 @@ dependencies = [ [[package]] name = "filmcraft-interchange" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "filmcraft-cfb", "filmcraft-color", @@ -2603,22 +2614,24 @@ dependencies = [ [[package]] name = "filmcraft-isobmff" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ - "filmcraft-bitstream", - "thiserror 2.0.21", + "craft-media", ] [[package]] name = "filmcraft-matroska" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" +dependencies = [ + "craft-media", +] [[package]] name = "filmcraft-media" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "filmcraft-color", "filmcraft-dng", @@ -2632,8 +2645,8 @@ dependencies = [ [[package]] name = "filmcraft-mpa" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "filmcraft-bitstream", "thiserror 2.0.21", @@ -2641,8 +2654,8 @@ dependencies = [ [[package]] name = "filmcraft-mpeg2v" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "rayon", "thiserror 2.0.21", @@ -2650,31 +2663,34 @@ dependencies = [ [[package]] name = "filmcraft-mpegts" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "thiserror 2.0.21", ] [[package]] name = "filmcraft-mxf" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" +dependencies = [ + "chrono", +] [[package]] name = "filmcraft-ogg" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" [[package]] name = "filmcraft-opus" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" [[package]] name = "filmcraft-project" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "filmcraft-color", "filmcraft-geom", @@ -2687,8 +2703,8 @@ dependencies = [ [[package]] name = "filmcraft-prores" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "filmcraft-bitstream", "rayon", @@ -2697,24 +2713,24 @@ dependencies = [ [[package]] name = "filmcraft-time" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "serde", ] [[package]] name = "filmcraft-vp8" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "thiserror 2.0.21", ] [[package]] name = "filmcraft-vp9" -version = "0.8.0" -source = "git+https://github.com/storytold/filmcraft?rev=09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5#09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" +version = "0.12.0" +source = "git+https://github.com/storytold/filmcraft?rev=d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5#d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" dependencies = [ "filmcraft-bitstream", "rayon", diff --git a/Cargo.toml b/Cargo.toml index e512ecf15..8ffe7b988 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,29 +84,29 @@ tempfile = "3.27" # media codecs + containers: FilmCraft's pure-Rust crates, pinned to a revision (plan/adr/0001). # Only `crates/media`, `crates/export` and `crates/interchange` may depend on these. -filmcraft-codecs = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } -filmcraft-media = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } -filmcraft-frame = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } -filmcraft-color = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } -filmcraft-time = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } +filmcraft-codecs = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } +filmcraft-media = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } +filmcraft-frame = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } +filmcraft-color = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } +filmcraft-time = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } # encoders + muxer (crates/export) -filmcraft-h264enc = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } -filmcraft-prores = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } -filmcraft-aac = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } -filmcraft-isobmff = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } +filmcraft-h264enc = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } +filmcraft-prores = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } +filmcraft-aac = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } +filmcraft-isobmff = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } # container headers read for AAC channel layouts (crates/media) -filmcraft-matroska = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } -filmcraft-mpegts = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } +filmcraft-matroska = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } +filmcraft-mpegts = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } # decoder used as the round-trip oracle of effectcraft-opusenc (crates/export dev-dependency) -filmcraft-opus = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } -filmcraft-vp9 = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } +filmcraft-opus = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } +filmcraft-vp9 = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } # decoders used as the round-trip oracles of effectcraft-hevcenc / effectcraft-av1enc (dev-dependencies only) -filmcraft-hevc = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } -filmcraft-av1 = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } +filmcraft-hevc = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } +filmcraft-av1 = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } # timeline interchange (EDL, FCP7 XML, FCPXML, OTIO) for Premiere Pro interop (crates/interchange only) -filmcraft-interchange = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } -filmcraft-project = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } -filmcraft-geom = { git = "https://github.com/storytold/filmcraft", rev = "09aaf9bf15d510eda1c413c0ccabb1d7e08b41c5" } +filmcraft-interchange = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } +filmcraft-project = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } +filmcraft-geom = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" } # gpu (the same wgpu eframe / egui-wgpu use) wgpu = { version = "30.0.1", features = ["fragile-send-sync-non-atomic-wasm"] } diff --git a/ROADMAP.md b/ROADMAP.md index dff795d85..e41cbe2f1 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **Stage: alpha** · next: beta, ~30 points of "ready for real work" and ~350–620 h away -> **Last reviewed:** 2026-10-22 · **Last updated:** 2026-10-30 · **Change:** minor (features of 30 October in the progress log: text Line Join, Timeline drag-reorder and multi-select of effects, viewer edge pan, ScriptUI drag capture and dock size; previously: localization recount after #1774) · **Target:** Adobe After Effects 2026 (26.5.0.89) +> **Last reviewed:** 2026-10-22 · **Last updated:** 2026-11-05 · **Change:** minor (ProRes alpha final-run conformance; previously: features of 30 October in the progress log: text Line Join, Timeline drag-reorder and multi-select of effects, viewer edge pan, ScriptUI drag capture and dock size; previously: localization recount after #1774) · **Target:** Adobe After Effects 2026 (26.5.0.89) EffectCraft aims to do what After Effects does, with the same panels, menus and behaviour, written from scratch in Rust. This page is the one-page summary; the evidence is in @@ -117,6 +117,10 @@ Come tell us what matters most to you on [Discord](https://discord.gg/artcraft). ## Progress log +- 2026-11-05: ProRes 4444 and XQ exports write the required run after the final alpha + difference. The FilmCraft encoder pin fixes alpha movies rejected by native macOS + decoding; an export regression checks the stored bytes for both profiles, alpha modes, + and motion-blur settings (#293). Resolve itself still needs confirmation. - 2026-10-30: Text strokes take a Line Join (Miter, Round or Bevel) in the Character panel, Miter for new text as in After Effects (#2077). In the Timeline, effects, masks and shape items drag to a new place in their stack (#2210) and Shift-click adds them to the selection, so one diff --git a/crates/export/tests/it/movie_bytes.rs b/crates/export/tests/it/movie_bytes.rs index c72ec5427..76b89d1c7 100644 --- a/crates/export/tests/it/movie_bytes.rs +++ b/crates/export/tests/it/movie_bytes.rs @@ -95,8 +95,9 @@ fn export_bytes(format: OutputFormat, name: &str) -> Vec { fn h264_movie_bytes_are_pinned() { let data = export_bytes(OutputFormat::H264, "/out/pinned.mp4"); // FilmCraft 09aaf9b's encoder lists its settings in the first frame's user-data SEI (211 - // bytes where 0.1.1's name took 50): 161 bytes more than the FilmCraft 2c5ba7a pin. - assert_eq!((data.len(), fnv1a(&data)), (104_341, 17_655_680_090_264_218_914), "H.264 MP4"); + // bytes where 0.1.1's name took 50): 161 bytes more than the FilmCraft 2c5ba7a pin. The encoder's + // version string "0.8.0" -> "0.12.0" in that SEI adds one more byte (and one to the size fields). + assert_eq!((data.len(), fnv1a(&data)), (104_342, 17_327_612_315_319_115_693), "H.264 MP4"); } #[test] diff --git a/crates/export/tests/it/roundtrip.rs b/crates/export/tests/it/roundtrip.rs index 3ad174691..8531eed54 100644 --- a/crates/export/tests/it/roundtrip.rs +++ b/crates/export/tests/it/roundtrip.rs @@ -10,7 +10,7 @@ use std::path::{Path, PathBuf}; use std::process::Command; use effectcraft_color::Label; -use effectcraft_export::render_queue::{AudioOutput, Channels, OutputFormat, OutputModule, ProResProfile, RenderSettings, TimeSpan, sequence_path}; +use effectcraft_export::render_queue::{AlphaMode, AudioOutput, Channels, OutputFormat, OutputModule, ProResProfile, RenderSettings, TimeSpan, sequence_path}; use effectcraft_export::{Job, Progress, export}; use effectcraft_media::MediaPool; use effectcraft_project::{Comp, ItemId, ItemKind, LayerSource, Project, Solid, build}; @@ -70,6 +70,7 @@ fn settings() -> RenderSettings { RenderSettings { time_span: TimeSpan::LengthOfComp, ..Default::default() } } +/// Exports the standard test comp and checks progress for all [`FRAMES`] frames. fn run(p: &Project, cid: ItemId, footage: &dyn FootageSource, om: &OutputModule, path: &Path) -> effectcraft_export::Report { run_frames(p, cid, footage, om, path, FRAMES) } @@ -251,6 +252,65 @@ fn prores_4444_with_alpha() { } } +/// Checks stored alpha run codewords in exported MOVs across both 4444 profiles, +/// alpha modes and motion-blur settings, without relying on permissive decoders. +#[test] +fn prores_alpha_final_run_is_present_in_exported_movies() { + let dir = out_dir("prores-alpha-final-run"); + let png = dir.join("alpha.png"); + // One macroblock with 255 transparent pixels followed by one opaque pixel. + // The last alpha difference is -1 (modulo 65536), requiring a singleton run. + let mut pixels = image::RgbaImage::new(16, 16); + pixels.put_pixel(15, 15, image::Rgba([255, 80, 20, 255])); + pixels.save(&png).expect("synthetic alpha PNG"); + + for profile in [ProResProfile::P4444, ProResProfile::P4444Xq] { + for alpha_mode in [AlphaMode::Straight, AlphaMode::Premultiplied] { + for motion_blur in [false, true] { + let mut project = Project::default(); + let rate = FrameRate::FPS_30; + let mut comp = Comp::new(16, 16, rate, rate.tick_of(1)); + comp.enable_motion_blur = motion_blur; + let footage = effectcraft_media::probe(&png).expect("probe synthetic PNG"); + let item = project.add_item("Alpha", Label::SeaFoam, None, ItemKind::Footage(footage)); + let mut layer = build::layer(&mut project, &comp, "Alpha", LayerSource::Footage { item }, (16, 16), None); + layer.switches.motion_blur = motion_blur; + comp.layers.push(layer); + let comp = project.add_item("Alpha", Label::Sandstone, None, ItemKind::Comp(comp.into())); + let output = OutputModule { + channels: Channels::Rgba, + prores_profile: profile, + alpha_mode, + audio: AudioOutput::Off, + ..OutputModule::for_format(OutputFormat::ProRes) + }; + let path = dir.join(format!("{profile:?}-{alpha_mode:?}-blur-{motion_blur}.mov")); + run_frames(&project, comp, &MediaPool::new(), &output, &path, 1); + + let bytes = std::fs::read(&path).expect("exported MOV"); + let movie = filmcraft_isobmff::open(bytes.as_slice()).expect("demux exported MOV"); + let video = movie.tracks.iter().find(|track| track.kind == filmcraft_isobmff::TrackKind::Video).expect("video track"); + assert_eq!(video.samples.len(), 1); + let sample = &video.samples[0]; + let frame = &bytes[sample.offset as usize..][..sample.size as usize]; + let (header, start, size) = filmcraft_prores::FrameHeader::parse(frame).expect("ProRes header"); + assert_eq!((header.width, header.height), (16, 16)); + let picture = &frame[start..size]; + let header = filmcraft_prores::PictureHeader::parse(picture).expect("picture header"); + assert_eq!(header.num_slices, 1); + let slice = &picture[header.header_size + 2..]; + let color_bytes: usize = slice[2..8].as_chunks::<2>().0.iter().map(|b| u16::from_be_bytes(*b) as usize).sum(); + let alpha = &slice[(slice[0] >> 3) as usize + color_bytes..]; + // RDD 36:2022 Tables 12–14: +1, run 255, -1, run 1, alignment. + // The old encoder stopped after -1, giving [00 00 fe 01]. Its + // own decoder and FFmpeg accept that truncated payload, so a + // round-trip alone cannot enforce the native decoder's syntax. + assert_eq!(alpha, [0x00, 0x00, 0xfe, 0x01, 0x80], "{profile:?}, {alpha_mode:?}, blur={motion_blur}"); + } + } + } +} + fn check_still_dir(dir: &Path, ext: &str, tol: f32, alpha: bool) { for k in 0..FRAMES { let path = sequence_path(&dir.join(format!("f_[#####].{ext}")).to_string_lossy(), k as i64); diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index 5ef21511d..1dc95e3a6 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -1,6 +1,6 @@ # File format parity -> **Last reviewed:** 2026-10-19 · **Last updated:** 2026-10-19 · **Change:** `.aep` / `.aepx` import (`crates/aep`: `container`, `import`) · **Target:** Adobe After Effects 2026 +> **Last reviewed:** 2026-10-19 · **Last updated:** 2026-11-05 · **Change:** ProRes alpha final-run conformance; previously `.aep` / `.aepx` import (`crates/aep`: `container`, `import`) · **Target:** Adobe After Effects 2026 Every format After Effects reads or writes, with EffectCraft's support. After Effects' list comes from its installed bundle (26.5.0.89: `Info.plist` `CFBundleDocumentTypes`, `Plug-ins/Format`, @@ -62,7 +62,7 @@ trips and renders, checked by tests, not against After Effects), **partial** (na | MP4 / MOV with H.264 | R/W | yes | yes (own encoder, + AAC) | decoder bit-exact on conformance streams (FilmCraft) | | HEVC (H.265) | R/W | yes | yes (MP4 `hvc1`, Main / Main 10) | encoder bit-exact with ffmpeg's decode; no B-frames / SAO, larger files | | AV1 | R | yes | yes (MP4 `av01`, WebM) | beyond After Effects' export; no B-frames / CDEF | -| Apple ProRes (422 family, 4444) | R/W | yes | yes | good; 4444 colour tag wrong (#340); no ProRes RAW | +| Apple ProRes (422 family, 4444) | R/W | yes | yes | alpha final-run syntax checked in exported MOVs (#293); 4444 colour tag wrong (#340); no ProRes RAW | | WebM / Matroska (VP9, AV1, Opus) | R (limited) | yes, incl. VP9 alpha | yes | beyond After Effects | | MXF (OP1a / OP-Atom) | R/W | yes (FilmCraft's demuxer: AVC, MPEG-2 incl. XDCAM / D-10, VC-3 / DNxHD, ProRes; PCM audio) | none | partial: import (#767), checked against ffmpeg; no MXF output | | AVI | R | yes (FilmCraft's demuxer: AVI 1.0 / OpenDML; Motion JPEG, H.264, HEVC, uncompressed; PCM, MP3, MP2, AC-3 audio) | none | partial: import (#1701), checked against ffmpeg; H.264 needs its parameter sets in the chunks (global headers and copies with an `avcC` record don't decode until storytold/filmcraft#2303), and a damaged video index loses the video (storytold/filmcraft#2302); DV, MPEG-4 Part 2 (DivX / Xvid) and other legacy codecs import but show why they have no picture; no AVI output | @@ -146,6 +146,7 @@ relinking 6–12 h, `.mogrt`-compatible templates 15–30 h (partly parallel wit | Date | Change | Summary | |---|---|---| +| 2026-11-05 | minor | ProRes encoder pin and export regression for final alpha runs; native macOS decode validated, Resolve still unverified (#293) | | 2026-10-30 | minor | Render-queue source-protection behavior and limits consolidated here from the standalone page | | 2026-10-12 | minor | #324 and #514 marked closed; open `.aep` PRs #251 and #860 noted | | 2026-10-10 | major | First file format parity document, from the After Effects 26.5 bundle and this repository's import/export registries |