tts_local operator: text→speech via Kokoro-82M on MLX (character voice templates)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-17 18:01:58 +10:00
parent 26c4cfd099
commit f6626f0a06
2 changed files with 62 additions and 0 deletions

View File

@ -0,0 +1,19 @@
{
"id": "tts_local",
"name": "Speak (local, Kokoro)",
"category": "audio",
"description": "Text → speech WAV entirely on this Mac via Kokoro-82M on MLX (Apache 2.0, ungated, ~350MB first download). ~50 built-in voices spanning male/female US/UK = instant character voice templates. Faster than realtime, no API cost, no cloud.",
"accepts": [],
"produces": ["audio"],
"resources": "gpu",
"entry": "run.py",
"python": "venvs/kokoro/bin/python",
"params_schema": {
"type": "object",
"properties": {
"text": {"type": "string", "default": "", "description": "The line to speak"},
"voice": {"type": "string", "default": "af_heart", "description": "Kokoro voice id (af_/am_ = US female/male, bf_/bm_ = UK): af_heart, af_bella, af_nicole, am_adam, am_michael, am_onyx, bf_emma, bf_isabella, bm_george, bm_lewis, ..."},
"speed": {"type": "number", "default": 1.0, "minimum": 0.5, "maximum": 2.0, "description": "Speaking rate"}
}
}
}

View File

@ -0,0 +1,43 @@
import argparse
import json
import subprocess
import sys
import time
from pathlib import Path
ap = argparse.ArgumentParser()
ap.add_argument("--input", action="append", default=[])
ap.add_argument("--outdir", required=True)
ap.add_argument("--params", default="{}")
a = ap.parse_args()
p = json.loads(a.params)
text = (p.get("text") or "").strip()
if not text:
print("ERROR: text is required")
sys.exit(1)
voice = p.get("voice", "af_heart")
speed = float(p.get("speed", 1.0))
outdir = Path(a.outdir)
cmd = [
sys.executable, "-m", "mlx_audio.tts.generate",
"--model", "prince-canuma/Kokoro-82M",
"--text", text,
"--voice", voice,
"--speed", str(speed),
"--file_prefix", str(outdir / f"tts_{voice}"),
]
t0 = time.time()
r = subprocess.run(cmd, capture_output=True, text=True)
if r.stdout:
print(r.stdout[-2000:])
if r.stderr:
print(r.stderr[-2000:], file=sys.stderr)
wavs = sorted(outdir.glob("*.wav"))
if r.returncode != 0 or not wavs:
print("ERROR: kokoro generation failed")
sys.exit(1)
print(f"done in {time.time() - t0:.1f}s: {', '.join(w.name for w in wavs)}")