mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-04 16:42:27 +00:00
fix(engine): fail partial audio track preparation (#2488)
This commit is contained in:
@@ -143,6 +143,61 @@ describe("processCompositionAudio", () => {
|
||||
expect(filter).toContain("[mixed]volume=3[out]");
|
||||
});
|
||||
|
||||
it("fails the audio result instead of silently mixing after one track preparation fails", async () => {
|
||||
const baseDir = mkdtempSync(join(tmpdir(), "hf-audio-base-"));
|
||||
const workDir = mkdtempSync(join(tmpdir(), "hf-audio-work-"));
|
||||
tempDirs.push(baseDir, workDir);
|
||||
|
||||
writeFileSync(join(baseDir, "working.wav"), "stub");
|
||||
writeFileSync(join(baseDir, "missing-cue.wav"), "stub");
|
||||
|
||||
const defaultImplementation = runFfmpegMock.getMockImplementation()!;
|
||||
runFfmpegMock.mockImplementation(async (args: string[]) => {
|
||||
const isMissingCuePrepare = args.includes(join(baseDir, "missing-cue.wav"));
|
||||
return {
|
||||
success: !isMissingCuePrepare,
|
||||
durationMs: 1,
|
||||
stderr: isMissingCuePrepare ? "Invalid data found when processing input" : "",
|
||||
exitCode: isMissingCuePrepare ? 1 : 0,
|
||||
};
|
||||
});
|
||||
|
||||
const result = await processCompositionAudio(
|
||||
[
|
||||
{
|
||||
id: "working",
|
||||
src: "working.wav",
|
||||
start: 0,
|
||||
end: 0.5,
|
||||
mediaStart: 0,
|
||||
layer: 0,
|
||||
volume: 1,
|
||||
type: "audio",
|
||||
},
|
||||
{
|
||||
id: "missing-cue",
|
||||
src: "missing-cue.wav",
|
||||
start: 3.859,
|
||||
end: 4.359,
|
||||
mediaStart: 0,
|
||||
layer: 1,
|
||||
volume: 1,
|
||||
type: "audio",
|
||||
},
|
||||
],
|
||||
baseDir,
|
||||
workDir,
|
||||
join(baseDir, "out.m4a"),
|
||||
5,
|
||||
);
|
||||
runFfmpegMock.mockImplementation(defaultImplementation);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.tracksProcessed).toBe(1);
|
||||
expect(result.error).toMatch(/Prepare failed: missing-cue/);
|
||||
expect(runFfmpegMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("uses frame-evaluated volume automation when keyframes are present", async () => {
|
||||
const baseDir = mkdtempSync(join(tmpdir(), "hf-audio-base-"));
|
||||
const workDir = mkdtempSync(join(tmpdir(), "hf-audio-work-"));
|
||||
|
||||
@@ -625,6 +625,25 @@ export async function processCompositionAudio(
|
||||
}),
|
||||
);
|
||||
|
||||
// Never turn a per-track preparation failure into a successful partial mix.
|
||||
// The producer only surfaces audio failures when `success` is false; mixing
|
||||
// the remaining tracks made the omitted cue indistinguishable from a valid
|
||||
// render unless someone manually audited that exact audio window.
|
||||
if (errors.length > 0) {
|
||||
try {
|
||||
rmSync(workDir, { recursive: true, force: true });
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
outputPath,
|
||||
durationMs: Date.now() - startMs,
|
||||
tracksProcessed: tracks.length,
|
||||
error: `Audio processing failed: ${errors.join(", ")}`,
|
||||
};
|
||||
}
|
||||
|
||||
const mixResult = await mixAudioTracks(tracks, outputPath, totalDuration, signal, config);
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user