馃敡 Where my dotfiles lives in harmony and peace, most of the time
1#!/usr/bin/env bash
2set -euo pipefail
3
4# Whispr - Groq Whisper Transcription Script
5# Based on https://github.com/EmbeddedMhawar/groq-whisper-linux/blob/master/groq_whisper.sh
6
7API_KEY="${GROQ_API_KEY:-}"
8if [[ -z "$API_KEY" ]]; then
9 notify-send -u critical "Groq Error" "Missing GROQ_API_KEY"
10 exit 1
11fi
12
13STATE_DIR="${XDG_RUNTIME_DIR:-/tmp}/whisp"
14mkdir -p "$STATE_DIR"
15
16FILENAME="${STATE_DIR}/rec.flac"
17PIDFILE="${STATE_DIR}/rec.pid"
18REC_LOG="${STATE_DIR}/rec_error.log"
19CURL_LOG="${STATE_DIR}/curl_error.log"
20ERROR_LOG="${STATE_DIR}/error.log"
21
22if [ -f "$PIDFILE" ]; then
23 PID="$(<"$PIDFILE")"
24 if kill -0 "$PID" 2>/dev/null; then
25 notify-send -u low -t 1000 "Groq" "Finishing..."
26 sleep 1
27 kill -INT "$PID" 2>/dev/null || true
28 for _ in {1..50}; do
29 kill -0 "$PID" 2>/dev/null || break
30 sleep 0.1
31 done
32 fi
33 rm "$PIDFILE"
34
35 notify-send -u low -t 2000 "Groq" "Transcribing..."
36
37 if [[ ! -s "$FILENAME" ]]; then
38 {
39 echo "Error: recording file missing or empty: $FILENAME"
40 [[ -s "$REC_LOG" ]] && { echo; echo "--- rec stderr ---"; cat "$REC_LOG"; }
41 } >"$ERROR_LOG"
42 notify-send -u critical "Groq Error" "No audio captured. Check $ERROR_LOG"
43 exit 1
44 fi
45
46 MAX_RETRIES=3
47 RETRY_DELAY=1
48 HTTP_CODE=""
49 TEXT=""
50
51 : >"$CURL_LOG"
52
53 attempt=0
54 while (( attempt < MAX_RETRIES )); do
55 attempt=$((attempt + 1))
56 CURL_BODY="$(mktemp -p "$STATE_DIR" whisp_curl_body.XXXXXX)"
57 set +e
58 HTTP_CODE="$(curl -sS -o "$CURL_BODY" -w "%{http_code}" "https://api.groq.com/openai/v1/audio/transcriptions" \
59 -H "Authorization: Bearer $API_KEY" \
60 -F "file=@$FILENAME" \
61 -F "model=whisper-large-v3" \
62 -F "response_format=text" \
63 --connect-timeout 15 \
64 --max-time 300 \
65 2>>"$CURL_LOG")"
66 CURL_EXIT=$?
67 set -e
68 TEXT="$(<"$CURL_BODY")"
69 rm -f "$CURL_BODY"
70
71 if [[ $CURL_EXIT -ne 0 ]]; then
72 HTTP_CODE="000"
73 fi
74
75 if [[ "$HTTP_CODE" == "200" ]]; then
76 break
77 fi
78
79 # Retry on transient errors
80 if [[ "$HTTP_CODE" =~ ^(400|429|500|502|503|504)$ ]] && [[ $attempt -lt $MAX_RETRIES ]]; then
81 notify-send -u low -t 1500 "Groq" "Retrying... (attempt $((attempt+1))/$MAX_RETRIES)"
82 sleep $RETRY_DELAY
83 RETRY_DELAY=$((RETRY_DELAY * 2))
84 else
85 break
86 fi
87 done
88
89 if [[ "$HTTP_CODE" != "200" ]]; then
90 {
91 echo "Error (HTTP $HTTP_CODE) after $attempt attempts:"
92 [[ -n "${TEXT:-}" ]] && echo "$TEXT"
93 [[ -s "$CURL_LOG" ]] && { echo; echo "--- curl stderr (tail) ---"; tail -n 50 "$CURL_LOG"; }
94 } >"$ERROR_LOG"
95 notify-send -u critical "Groq Error" "Failed (Code $HTTP_CODE). Check $ERROR_LOG"
96 exit 1
97 else
98 printf %s "$TEXT" | wl-copy
99 notify-send -u low -t 2000 "Groq" "Transcription Done!"
100 fi
101
102else
103 : >"$REC_LOG"
104 rec -q -r 16000 -c 1 -b 16 "$FILENAME" 2>"$REC_LOG" &
105 PID=$!
106 echo "$PID" > "$PIDFILE"
107
108 sleep 0.1
109 if ! kill -0 "$PID" 2>/dev/null; then
110 rm -f "$PIDFILE"
111 notify-send -u critical "Groq Error" "Recording failed. Check $REC_LOG"
112 exit 1
113 fi
114
115 notify-send -u low -t 1000 "Groq" "Listening... (press again to finish)"
116fi