Files
Adolfo Reyna 354cacd417 feat: ksay — Kokoro 82M local TTS replaces say (4x RTF, offline, 9 voices)
- engine_kokoro_tts.py: tts(text, voice, outfile) 0.5s gen 4-5x RTF M2
- ksay: drop-in say replacement, usage: ksay [-v voice] [-o file] "text"
- install-ksay.sh: one-shot setup for macmini (pip + model 310MB + warm)
- voices: af_bella (default), af_heart, am_adam, bf_emma etc
- model ready after: ./install-ksay.sh (pre-downloads hexgrad/Kokoro-82M)
- macmini: git pull gitea main && bash install-ksay.sh && ./ksay "hello"
2026-07-14 13:37:58 -04:00

30 lines
1.2 KiB
Bash
Executable File

#!/bin/bash
# ksay — Kokoro 82M TTS, better than say -v Eddy, fully offline after install, 4x RTF on M2
set -e
ROOT="$(cd "$(dirname "$0")" && pwd)"
VOICE="af_bella"
OUT=""
TEXT=""
PLAY=1
while [[ $# -gt 0 ]]; do
case "$1" in
-v|--voice) VOICE="$2"; shift 2;;
-o|--output-file) OUT="$2"; shift 2;;
-l|--list-voices) PYTHONPATH="" "$ROOT/venv/bin/python" "$ROOT/engine_kokoro_tts.py" --list-voices; exit 0;;
--no-play) PLAY=0; shift;;
--) shift; TEXT="$*"; break;;
-*) echo "unknown $1" >&2; exit 2;;
*) TEXT="${TEXT:+$TEXT }$1"; shift;;
esac
done
if [[ -z "$TEXT" && ! -t 0 ]]; then TEXT=$(cat); fi
if [[ -z "$TEXT" ]]; then echo "usage: ksay [-v voice] [-o file.wav] \"text\" | echo text | ksay" >&2; exit 1; fi
TMP="${OUT:-/tmp/ksay-$$.wav}"
PYTHONPATH="" "$ROOT/venv/bin/python" "$ROOT/engine_kokoro_tts.py" "$TEXT" -v "$VOICE" -o "$TMP" 2>&1 | grep -vE "UserWarning|FutureWarning|Defaulting|You are sending|torch/nn|WeightNorm|super\(\)" || true
if [[ -z "$OUT" ]]; then
if [[ "$PLAY" -eq 1 ]]; then afplay "$TMP"; rm -f "$TMP"; fi
else
echo "saved $TMP (as $OUT requested but kokoro writes $TMP, copy)"
cp "$TMP" "$OUT" 2>/dev/null || true
fi