Skip to content

Commit 946c24d

Browse files
authored
fix: validate edge tts output file is non-empty before reporting success (#43385) thanks @Huntterxx
Merged after review.\n\nSmall, scoped fix: treat 0-byte Edge TTS output as failure so provider fallback can continue.
1 parent c57b750 commit 946c24d

2 files changed

Lines changed: 76 additions & 1 deletion

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2+
import { tmpdir } from "node:os";
3+
import path from "node:path";
4+
import { afterEach, describe, expect, it, vi } from "vitest";
5+
6+
let mockTtsPromise = vi.fn<(text: string, filePath: string) => Promise<void>>();
7+
8+
vi.mock("node-edge-tts", () => ({
9+
EdgeTTS: class {
10+
ttsPromise(text: string, filePath: string) {
11+
return mockTtsPromise(text, filePath);
12+
}
13+
},
14+
}));
15+
16+
const { edgeTTS } = await import("./tts-core.js");
17+
18+
const baseEdgeConfig = {
19+
enabled: true,
20+
voice: "en-US-MichelleNeural",
21+
lang: "en-US",
22+
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
23+
outputFormatConfigured: false,
24+
saveSubtitles: false,
25+
};
26+
27+
describe("edgeTTS – empty audio validation", () => {
28+
let tempDir: string;
29+
30+
afterEach(() => {
31+
rmSync(tempDir, { recursive: true, force: true });
32+
});
33+
34+
it("throws when the output file is 0 bytes", async () => {
35+
tempDir = mkdtempSync(path.join(tmpdir(), "tts-test-"));
36+
const outputPath = path.join(tempDir, "voice.mp3");
37+
38+
mockTtsPromise = vi.fn(async (_text: string, filePath: string) => {
39+
writeFileSync(filePath, "");
40+
});
41+
42+
await expect(
43+
edgeTTS({
44+
text: "Hello",
45+
outputPath,
46+
config: baseEdgeConfig,
47+
timeoutMs: 10000,
48+
}),
49+
).rejects.toThrow("Edge TTS produced empty audio file");
50+
});
51+
52+
it("succeeds when the output file has content", async () => {
53+
tempDir = mkdtempSync(path.join(tmpdir(), "tts-test-"));
54+
const outputPath = path.join(tempDir, "voice.mp3");
55+
56+
mockTtsPromise = vi.fn(async (_text: string, filePath: string) => {
57+
writeFileSync(filePath, Buffer.from([0xff, 0xfb, 0x90, 0x00]));
58+
});
59+
60+
await expect(
61+
edgeTTS({
62+
text: "Hello",
63+
outputPath,
64+
config: baseEdgeConfig,
65+
timeoutMs: 10000,
66+
}),
67+
).resolves.toBeUndefined();
68+
});
69+
});

src/tts/tts-core.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { rmSync } from "node:fs";
1+
import { rmSync, statSync } from "node:fs";
22
import { completeSimple, type TextContent } from "@mariozechner/pi-ai";
33
import { EdgeTTS } from "node-edge-tts";
44
import { ensureCustomApiRegistered } from "../agents/custom-api-registry.js";
@@ -715,4 +715,10 @@ export async function edgeTTS(params: {
715715
timeout: config.timeoutMs ?? timeoutMs,
716716
});
717717
await tts.ttsPromise(text, outputPath);
718+
719+
const { size } = statSync(outputPath);
720+
721+
if (size === 0) {
722+
throw new Error("Edge TTS produced empty audio file");
723+
}
718724
}

0 commit comments

Comments
 (0)