mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 10:52:17 +03:00
FU-06 (Audio Bridge STT orchestration): one download, two extractions — takes already-downloaded video bytes and extracts bounded mono 16kHz PCM WAV via the loopback broker's new mode=audio operation, sharing the exact same queue/deadline/byte budgets as the frame path. Dual opt-in (operator setting default false + per-request), only reaches the STT call when both are on. Rebased onto the tip after sibling #12011 (subtitle mode) landed first, both touching the same broker route/client — combined additively so frames/audio/subtitles all share the one extractionQueue singleton. Re-validated: 59/59 focused tests pass.
141 lines
4.4 KiB
TypeScript
141 lines
4.4 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { writeFile } from "node:fs/promises";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
VIDEO_AUDIO_CHANNELS,
|
|
VIDEO_AUDIO_SAMPLE_RATE_HZ,
|
|
extractVideoAudioFromBytes,
|
|
} from "../../../src/lib/guardrails/videoBridgeAudioExtraction.ts";
|
|
import type { VideoCommandRunner } from "../../../src/lib/guardrails/videoBridgeRuntime.ts";
|
|
|
|
test("extracts bounded mono 16kHz WAV bytes and reuses the probed duration", async () => {
|
|
const calls: Array<{ executable: string; args: string[] }> = [];
|
|
const runner: VideoCommandRunner = async (executable, args) => {
|
|
calls.push({ executable, args: [...args] });
|
|
if (executable === "ffprobe") {
|
|
return {
|
|
stdout: JSON.stringify({
|
|
format: { duration: "4.5", format_name: "mp4" },
|
|
streams: [{ index: 0, codec_type: "video", width: 640, height: 360 }],
|
|
}),
|
|
stderr: "",
|
|
};
|
|
}
|
|
await writeFile(args.at(-1) ?? "", Buffer.from("RIFF....WAVEfmt "));
|
|
return { stdout: "", stderr: "" };
|
|
};
|
|
|
|
const result = await extractVideoAudioFromBytes(Buffer.from("fake video bytes"), {
|
|
maxDurationSeconds: 600,
|
|
runner,
|
|
timeoutMs: 5_000,
|
|
});
|
|
|
|
assert.equal(result.durationSeconds, 4.5);
|
|
assert.equal(result.channels, VIDEO_AUDIO_CHANNELS);
|
|
assert.equal(result.sampleRateHz, VIDEO_AUDIO_SAMPLE_RATE_HZ);
|
|
assert.match(result.dataUri, /^data:audio\/wav;base64,/);
|
|
assert.deepEqual(
|
|
Buffer.from(result.dataUri.split(",", 2)[1], "base64"),
|
|
Buffer.from("RIFF....WAVEfmt ")
|
|
);
|
|
|
|
const ffmpegCall = calls.find((call) => call.executable === "ffmpeg");
|
|
assert.ok(ffmpegCall, "ffmpeg must be invoked");
|
|
assert.deepEqual(ffmpegCall!.args.slice(ffmpegCall!.args.indexOf("-map"), ffmpegCall!.args.indexOf("-map") + 2), [
|
|
"-map",
|
|
"0:a:0",
|
|
]);
|
|
assert.deepEqual(ffmpegCall!.args.slice(ffmpegCall!.args.indexOf("-ac"), ffmpegCall!.args.indexOf("-ac") + 2), [
|
|
"-ac",
|
|
"1",
|
|
]);
|
|
assert.deepEqual(ffmpegCall!.args.slice(ffmpegCall!.args.indexOf("-ar"), ffmpegCall!.args.indexOf("-ar") + 2), [
|
|
"-ar",
|
|
"16000",
|
|
]);
|
|
// No shell string ever built — every arg is a discrete array element (Hard Rule #13).
|
|
assert.equal(
|
|
ffmpegCall!.args.some((arg) => arg.includes(";") || arg.includes("&&")),
|
|
false
|
|
);
|
|
});
|
|
|
|
test("propagates the shared probe's duration cap instead of re-validating it", async () => {
|
|
const runner: VideoCommandRunner = async (executable) => {
|
|
if (executable === "ffprobe") {
|
|
return {
|
|
stdout: JSON.stringify({
|
|
format: { duration: "700", format_name: "mp4" },
|
|
streams: [{ index: 0, codec_type: "video", width: 640, height: 360 }],
|
|
}),
|
|
stderr: "",
|
|
};
|
|
}
|
|
throw new Error("ffmpeg must not run once the shared duration cap already rejected the file");
|
|
};
|
|
|
|
await assert.rejects(
|
|
() =>
|
|
extractVideoAudioFromBytes(Buffer.from("fake video bytes"), {
|
|
maxDurationSeconds: 600,
|
|
runner,
|
|
timeoutMs: 5_000,
|
|
}),
|
|
/maximum duration/
|
|
);
|
|
});
|
|
|
|
test("rejects a WAV output that exceeds the configured byte cap", async () => {
|
|
const runner: VideoCommandRunner = async (executable, args) => {
|
|
if (executable === "ffprobe") {
|
|
return {
|
|
stdout: JSON.stringify({
|
|
format: { duration: "4", format_name: "mp4" },
|
|
streams: [{ index: 0, codec_type: "video", width: 640, height: 360 }],
|
|
}),
|
|
stderr: "",
|
|
};
|
|
}
|
|
await writeFile(args.at(-1) ?? "", Buffer.alloc(16));
|
|
return { stdout: "", stderr: "" };
|
|
};
|
|
|
|
await assert.rejects(
|
|
() =>
|
|
extractVideoAudioFromBytes(Buffer.from("fake video bytes"), {
|
|
maxDurationSeconds: 600,
|
|
maxOutputBytes: 8,
|
|
runner,
|
|
timeoutMs: 5_000,
|
|
}),
|
|
/byte limit exceeded/
|
|
);
|
|
});
|
|
|
|
test("propagates ffmpeg's own failure when the container has no audio stream", async () => {
|
|
const runner: VideoCommandRunner = async (executable) => {
|
|
if (executable === "ffprobe") {
|
|
return {
|
|
stdout: JSON.stringify({
|
|
format: { duration: "4", format_name: "mp4" },
|
|
streams: [{ index: 0, codec_type: "video", width: 640, height: 360 }],
|
|
}),
|
|
stderr: "",
|
|
};
|
|
}
|
|
throw new Error("Stream map '0:a:0' matches no streams");
|
|
};
|
|
|
|
await assert.rejects(
|
|
() =>
|
|
extractVideoAudioFromBytes(Buffer.from("fake video bytes"), {
|
|
maxDurationSeconds: 600,
|
|
runner,
|
|
timeoutMs: 5_000,
|
|
}),
|
|
/matches no streams/
|
|
);
|
|
});
|