From 78b4fe4170c998cf21dce86f98fcdb5c7376c91d Mon Sep 17 00:00:00 2001 From: Marco Arnulfo Date: Wed, 14 Oct 2026 13:44:59 +0200 Subject: [PATCH] Import: AIFF files import, including EffectCraft's own AIFF renders (fixes #1428) --- Cargo.lock | 1 + Cargo.toml | 3 +++ crates/export/tests/roundtrip.rs | 25 +++++++++++++++++++++++++ crates/media/Cargo.toml | 2 ++ 4 files changed, 31 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 38999ed0..195b59d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1440,6 +1440,7 @@ dependencies = [ "log", "rayon", "serde_json", + "symphonia", "thiserror 2.0.21", ] diff --git a/Cargo.toml b/Cargo.toml index 47c8d417..e64595cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,6 +91,9 @@ filmcraft-av1 = { git = "https://github.com/storytold/filmcraft", rev = "2c5ba7a filmcraft-interchange = { git = "https://github.com/storytold/filmcraft", rev = "2c5ba7a72619935f0481f99d59409e0d1844b234" } filmcraft-project = { git = "https://github.com/storytold/filmcraft", rev = "2c5ba7a72619935f0481f99d59409e0d1844b234" } filmcraft-geom = { git = "https://github.com/storytold/filmcraft", rev = "2c5ba7a72619935f0481f99d59409e0d1844b234" } +# The symphonia filmcraft-codecs already builds, named only to turn on its AIFF reader: the pinned +# rev enables wav alone (FilmCraft main enables aiff too). Drop it when the pin moves past that. +symphonia = { version = "0.5.5", default-features = false, features = ["aiff"] } # gpu (the same wgpu eframe / egui-wgpu use) wgpu = { version = "30.0.1", features = ["fragile-send-sync-non-atomic-wasm"] } diff --git a/crates/export/tests/roundtrip.rs b/crates/export/tests/roundtrip.rs index 94b83991..0fc74701 100644 --- a/crates/export/tests/roundtrip.rs +++ b/crates/export/tests/roundtrip.rs @@ -613,3 +613,28 @@ fn wav_and_aiff_audio_only() { } } } + +/// An AIFF EffectCraft renders imports back (#1428): plain 16-bit and AIFF-C `fl32`. +#[test] +fn rendered_aiff_imports_back() { + use effectcraft_export::render_queue::AudioFormat; + let d = out_dir("aiff-import"); + let wav = d.join("tone.wav"); + write_tone(&wav, 2.0); + let (p, cid) = project(Some(&wav)); + let pool = MediaPool::new(); + for (format, name) in [(AudioFormat::S16, "s16.aif"), (AudioFormat::F32, "f32.aif")] { + // 44.1 kHz, not the 48 kHz the samples are read back at: a wrong rate shows in the duration. + let om = OutputModule { audio_sample_rate: 44_100, audio_channels: 2, audio_format: format, ..OutputModule::for_format(OutputFormat::Aiff) }; + let path = d.join(name); + assert!(run(&p, cid, &pool, &om, &path).audio); + let f = effectcraft_media::probe(&path).unwrap_or_else(|e| panic!("{name}: {e}")); + assert!(f.has_audio && !f.has_video, "{name}"); + assert_eq!(f.duration, Tick::from_seconds_f64(1.2), "{name}: 52 920 frames at 44.1 kHz"); + // Tone on the left, silence on the right: the two channels are read as two. + let s = MediaPool::new().audio_samples(&f, Tick::from_seconds_f64(0.3), 24_000, 48_000); + let (l, r) = (rms(s.iter().step_by(2).copied()), rms(s.iter().skip(1).step_by(2).copied())); + assert!((l - 0.3536).abs() < 0.01, "{name}: left RMS {l}"); + assert!(r < 0.01, "{name}: right RMS {r}"); + } +} diff --git a/crates/media/Cargo.toml b/crates/media/Cargo.toml index a3ff624b..bb47dccb 100644 --- a/crates/media/Cargo.toml +++ b/crates/media/Cargo.toml @@ -24,6 +24,8 @@ filmcraft-isobmff = { workspace = true } filmcraft-frame = { workspace = true } filmcraft-color = { workspace = true } filmcraft-time = { workspace = true } +# AIFF import (#1428): enables the aiff reader of filmcraft-codecs' symphonia. +symphonia = { workspace = true } exr = { workspace = true } gif = { workspace = true } image = { workspace = true, features = ["gif", "webp", "tiff", "bmp", "exr", "tga", "hdr"] }