/* Step before building: * 1. Copy onnxruntime-node/bin to out/ * 2. Modify onnxruntime-node/dist/binding.js for bun * 3. Profit */ import os from "node:os"; import { $, which } from "bun"; import chalk from "chalk"; import { KokoroTTS, TextSplitterStream } from "kokoro-js"; const model_id = "onnx-community/Kokoro-82M-v1.0-ONNX"; const tts = await KokoroTTS.from_pretrained(model_id, { dtype: "fp32", // fp32 and q8 work best; fp16 results in silent segments device: "cpu", }); const stream = new TextSplitterStream(); const input = await $`wl-paste`.text(); if (!input) { console.error("No input"); process.exit(1); } stream.push(input); console.log(chalk.blue("Welcome to aitts!\n")); console.log(input); await $`rm -r ${os.homedir()}/.aitts/`.nothrow(); await $`mkdir ${os.homedir()}/.aitts/`.nothrow(); let fileId = 0; const files: string[] = []; const processText = async (text: string) => { const audio = await tts.generate(text, { voice: "af_bella", speed: 1.2, }); const file = `${os.homedir()}/.aitts/${fileId}.wav`; audio.save(file); files.push(file); fileId++; }; const play = async () => { for (const file of files) { console.log(chalk.green(`Playing ${file}`)); await Bun.spawn({ cmd: ["mpv", file], stdout: "inherit", }).exited; process.stdout.moveCursor(-9999, -5); process.stdout.clearScreenDown(); } }; let playing: Promise | undefined; for (const text of stream) { await processText(text); if (!playing && which("mpv")) { playing = play(); } } await playing;