Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/packages/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1756,7 +1756,7 @@ How it uses the machine, and when it gives up:
| `--strict-variables` | off | Fail rather than warn on an undeclared or mistyped variable key |
| `--batch-concurrency` / `--batch-fail-fast` | `1` / off | Batch rows at once, and whether the first failure stops the rest |
| `--debug` | off | Keep intermediates and write diagnostics under the Producer `.debug` directory |
| `--quiet` | off | Less output |
| `--quiet` | off | Less output; warnings, including delivery audio gain reductions, remain visible |
| `--skill` | — | Record which authoring workflow started this render, in usage telemetry |

Low-memory detection reads **host** RAM, not cgroup or container limits, so
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { getFfmpegBinary, muxVideoWithAudio } from "@hyperframes/engine";
import { padOrTrimAudioToVideoFrameCount } from "./audioPadTrim.js";
import { runAudioStage } from "./stages/audioStage.js";

const dirs: string[] = [];
afterEach(() => dirs.splice(0).forEach((dir) => rmSync(dir, { recursive: true, force: true })));
Expand Down Expand Up @@ -45,6 +46,86 @@ const hasFfmpeg = (() => {
})();

describe.skipIf(!hasFfmpeg)("audio pad real-media packet contract", () => {
it.each([
{ frameCount: 91, fps: { num: 30, den: 1 } },
{ frameCount: 31, fps: { num: 30000, den: 1001 } },
])(
"copies frame-fitted AAC without another encode at $fps.num/$fps.den",
async ({ frameCount, fps }) => {
const dir = mkdtempSync(join(tmpdir(), "hf-aac-copy-"));
dirs.push(dir);
const source = join(dir, "source.wav");
const duration = (frameCount * fps.den) / fps.num;
execFileSync(getFfmpegBinary(), [
"-nostdin",
"-v",
"error",
"-f",
"lavfi",
"-i",
`aevalsrc=0.15*sin(2*PI*73*t)+0.02*sin(2*PI*997*t)|0.12*sin(2*PI*101*t)+0.03*sin(2*PI*1601*t):s=48000:d=${duration + 0.1}:c=stereo`,
"-c:a",
"pcm_f32le",
source,
]);
const mixed = await runAudioStage({
projectDir: dir,
workDir: dir,
compiledDir: dir,
totalFrames: frameCount,
fps,
ffmpegProcessTimeout: 30_000,
audioGain: 1,
audios: [
{
id: "stereo",
src: source,
start: 0,
end: duration,
mediaStart: 0,
volume: 1,
type: "audio",
},
],
abortSignal: undefined,
assertNotAborted: () => {},
});
expect(mixed.hasAudio, mixed.audioError).toBe(true);
const normalized = await padOrTrimAudioToVideoFrameCount({
audioPath: mixed.audioOutputPath,
outputPath: join(dir, "normalized.m4a"),
videoPath: join(dir, "video.mp4"),
probeVideoFrameInfo: async () => ({ frameCount, fpsNum: fps.num, fpsDen: fps.den }),
});
expect(normalized.success, normalized.error).toBe(true);
expect(normalized.operation).toBe("copy");
const packetHashes = (path: string) =>
execFileSync(
"ffprobe",
[
"-v",
"error",
"-select_streams",
"a:0",
"-show_packets",
"-show_data_hash",
"sha256",
"-show_entries",
"packet=data_hash",
"-of",
"csv=p=0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocker: this ffprobe argv has no "--" before path, so src/utils/ffprobeArgvContract.test.ts fails in the Producer unit lane. I ran it locally at this head: audioPadTrim.integration.test.ts terminates options immediately before the input fails with "csv=p=0" , path. The older probe in this same file already uses the terminator.

Suggested change
"csv=p=0",
"csv=p=0",
"--",

path,
],
{ encoding: "utf8" },
)
.split("\n")
.filter((line) => line.includes("SHA256:"));
const originalPackets = packetHashes(mixed.audioOutputPath);
expect(originalPackets.length).toBeGreaterThan(1);
expect(packetHashes(normalized.outputPath)).toEqual(originalPackets);
},
);

it("normalizes a tiny raw-ADTS pad without an oversized terminal packet", async () => {
const dir = mkdtempSync(join(tmpdir(), "hf-pad-"));
dirs.push(dir);
Expand Down
25 changes: 23 additions & 2 deletions packages/producer/src/services/render/stages/assembleStage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ vi.mock("../shared.js", () => ({
updateJobStatus: vi.fn(),
}));

import { runAssembleStage } from "./assembleStage.js";
import { recordLimiterAttenuation, runAssembleStage } from "./assembleStage.js";
import { createConsoleLogger } from "../../../logger.js";
import { EncoderInterruptedError } from "../encoderInterruption.js";

function makeInput(overrides: Partial<AssembleStageInput> = {}): AssembleStageInput {
Expand Down Expand Up @@ -316,7 +317,7 @@ describe("runAssembleStage limiter honesty", () => {
await runAssembleStage(input);

expect(input.job.audioLoweredDb).toBe(1.44);
expect(logger.info).toHaveBeenCalledWith("Audio lowered by 1.4 dB to stay under −1 dBTP", {
expect(logger.warn).toHaveBeenCalledWith("Audio lowered by 1.4 dB to stay under −1 dBTP", {
audioLoweredDb: 1.44,
});
});
Expand All @@ -326,4 +327,24 @@ describe("runAssembleStage limiter honesty", () => {
await runAssembleStage(input);
expect(input.job).not.toHaveProperty("audioLoweredDb");
});

it("keeps an audible gain change visible with the CLI's quiet logger", () => {
const warning = vi.spyOn(console, "warn").mockImplementation(() => {});
const info = vi.spyOn(console, "error").mockImplementation(() => {});
try {
const input = makeInput();
input.job.config.logger = createConsoleLogger("warn");

recordLimiterAttenuation(input.job, 1.44);

expect(warning).toHaveBeenCalledWith(
'[WARN] Audio lowered by 1.4 dB to stay under −1 dBTP {"audioLoweredDb":1.44}',
);
expect(input.job.audioLoweredDb).toBe(1.44);
expect(info).not.toHaveBeenCalled();
} finally {
warning.mockRestore();
info.mockRestore();
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function recordLimiterAttenuation(job: RenderJob, audioLoweredDb: number
if (audioLoweredDb === undefined) return;
job.audioLoweredDb = audioLoweredDb;
const ceiling = `−${Math.abs(AAC_DELIVERY_TRUE_PEAK_DBFS)} dBTP`;
(job.config.logger ?? defaultLogger).info(
(job.config.logger ?? defaultLogger).warn(
`Audio lowered by ${audioLoweredDb.toFixed(1)} dB to stay under ${ceiling}`,
{ audioLoweredDb },
);
Expand Down
Loading