diff options
| -rwxr-xr-x | cal-notif | 29 |
1 files changed, 28 insertions, 1 deletions
@@ -296,13 +296,40 @@ def announce(occ, now, vcfg): # ---- voice ---- +def say(text, vcfg): + import numpy as np + from kokoro_onnx import Kokoro + k = Kokoro(vcfg["model"], vcfg["voices"]) + style = sum(w * k.get_voice_style(n) for n, w in vcfg["blend"].items()) + audio, sr = k.create(text, voice=style, lang=vcfg["lang"]) + # A new playback stream wakes the suspended sink and loses its first + # ~0.2 s: lead with silence so it eats that, not the first word. + lead = bytes(int(sr * vcfg["lead_silence"]) * 2) + pcm = (np.clip(audio, -1, 1) * 32767).astype("<i2").tobytes() + subprocess.run(["aplay", "-q", "-r", str(sr), "-f", "S16_LE", "-c", "1"], + input=lead + pcm, check=True) + + +def voice_ready(vcfg): + if not vcfg["enabled"]: + return False + missing = [p for p in (vcfg["model"], vcfg["voices"]) if not Path(p).exists()] + if missing or not shutil.which("aplay"): + print(f"cal-notif: voice off, missing {missing or 'aplay'}", file=sys.stderr) + return False + return True + # ---- daemon ---- # ---- picker ---- def main(): - sys.exit(__doc__) + cmd = sys.argv[1:2] + if cmd == ["say"] and len(sys.argv) > 2: + say(" ".join(sys.argv[2:]), load_config()["voice"]) + else: + sys.exit(__doc__) if __name__ == "__main__": |
