mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
test(engine): read what ffmpeg actually writes for pcm_f32le
The float fixtures beside this are hand-built canonical 44-byte headers. ffmpeg's pcm_f32le writes an 18-byte `fmt ` chunk plus a `fact` chunk, putting `data` at offset 92 — so every float assertion here would still have passed if the parser could not read a real sub-mix at all. An unreadable file returns false, which the caller reads as "no automation here" and drops the group's envelope silently. Verified empirically first: the tag is 3 (WAVE_FORMAT_IEEE_FLOAT), not 0xFFFE EXTENSIBLE, and the data offset lands 4-aligned. The parser was already right; nothing here was covering it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c1a9390024
commit
29ed258c27
@@ -1,7 +1,9 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { getFfmpegBinary } from "../utils/ffmpegBinaries.js";
|
||||
import { applyVolumeEnvelopeToWav } from "./audioVolumeEnvelope.js";
|
||||
|
||||
const SAMPLE_RATE = 48000;
|
||||
@@ -244,5 +246,70 @@ describe("applyVolumeEnvelopeToWav", () => {
|
||||
expect(floatSampleAt(path, Math.floor(SAMPLE_RATE / 2))).toBeCloseTo(0.7, 2);
|
||||
expect(floatSampleAt(path, SAMPLE_RATE - 1)).toBeCloseTo(0, 3);
|
||||
});
|
||||
|
||||
/**
|
||||
* The fixtures above are hand-built canonical 44-byte headers, which is NOT
|
||||
* what the group sub-mix actually hands this function: ffmpeg's `pcm_f32le`
|
||||
* writes an 18-byte `fmt ` chunk plus a `fact` chunk, putting `data` at
|
||||
* offset 92. Every assertion above would still pass if this function could
|
||||
* not read a real one — and an unreadable file returns false, which the
|
||||
* caller reads as "no automation here" and drops the group's envelope.
|
||||
*/
|
||||
it("reads what ffmpeg actually writes, not just a canonical header", () => {
|
||||
const path = join(tmp(), "ffmpeg-f32.wav");
|
||||
const made = spawnSync(
|
||||
getFfmpegBinary(),
|
||||
[
|
||||
"-nostdin",
|
||||
"-v",
|
||||
"error",
|
||||
"-f",
|
||||
"lavfi",
|
||||
"-i",
|
||||
"aevalsrc=0.5*sin(2*PI*440*t)|0.5*sin(2*PI*440*t):d=1:s=48000",
|
||||
"-acodec",
|
||||
"pcm_f32le",
|
||||
"-ar",
|
||||
"48000",
|
||||
path,
|
||||
],
|
||||
{ encoding: "utf-8" },
|
||||
);
|
||||
if (made.status !== 0) return; // no usable ffmpeg here
|
||||
|
||||
const before = readFileSync(path);
|
||||
// Non-canonical by construction: prove the fixture is the awkward shape.
|
||||
expect(before.readUInt32LE(16)).toBe(18); // fmt chunk size
|
||||
expect(before.readUInt16LE(20)).toBe(3); // WAVE_FORMAT_IEEE_FLOAT
|
||||
|
||||
expect(
|
||||
applyVolumeEnvelopeToWav(
|
||||
path,
|
||||
[
|
||||
{ time: 0, volume: 1 },
|
||||
{ time: 1, volume: 0 },
|
||||
],
|
||||
0,
|
||||
1,
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
// Locate `data` the way the parser does, then check the fade landed.
|
||||
let at = 12;
|
||||
let dataOffset = -1;
|
||||
const after = readFileSync(path);
|
||||
while (at + 8 <= after.length) {
|
||||
const id = after.toString("ascii", at, at + 4);
|
||||
const size = after.readUInt32LE(at + 4);
|
||||
if (id === "data") {
|
||||
dataOffset = at + 8;
|
||||
break;
|
||||
}
|
||||
at += 8 + size + (size % 2);
|
||||
}
|
||||
expect(dataOffset).toBeGreaterThan(44);
|
||||
// Faded to silence by the end (stereo float = 8 bytes per frame).
|
||||
expect(Math.abs(after.readFloatLE(dataOffset + (SAMPLE_RATE - 2) * 8))).toBeLessThan(0.02);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user