Merge pull request #357 from HEOJUNFO/fix-317-aiff-float

Export: AIFF 32-bit float writes AIFF-C fl32 instead of 24-bit (#317)
This commit is contained in:
Brandon Thomas
2026-10-09 09:31:38 -04:00
3 changed files with 43 additions and 11 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
//! | WebM | WebM (Matroska) | VP9 profile 0 (`effectcraft-vp9enc`: key + inter frames, a key frame every 2 s, loop filter), 8-bit 4:2:0; or AV1 (`effectcraft-av1enc`) | VP9 alpha (BlockAdditional) | Opus (`effectcraft-opusenc`: SILK / hybrid / CELT by bitrate and application), 48 kHz |
//! | HEVC | MP4 (`hvc1`) | `effectcraft-hevcenc` Main / Main 10 4:2:0, I + P slices, bitrate or constant quality, level, key-frame interval | no | AAC-LC (FilmCraft) |
//! | AV1 | MP4 (`av01`) | `effectcraft-av1enc` Main 8/10-bit 4:2:0, key + inter frames | no | AAC-LC (FilmCraft) |
//! | WAV / AIFF | RIFF WAVE / AIFF | — | — | PCM 16/24-bit (WAV also 32-bit float), mono/stereo (audio only) |
//! | WAV / AIFF | RIFF WAVE / AIFF (AIFF-C for float) | — | — | PCM 16/24-bit or 32-bit float, mono/stereo (audio only) |
//!
//! Every frame goes through [`pipeline`]: the Render Settings overrides (Effects, Solo Switches,
//! Guide Layers, Frame Blending, Motion Blur, Color Depth), field rendering (two fields half a
+24 -9
View File
@@ -9,8 +9,8 @@
//! frames every two seconds, inter frames between them: SimpleBlocks carry the key flag, inter
//! BlockGroups a ReferenceBlock; the alpha stream's key frames line up with the colour stream's),
//! Opus from `effectcraft-opusenc` (CELT, 48 kHz stereo, 20 ms packets,
//! `CodecDelay` = pre-skip). WAV (RIFF, 16-bit PCM) and AIFF (big-endian 16-bit, 80-bit
//! extended sample rate) follow their published file layouts.
//! `CodecDelay` = pre-skip). WAV (RIFF, PCM or IEEE float) and AIFF (big-endian PCM, 80-bit
//! extended sample rate; AIFF-C `fl32` for float) follow their published file layouts.
use std::io::{Seek, SeekFrom, Write};
@@ -322,14 +322,11 @@ fn ieee_extended(v: f64) -> [u8; 10] {
}
/// WAV or AIFF: the comp's audio over the render span (Audio Output: sample rate, mono/stereo,
/// 16/24-bit integer or 32-bit float; AIFF has no float in plain AIFF, so it writes 24-bit).
/// 16/24-bit integer or 32-bit float; plain AIFF has no float, so 32-bit float is AIFF-C `fl32`).
pub(crate) fn audio_file(job: &Cx, comp: &Comp, aiff: bool, st: &mut State) -> Result<Report> {
let sr = job.output.audio_sample_rate.clamp(8_000, 192_000);
let channels = if job.output.audio_channels == 1 { 1u16 } else { 2 };
let fmt = match job.output.audio_format {
AudioFormat::F32 if aiff => AudioFormat::S24,
f => f,
};
let fmt = job.output.audio_format;
let bytes_per_sample: u16 = match fmt {
AudioFormat::S16 => 2,
AudioFormat::S24 => 3,
@@ -363,15 +360,33 @@ pub(crate) fn audio_file(job: &Cx, comp: &Comp, aiff: bool, st: &mut State) -> R
let frames = (pcm.len() / block_align as usize) as u32;
let mut out = vec![];
if aiff {
// AIFF-C (Apple, 1991) for float: a format version chunk, and the COMM chunk names the
// compression type and its Pascal-string name, padded to an even length.
let float = fmt == AudioFormat::F32;
let mut comm = vec![];
comm.extend_from_slice(&channels.to_be_bytes());
comm.extend_from_slice(&frames.to_be_bytes());
comm.extend_from_slice(&(bytes_per_sample * 8).to_be_bytes());
comm.extend_from_slice(&ieee_extended(sr as f64));
let mut fver = vec![];
if float {
const NAME: &[u8] = b"32-bit floating point";
comm.extend_from_slice(b"fl32");
comm.push(NAME.len() as u8);
comm.extend_from_slice(NAME);
if (1 + NAME.len()) % 2 == 1 {
comm.push(0);
}
fver.extend_from_slice(b"FVER");
fver.extend_from_slice(&4u32.to_be_bytes());
// AIFC Version 1 (May 23, 1990, 2:40 pm).
fver.extend_from_slice(&0xA280_5140u32.to_be_bytes());
}
let ssnd_len = 8 + pcm.len();
out.extend_from_slice(b"FORM");
out.extend_from_slice(&((4 + 8 + comm.len() + 8 + ssnd_len + ssnd_len % 2) as u32).to_be_bytes());
out.extend_from_slice(b"AIFF");
out.extend_from_slice(&((4 + fver.len() + 8 + comm.len() + 8 + ssnd_len + ssnd_len % 2) as u32).to_be_bytes());
out.extend_from_slice(if float { b"AIFC" } else { b"AIFF" });
out.extend_from_slice(&fver);
out.extend_from_slice(b"COMM");
out.extend_from_slice(&(comm.len() as u32).to_be_bytes());
out.extend_from_slice(&comm);