mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(producer): audioPadTrim FFmpeg-8.x-compatible apad invocation
The `audioPadTrim` module's pad-concat step generates a concat script
whose file directives use `file://` URLs (built via Node's
`pathToFileURL`). FFmpeg 8.x on Windows rejects these with
"Impossible to open file:///C:/…" — its `file:` protocol handler strips
the scheme leaving `///C:/…`, which Windows path parsing then rejects.
Field-signal (4 reports over ~24h, all win32/x64, CLI 0.7.59):
- ts=1784169914 (Baoyu, 60s render, native audio assembly failed)
- ts=1784177061 (andre 22cores, 345.87s composition, 9 WAV audio elements)
- ts=1784177375 (KEY DIAGNOSTIC: 13 mono 44.1kHz mp3 tracks, ffmpeg
8.1.1-full_build gyan.dev, "same project rendered fine in July with
an older ffmpeg"; manual `ffmpeg -i track.mp3 -af apad=whole_dur=16
-t 16 -c:a aac out.aac` works with the same binary, so the tool's
audioPadTrim invocation is the incompatible part)
- ts=1784177375 (duplicate reporter follow-up)
The concat approach itself is fine — the sibling concat scripts in
`assemble.ts` and `chunkEncoder.ts` pass raw paths (no `pathToFileURL`)
and work on Windows. `audioPadTrim.ts` was the outlier introduced in
PR #1615 (2026-06-20). Aligns with the codebase convention.
Regression pin: unit test asserts the pad-concat stdin never contains
the `file://` scheme, including for a Windows-shaped input path.
End-to-end verification requires a Windows + FFmpeg 8.x reviewer; the
unit test snapshots the arg shape.
Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
— Via
This commit is contained in:
@@ -47,8 +47,13 @@ describe("buildPadTrimAudioArgs", () => {
|
|||||||
expect(concatArgs[concatArgs.indexOf("-i") + 1]).toBe("pipe:0");
|
expect(concatArgs[concatArgs.indexOf("-i") + 1]).toBe("pipe:0");
|
||||||
expect(concatArgs[concatArgs.indexOf("-c:a") + 1]).toBe("copy");
|
expect(concatArgs[concatArgs.indexOf("-c:a") + 1]).toBe("copy");
|
||||||
expect(concatArgs[concatArgs.length - 1]).toBe("/tmp/out.aac");
|
expect(concatArgs[concatArgs.length - 1]).toBe("/tmp/out.aac");
|
||||||
expect(plan.steps[1]!.stdin).toContain("file 'file:///tmp/in.aac'");
|
// Concat script MUST use bare paths, NOT `file://` URLs. FFmpeg 8.x
|
||||||
expect(plan.steps[1]!.stdin).toContain("file 'file:///tmp/out.aac.pad-silence.aac'");
|
// on Windows can't open `file:///C:/…` URLs from the concat demuxer
|
||||||
|
// (field-signal ts=1784169914 / 1784177061 / 1784177375). Regression
|
||||||
|
// pin: the `file://` scheme prefix must never appear in the stdin.
|
||||||
|
expect(plan.steps[1]!.stdin).toContain("file '/tmp/in.aac'");
|
||||||
|
expect(plan.steps[1]!.stdin).toContain("file '/tmp/out.aac.pad-silence.aac'");
|
||||||
|
expect(plan.steps[1]!.stdin).not.toContain("file://");
|
||||||
expect(plan.cleanupPaths).toEqual(["/tmp/out.aac.pad-silence.aac"]);
|
expect(plan.cleanupPaths).toEqual(["/tmp/out.aac.pad-silence.aac"]);
|
||||||
|
|
||||||
const reencodedSourceStep = plan.steps.find(
|
const reencodedSourceStep = plan.steps.find(
|
||||||
@@ -106,6 +111,31 @@ describe("buildPadTrimAudioArgs", () => {
|
|||||||
const trimNeeded = buildPadTrimAudioArgs("/tmp/a.aac", "/tmp/o.aac", 5.002, 5.0);
|
const trimNeeded = buildPadTrimAudioArgs("/tmp/a.aac", "/tmp/o.aac", 5.002, 5.0);
|
||||||
expect(trimNeeded.operation).toBe("trim");
|
expect(trimNeeded.operation).toBe("trim");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not emit `file://` URLs in the pad-concat stdin (FFmpeg 8.x Windows compat)", () => {
|
||||||
|
// Regression pin for field-signal reports ts=1784169914 / 1784177061 /
|
||||||
|
// ts=1784177375 (win32/x64, CLI 0.7.59, ffmpeg 8.1.1-full_build). The
|
||||||
|
// concat demuxer's file open on Windows in FFmpeg 8.x rejects
|
||||||
|
// `file:///C:/…` URLs with "Impossible to open …". The concat script
|
||||||
|
// MUST use bare paths. Match sibling `assemble.ts` /
|
||||||
|
// `chunkEncoder.ts` conventions.
|
||||||
|
const winPlan = buildPadTrimAudioPlan(
|
||||||
|
"C:\\Users\\alice\\AppData\\Local\\Temp\\hf-render-abc\\audio.aac",
|
||||||
|
"C:\\Users\\alice\\AppData\\Local\\Temp\\hf-render-abc\\audio-padded.aac",
|
||||||
|
4.0,
|
||||||
|
5.0,
|
||||||
|
);
|
||||||
|
expect(winPlan.operation).toBe("pad");
|
||||||
|
const concatStep = winPlan.steps.find((s) => s.kind === "pad-concat");
|
||||||
|
expect(concatStep).toBeDefined();
|
||||||
|
expect(concatStep!.stdin).toBeDefined();
|
||||||
|
expect(concatStep!.stdin).not.toContain("file://");
|
||||||
|
expect(concatStep!.stdin).not.toContain("file:\\\\");
|
||||||
|
// Bare Windows paths appear as-is in the concat directives.
|
||||||
|
expect(concatStep!.stdin).toContain(
|
||||||
|
"file 'C:\\Users\\alice\\AppData\\Local\\Temp\\hf-render-abc\\audio.aac'",
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("padOrTrimAudioToVideoFrameCount", () => {
|
describe("padOrTrimAudioToVideoFrameCount", () => {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
|
|
||||||
import { spawn } from "node:child_process";
|
import { spawn } from "node:child_process";
|
||||||
import { rmSync } from "node:fs";
|
import { rmSync } from "node:fs";
|
||||||
import { pathToFileURL } from "node:url";
|
|
||||||
import {
|
import {
|
||||||
extractAudioMetadata,
|
extractAudioMetadata,
|
||||||
formatFfmpegError,
|
formatFfmpegError,
|
||||||
@@ -231,8 +230,17 @@ function channelLayoutForChannels(channels: number | undefined): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function concatFileLine(path: string): string {
|
function concatFileLine(path: string): string {
|
||||||
const normalized = pathToFileURL(path).href;
|
// Bare paths in concat directives — NOT `file://` URLs. FFmpeg 8.x on
|
||||||
return `file '${normalized.replace(/'/g, "'\\''")}'`;
|
// Windows fails to open URL-form paths from the concat demuxer with
|
||||||
|
// "Impossible to open file:///C:/…" (its file protocol strips the
|
||||||
|
// `file:` prefix leaving `///C:/…`, which Windows path parsing then
|
||||||
|
// rejects). Field-signal reports (ts=1784169914 / 1784177061 /
|
||||||
|
// 1784177375, all win32/x64 CLI 0.7.59; the last isolated the module's
|
||||||
|
// arg shape vs a working manual `apad=whole_dur` command). Bare paths
|
||||||
|
// also match the convention already used by the sibling concat
|
||||||
|
// scripts in `assemble.ts` and `chunkEncoder.ts`. The single-quote
|
||||||
|
// escaping (`'\''`) is the concat demuxer's own escape rule.
|
||||||
|
return `file '${path.replace(/'/g, "'\\''")}'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user