diff --git a/skills/media-use/audio/scripts/lib/tts.mjs b/skills/media-use/audio/scripts/lib/tts.mjs index b708594744..f411d441e1 100644 --- a/skills/media-use/audio/scripts/lib/tts.mjs +++ b/skills/media-use/audio/scripts/lib/tts.mjs @@ -241,6 +241,22 @@ save(audio, sys.argv[3]) // [{text,start,end}] array for HeyGen (native), or null for ElevenLabs/Kokoro // (caller must transcribeWav). Never throws; failures return { ok:false, error } // where `error` states WHY (so the caller can surface it, not a bare "TTS failed"). +export function buildKokoroTtsArgs({ textPath, voiceId, wavRel, lang = "en", speed = 1.0 }) { + const args = [ + "hyperframes", + "tts", + textPath, + "--voice", + voiceId, + "--output", + wavRel, + "--speed", + String(speed), + ]; + if (lang !== "en") args.push("--lang", lang); + return args; +} + export async function synthesizeOne({ provider, text, @@ -276,8 +292,13 @@ export async function synthesizeOne({ } // kokoro — via the published CLI; --output is relative to the project dir. const wavRel = relTo(hyperframesDir, wavAbs); - const args = ["hyperframes", "tts", writeTmpText(text), "--voice", voiceId, "--output", wavRel]; - if (lang !== "en") args.push("--lang", lang); + const args = buildKokoroTtsArgs({ + textPath: writeTmpText(text), + voiceId, + wavRel, + lang, + speed, + }); const r = await spawnP("npx", args, { cwd: hyperframesDir }); return synthResult(r, wavAbs, "kokoro (npx hyperframes tts)"); } diff --git a/skills/media-use/audio/scripts/lib/tts.test.mjs b/skills/media-use/audio/scripts/lib/tts.test.mjs index a4f651908d..5b0d0fc7d9 100644 --- a/skills/media-use/audio/scripts/lib/tts.test.mjs +++ b/skills/media-use/audio/scripts/lib/tts.test.mjs @@ -9,8 +9,31 @@ import { synthesizeOne, synthesizeHeygen, synthResult, + buildKokoroTtsArgs, } from "./tts.mjs"; +test("forwards a non-default speed to the Kokoro CLI", () => { + const args = buildKokoroTtsArgs({ + textPath: "/tmp/narration.txt", + voiceId: "am_michael", + wavRel: "audio/narration.wav", + lang: "en", + speed: 1.15, + }); + + assert.deepEqual(args, [ + "hyperframes", + "tts", + "/tmp/narration.txt", + "--voice", + "am_michael", + "--output", + "audio/narration.wav", + "--speed", + "1.15", + ]); +}); + test("parseFfmpegDurationBanner reads ffmpeg's stderr Duration line", () => { const stderr = [ "ffmpeg version 6.0",