/** tts_bridge.js — Persistent Node.js bridge for aquestalk.js TTS synthesis. Protocol (via stdin/stdout): - On startup, prints "READY" to stdout. - Reads lines from stdin in format: INPUT_PATH|OUTPUT_PATH - Reads kana text from INPUT_PATH, synthesizes WAV, writes to OUTPUT_PATH. - Prints "OK:OUTPUT_PATH" on success, "ERR:message" on failure. - Exits when stdin is closed. Usage: node tts_bridge.js --voice f1 --speed 100 */ import { readFileSync, writeFileSync } from "fs"; import { createInterface } from "readline"; import { fileURLToPath, pathToFileURL } from "url"; import { dirname, join } from "path"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Parse CLI arguments const args = process.argv.slice(2); let voice = "f1"; let speed = 100; for (let i = 0; i < args.length; i++) { if (args[i] === "--voice" && i + 1 < args.length) { voice = args[++i]; } else if (args[i] === "--speed" && i + 1 < args.length) { speed = parseInt(args[++i], 10); } } // Import aquestalk.js from sibling directory const aquestalkMod = pathToFileURL(join(__dirname, "..", "aquestalk.js", "dist", "index.js")).href; const { load } = await import(aquestalkMod); console.error(`[bridge] Loading aquestalk.js with voice="${voice}", speed=${speed}...`); let aq; try { aq = await load(voice); console.error("[bridge] aquestalk.js loaded successfully."); } catch (err) { console.error("[bridge] Failed to load aquestalk.js:", err.message); process.stdout.write("ERR:FAILED_TO_LOAD\n"); process.exit(1); } process.stdout.write("READY\n"); const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: false, }); rl.on("line", (line) => { line = line.trim(); if (!line) return; const sepIdx = line.indexOf("|"); if (sepIdx === -1) { process.stdout.write(`ERR:INVALID_FORMAT:${line}\n`); return; } const inputPath = line.substring(0, sepIdx); const outputPath = line.substring(sepIdx + 1); try { const kanaText = readFileSync(inputPath, "utf-8").trim(); if (!kanaText) { process.stdout.write(`ERR:EMPTY_TEXT\n`); return; } const wav = aq.run(kanaText, speed); writeFileSync(outputPath, Buffer.from(wav)); process.stdout.write(`OK:${outputPath}\n`); } catch (err) { process.stdout.write(`ERR:${err.message}\n`); } }); rl.on("close", () => { console.error("[bridge] stdin closed, shutting down."); aq.destroy().then(() => { process.exit(0); }); });