Primary: /run/agenix/elevenlabs-key (read via 'read -r' which strips the trailing newline agenix files contain). Fallback: ELEVENLABS_API_KEY env var (backwards compatible). No need to export the key in shell environment anymore — if the agenix secret exists, talk picks it up automatically. Override path via ELEVENLABS_KEY_FILE env var if needed.
138 lines
4.3 KiB
Nix
138 lines
4.3 KiB
Nix
{
|
|
lib,
|
|
stdenv,
|
|
writeShellScriptBin,
|
|
fetchurl,
|
|
curl,
|
|
python3,
|
|
mpv,
|
|
libnotify,
|
|
busybox,
|
|
}: let
|
|
icon = fetchurl {
|
|
url = "https://s3.m3tam3re.com/public/chiron.jpeg";
|
|
hash = "sha256-LIIR3aqJ5ePsZuvVQxJ70c6yd+L85fLE5uY/fLE5Ypo=";
|
|
};
|
|
script = writeShellScriptBin "talk" ''
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# ── Toggle: VOICE_NOTIFY=0 deaktiviert alles ───────────────────
|
|
if [ "''${VOICE_NOTIFY:-1}" = "0" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
# ── Config ──────────────────────────────────────────────────────
|
|
# API key: read from agenix file (preferred), fall back to env var.
|
|
# `read -r` strips the trailing newline that agenix files contain.
|
|
ELEVENLABS_KEY_FILE="''${ELEVENLABS_KEY_FILE:-/run/agenix/elevenlabs-key}"
|
|
if [ -f "$ELEVENLABS_KEY_FILE" ]; then
|
|
read -r API_KEY < "$ELEVENLABS_KEY_FILE"
|
|
elif [ -n "''${ELEVENLABS_API_KEY:-}" ]; then
|
|
API_KEY="''${ELEVENLABS_API_KEY}"
|
|
else
|
|
echo "Error: No ElevenLabs API key found." >&2
|
|
echo "Create $ELEVENLABS_KEY_FILE (agenix) or set ELEVENLABS_API_KEY env var." >&2
|
|
exit 1
|
|
fi
|
|
VOICE_ID="''${ELEVENLABS_VOICE_ID:-KXxZd16DiBqt82nbarJx}"
|
|
# Argument hat Vorrang, sonst stdin lesen wenn Pipe
|
|
if [ $# -ge 1 ]; then
|
|
TEXT="$1"
|
|
elif [ ! -t 0 ]; then
|
|
TEXT=$(cat)
|
|
else
|
|
echo "Usage: talk \"Text\" oder echo Text | talk" >&2
|
|
exit 1
|
|
fi
|
|
AUDIO_FILE="/tmp/voice-notify-$$.mp3"
|
|
ACTION_FILE="/tmp/voice-action-$$"
|
|
|
|
CURL="${lib.getExe curl}"
|
|
PYTHON="${lib.getExe python3}"
|
|
MPV="${lib.getExe mpv}"
|
|
NOTIFY="${lib.getExe libnotify}"
|
|
RM="${busybox}/bin/rm"
|
|
CAT="${busybox}/bin/cat"
|
|
GREP="${busybox}/bin/grep"
|
|
SLEEP="${busybox}/bin/sleep"
|
|
KILL="${busybox}/bin/kill"
|
|
STAT="${busybox}/bin/stat"
|
|
|
|
trap '$RM -f "$AUDIO_FILE" "$ACTION_FILE"' EXIT
|
|
|
|
# ── TTS generieren ──────────────────────────────────────────────
|
|
PAYLOAD=$("$PYTHON" -c '
|
|
import json, sys
|
|
print(json.dumps({
|
|
"text": sys.argv[1],
|
|
"model_id": "eleven_multilingual_v2",
|
|
"voice_settings": {"stability": 0.5, "similarity_boost": 0.75}
|
|
}))
|
|
' "$TEXT")
|
|
|
|
HTTP_CODE=$("$CURL" -s -o "$AUDIO_FILE" -w "%{http_code}" \
|
|
-X POST "https://api.elevenlabs.io/v1/text-to-speech/''${VOICE_ID}" \
|
|
-H "xi-api-key: ''${API_KEY}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$PAYLOAD") || HTTP_CODE="000"
|
|
|
|
if [ "$HTTP_CODE" != "200" ]; then
|
|
ERR=$($CAT "$AUDIO_FILE" 2>/dev/null | head -c 300)
|
|
$RM -f "$AUDIO_FILE"
|
|
"$NOTIFY" -u critical "❌ Voice Fehler" "HTTP ''${HTTP_CODE}\n''${ERR}"
|
|
exit 1
|
|
fi
|
|
|
|
# ── Gleichzeitig: Audio + Notification ──────────────────────────
|
|
"$MPV" --no-video --no-terminal --really-quiet "$AUDIO_FILE" &
|
|
MPV_PID=$!
|
|
|
|
(
|
|
exec "$NOTIFY" \
|
|
--action=cancel="⛔ Abbrechen" \
|
|
--urgency=normal \
|
|
--icon=${icon} \
|
|
"🔊 KI Antwort" \
|
|
"$TEXT" > "$ACTION_FILE" 2>/dev/null
|
|
) &
|
|
NOTIFY_PID=$!
|
|
|
|
# ── Poll-Loop ───────────────────────────────────────────────────
|
|
while true; do
|
|
if ! $KILL -0 "$MPV_PID" 2>/dev/null; then
|
|
$KILL "$NOTIFY_PID" 2>/dev/null || true
|
|
break
|
|
fi
|
|
if [ -s "$ACTION_FILE" ]; then
|
|
if $GREP -q "cancel" "$ACTION_FILE" 2>/dev/null; then
|
|
$KILL "$MPV_PID" 2>/dev/null || true
|
|
fi
|
|
break
|
|
fi
|
|
$SLEEP 0.2
|
|
done
|
|
|
|
wait "$MPV_PID" 2>/dev/null || true
|
|
'';
|
|
in
|
|
stdenv.mkDerivation {
|
|
pname = "talk";
|
|
version = "0.1.0";
|
|
|
|
dontUnpack = true;
|
|
|
|
installPhase = ''
|
|
mkdir -p "$out/bin"
|
|
ln -s ${script}/bin/talk "$out/bin/talk"
|
|
'';
|
|
|
|
meta = with lib; {
|
|
description = "ElevenLabs TTS voice notifications with dismissable popup";
|
|
homepage = "https://code.m3ta.dev/m3tam3re/nixpkgs";
|
|
license = licenses.mit;
|
|
platforms = platforms.linux;
|
|
mainProgram = "talk";
|
|
};
|
|
}
|