-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathdictee-switch-backend
More file actions
executable file
·424 lines (397 loc) · 18.2 KB
/
Copy pathdictee-switch-backend
File metadata and controls
executable file
·424 lines (397 loc) · 18.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
#!/bin/bash
# dictee-switch-backend — Switch ASR or translation backend
# Usage: dictee-switch-backend <asr|translate> <backend>
set -euo pipefail
CONF="${XDG_CONFIG_HOME:-$HOME/.config}/dictee.conf"
# Common functions (debug, state, notifications)
_DBG_CONTEXT="switch-backend"
for _p in "$(dirname "$(readlink -f "$0")")" /usr/lib/dictee; do
[ -f "$_p/dictee-common.sh" ] && { source "$_p/dictee-common.sh"; break; }
done
usage() {
echo "Usage: dictee-switch-backend <command> [backend]"
echo ""
echo "Commands:"
echo " status Show current backends"
echo " asr <backend> Switch ASR backend"
echo " translate <backend> Switch translation backend"
echo " diarize [true|false|toggle] Toggle speaker diarization"
echo " context [true|false] Toggle audio context buffer"
echo " llm [true|false|toggle] Toggle LLM post-processing"
echo " short_text [true|false|toggle] Toggle short-text fix (both pp_normal + pp_translate)"
echo " quant [fp32|int8|toggle] Switch Parakeet model variant (restarts daemon)"
echo " force_cpu [true|false|toggle] Force CPU mode for all ASR backends"
echo ""
echo "ASR backends: parakeet, canary, vosk, whisper, whisper-rust, nemotron, kyutai"
echo "Translate backends: google, bing, ollama, libretranslate"
exit 1
}
# Update or append a key=value in dictee.conf
set_conf() {
local key="$1" value="$2"
if grep -q "^${key}=" "$CONF"; then
sed -i "s|^${key}=.*|${key}=${value}|" "$CONF"
else
echo "${key}=${value}" >> "$CONF"
fi
}
if [ ! -f "$CONF" ]; then
echo "Error: $CONF not found. Run dictee-setup first." >&2
exit 1
fi
if [ $# -lt 1 ]; then
usage
fi
_dbg "command=$1 ${2:-}"
# Serialize concurrent invocations (rapid toggle from plasmoid/tray, parallel
# CLI calls, etc.) — without this, two processes can race on dictee.conf
# (sed -i is not atomic) and trigger cascading daemon restarts.
# `status` is read-only and skips the lock so it stays instant.
# -w 5 = wait up to 5 s; if another invocation is still running (e.g. daemon
# restart pending), give up silently with exit 0 — the latest user click wins
# via the lock queue, intermediate ones are just dropped.
if [ "$1" != "status" ]; then
LOCK_FILE="/tmp/dictee-switch-backend-${USER}.lock"
exec 200>"$LOCK_FILE"
if ! flock -w 5 -x 200; then
echo "dictee-switch-backend: another invocation is in progress, skipping." >&2
_dbg "skip: lock contention on $LOCK_FILE"
exit 0
fi
fi
case "$1" in
status)
# Read current backends from config (disable -eu during source to tolerate malformed values)
set +eu
source "$CONF"
set -eu
asr="${DICTEE_ASR_BACKEND:-parakeet}"
svc=$(asr_service "$asr")
svc_state=$(systemctl --user is-active "${svc}.service" 2>/dev/null) || true
echo "ASR: $asr (${svc}.service, $svc_state)"
trans="${DICTEE_TRANSLATE_BACKEND:-trans}"
if [ "$trans" = "trans" ]; then
engine="${DICTEE_TRANS_ENGINE:-google}"
echo "Translate: $engine ($trans)"
else
echo "Translate: $trans"
fi
exit 0
;;
asr)
if [ $# -lt 2 ]; then usage; fi
case "$2" in parakeet|canary|vosk|whisper|whisper-rust|nemotron|kyutai) ;; *) echo "Error: unknown ASR backend '$2'" >&2; usage ;; esac
svc=$(asr_service "$2")
# Check that the service exists
if ! systemctl --user list-unit-files "${svc}.service" 2>/dev/null | grep -q "${svc}.service"; then
echo "Error: ${svc}.service not found. Is the $2 backend installed?" >&2
notify_dictee 0 dialog-error "Backend $2 not installed"
exit 1
fi
set_conf "DICTEE_ASR_BACKEND" "$2"
# Aligner DICTEE_FORCE_CPU avec la contrainte du nouveau backend, AVANT
# le restart du service. Sans ça, le daemon démarre avec l'ancien
# FORCE_CPU (héritage du backend précédent), charge le GPU pour rien,
# puis le plasmoid détecte l'incohérence et redémarre — d'où charge/
# décharge VRAM transitoire observée 2026-05-21 (whisper GPU →
# parakeet rapide). Cf. fix wrapper quant.
set +eu; source "$CONF"; set -eu
case "$2" in
parakeet)
# Parakeet int8 force CPU, fp32 libère la contrainte
if [ "${DICTEE_PARAKEET_QUANT:-fp32}" = "int8" ]; then
set_conf "DICTEE_FORCE_CPU" "1"
else
set_conf "DICTEE_FORCE_CPU" "0"
fi
;;
canary)
# Canary requires GPU (encoder too heavy for CPU)
set_conf "DICTEE_FORCE_CPU" "0"
;;
nemotron)
# Nemotron runs on CPU and GPU; leave the user free (like Parakeet fp32)
set_conf "DICTEE_FORCE_CPU" "0"
;;
kyutai)
# Kyutai is GPU NVIDIA-only — never force CPU.
set_conf "DICTEE_FORCE_CPU" "0"
;;
# vosk : CPU only by design, FORCE_CPU sans effet — ne pas toucher
# whisper : libre, ne pas toucher
esac
write_state "switching"
_dbg "asr: switching to $2 (svc=$svc)"
# Stop all daemons, start the right one
for s in dictee dictee-canary dictee-vosk dictee-whisper dictee-whisper-rust dictee-nemotron dictee-kyutai; do
if [ "$s" != "$svc" ]; then
systemctl --user stop "${s}.service" 2>/dev/null || true
systemctl --user disable "${s}.service" 2>/dev/null || true
fi
done
systemctl --user enable "${svc}.service" 2>/dev/null || true
if ! systemctl --user restart "${svc}.service" 2>/dev/null; then
echo "Warning: ${svc}.service failed to start. Check: systemctl --user status ${svc}.service" >&2
write_state "offline"
_dbg "asr: $svc FAILED to start"
else
write_state "idle"
_dbg "asr: $svc started OK"
fi
# Build notification with model name for Whisper/Vosk, quantization variant for Parakeet
_notify_detail=""
if [ "$2" = "whisper" ]; then
_wm=$(grep '^DICTEE_WHISPER_MODEL=' "$CONF" 2>/dev/null | cut -d= -f2 || true)
[ -n "$_wm" ] && _notify_detail=" (${_wm})"
elif [ "$2" = "vosk" ]; then
_vm=$(grep '^DICTEE_VOSK_MODEL=' "$CONF" 2>/dev/null | cut -d= -f2 || true)
[ -n "$_vm" ] && _notify_detail=" (${_vm})"
elif [ "$2" = "parakeet" ]; then
# Show the active model variant (FP32 / INT8) in the notification
_pq=$(grep '^DICTEE_PARAKEET_QUANT=' "$CONF" 2>/dev/null | cut -d= -f2 || true)
case "${_pq:-fp32}" in
int8) _notify_detail=" (INT8)" ;;
fp32|*) _notify_detail=" (FP32)" ;;
esac
fi
echo "ASR backend: $2${_notify_detail} (${svc}.service)"
notify_dictee 0 audio-chat-symbolic "Backend ASR: $2${_notify_detail}"
(sleep 3; gdbus call --session --dest org.freedesktop.Notifications --object-path /org/freedesktop/Notifications --method org.freedesktop.Notifications.CloseNotification "$NOTIFY_SERVER_ID" >/dev/null 2>&1) &
;;
translate)
if [ $# -lt 2 ]; then usage; fi
case "$2" in
google)
set_conf "DICTEE_TRANSLATE_BACKEND" "trans"
set_conf "DICTEE_TRANS_ENGINE" "google"
;;
bing)
set_conf "DICTEE_TRANSLATE_BACKEND" "trans"
set_conf "DICTEE_TRANS_ENGINE" "bing"
;;
ollama)
if ! command -v ollama >/dev/null 2>&1; then
echo "Error: ollama is not installed. See https://ollama.com" >&2
notify_dictee 0 dialog-error "Ollama non installé" "Installez ollama depuis https://ollama.com"
exit 1
fi
set +eu; source "$CONF"; set -eu
model="${DICTEE_OLLAMA_MODEL:-translategemma}"
base="${model%%:*}"
set +o pipefail
if ! ollama list 2>/dev/null | grep -q "$base"; then
set -o pipefail
echo "Error: model '$model' not downloaded. Run: ollama pull $model" >&2
notify_dictee 0 dialog-warning "Modèle Ollama manquant" "Configurez Ollama dans dictee-setup"
exit 1
fi
set -o pipefail
set_conf "DICTEE_TRANSLATE_BACKEND" "ollama"
;;
libretranslate)
set_conf "DICTEE_TRANSLATE_BACKEND" "libretranslate"
;;
*) echo "Error: unknown translate backend '$2'" >&2; usage ;;
esac
echo "Translate backend: $2"
notify_dictee 0 translate-symbolic "Traduction: $2"
(sleep 3; gdbus call --session --dest org.freedesktop.Notifications --object-path /org/freedesktop/Notifications --method org.freedesktop.Notifications.CloseNotification "$NOTIFY_SERVER_ID" >/dev/null 2>&1) &
;;
diarize)
val="${2:-true}"
set +eu; source "$CONF"; set -eu
current_asr="${DICTEE_ASR_BACKEND:-parakeet}"
_dbg "diarize: val=$val current_asr=$current_asr"
if [ "$val" = "true" ]; then
# Save current backend and stop daemon to free VRAM
write_state "preparing"
set_conf "DICTEE_PRE_DIARIZE_BACKEND" "$current_asr"
# Stop all daemons and wait for VRAM release
_dbg "diarize: stopping all daemons for VRAM"
systemctl --user disable --now dictee dictee-vosk dictee-whisper dictee-whisper-rust dictee-canary dictee-nemotron dictee-kyutai 2>/dev/null
# Wait until GPU processes are gone (5s timeout)
for i in $(seq 1 10); do
if ! nvidia-smi --query-compute-apps=name --format=csv,noheader 2>/dev/null | grep -qE "transcribe|python"; then
break
fi
sleep 0.5
done
write_state "diarize-ready"
notify_dictee 0 audio-speakers-symbolic "Diarisation activée"
(sleep 3; gdbus call --session --dest org.freedesktop.Notifications --object-path /org/freedesktop/Notifications --method org.freedesktop.Notifications.CloseNotification "$NOTIFY_SERVER_ID" >/dev/null 2>&1) &
else
# Restore the previous backend daemon
pre_backend="${DICTEE_PRE_DIARIZE_BACKEND:-$current_asr}"
if [ -n "$pre_backend" ]; then
set_conf "DICTEE_ASR_BACKEND" "$pre_backend"
svc=$(asr_service "$pre_backend")
_dbg "diarize: restoring $pre_backend → $svc"
# Wait until GPU processes have released VRAM (5s timeout) before
# restarting the daemon. Canary 1B needs ~4.7 GB and OOMs if launched
# while transcribe-diarize-batch / diarize-only subprocesses are
# still finishing their CUDA cleanup — systemd then enters a
# Restart=on-failure loop. Parakeet (~600 MB) tolerates residual
# VRAM so this asymmetry was invisible until Canary became the
# default backend. Symmetric with the wait in `diarize true` above.
for i in $(seq 1 10); do
if ! nvidia-smi --query-compute-apps=name --format=csv,noheader 2>/dev/null | grep -qE "transcribe|diarize"; then
break
fi
sleep 0.5
done
systemctl --user enable --now "$svc" 2>/dev/null
set_conf "DICTEE_PRE_DIARIZE_BACKEND" ""
notify_dictee 0 audio-speakers-symbolic "Diarisation désactivée — retour à $pre_backend"
else
notify_dictee 0 audio-speakers-symbolic "Diarisation désactivée"
fi
(sleep 3; gdbus call --session --dest org.freedesktop.Notifications --object-path /org/freedesktop/Notifications --method org.freedesktop.Notifications.CloseNotification "$NOTIFY_SERVER_ID" >/dev/null 2>&1) &
# Clean up stale DICTEE_DIARIZE from config (no longer used, but might exist from older versions)
sed -i '/^DICTEE_DIARIZE=/d' "$CONF" 2>/dev/null || true
write_state "idle"
fi
echo "Diarization: $val"
;;
context)
val="${2:-true}"
case "$val" in
true|false) ;;
toggle)
set +eu; source "$CONF"; set -eu
if [ "${DICTEE_AUDIO_CONTEXT:-false}" = "true" ]; then val="false"; else val="true"; fi
;;
*) echo "Error: context expects true, false, or toggle" >&2; usage ;;
esac
set_conf "DICTEE_AUDIO_CONTEXT" "$val"
_dbg "context: $val"
if [ "$val" = "true" ]; then
notify_dictee 0 audio-chat-symbolic "Audio context: ON"
else
notify_dictee 0 audio-chat-symbolic "Audio context: OFF"
# Clean up buffer files
rm -f "/dev/shm/.dictee_buffer${_UID_SUFFIX}.wav" "/dev/shm/.dictee_buffer_ts${_UID_SUFFIX}" "/dev/shm/.dictee_buffer_text${_UID_SUFFIX}"
fi
(sleep 3; gdbus call --session --dest org.freedesktop.Notifications --object-path /org/freedesktop/Notifications --method org.freedesktop.Notifications.CloseNotification "$NOTIFY_SERVER_ID" >/dev/null 2>&1) &
echo "Audio context: $val"
;;
llm)
val="${2:-toggle}"
case "$val" in
true|false) ;;
toggle)
set +eu; source "$CONF"; set -eu
if [ "${DICTEE_LLM_POSTPROCESS:-false}" = "true" ]; then val="false"; else val="true"; fi
;;
*) echo "Error: llm expects true, false, or toggle" >&2; usage ;;
esac
set_conf "DICTEE_LLM_POSTPROCESS" "$val"
_dbg "llm: $val"
if [ "$val" = "true" ]; then
notify_dictee 0 document-edit-symbolic "LLM post-processing: ON"
else
notify_dictee 0 document-edit-symbolic "LLM post-processing: OFF"
fi
(sleep 3; gdbus call --session --dest org.freedesktop.Notifications --object-path /org/freedesktop/Notifications --method org.freedesktop.Notifications.CloseNotification "$NOTIFY_SERVER_ID" >/dev/null 2>&1) &
echo "LLM post-processing: $val"
;;
short_text)
val="${2:-toggle}"
case "$val" in
true|false) ;;
toggle)
set +eu; source "$CONF"; set -eu
# OR between pp_normal and pp_translate: if any is ON, toggle turns both OFF.
cur_pp="${DICTEE_PP_SHORT_TEXT:-true}"
cur_trpp="${DICTEE_TRPP_SHORT_TEXT:-true}"
if [ "$cur_pp" = "true" ] || [ "$cur_trpp" = "true" ]; then val="false"; else val="true"; fi
;;
*) echo "Error: short_text expects true, false, or toggle" >&2; usage ;;
esac
set_conf "DICTEE_PP_SHORT_TEXT" "$val"
set_conf "DICTEE_TRPP_SHORT_TEXT" "$val"
_dbg "short_text: $val (both pipelines)"
if [ "$val" = "true" ]; then
notify_dictee 0 document-edit-symbolic "Short-text fix: ON (normal + translate)"
else
notify_dictee 0 document-edit-symbolic "Short-text fix: OFF (normal + translate)"
fi
(sleep 3; gdbus call --session --dest org.freedesktop.Notifications --object-path /org/freedesktop/Notifications --method org.freedesktop.Notifications.CloseNotification "$NOTIFY_SERVER_ID" >/dev/null 2>&1) &
echo "Short-text: $val"
;;
quant)
# Parakeet quantization variant (fp32 | int8). Writes DICTEE_PARAKEET_QUANT
# to dictee.conf and restarts dictee.service so the daemon picks up the
# new model file at next load.
val="${2:-toggle}"
case "$val" in
fp32|int8) ;;
toggle)
set +eu; source "$CONF"; set -eu
if [ "${DICTEE_PARAKEET_QUANT:-fp32}" = "int8" ]; then val="fp32"; else val="int8"; fi
;;
*) echo "Error: quant expects fp32, int8, or toggle" >&2; usage ;;
esac
set_conf "DICTEE_PARAKEET_QUANT" "$val"
_dbg "quant: $val"
# Align DICTEE_FORCE_CPU avec la nouvelle contrainte du quant :
# - int8 force CPU (GPU 5× plus lent) → DICTEE_FORCE_CPU=1
# - fp32 libère la contrainte → DICTEE_FORCE_CPU=0 (GPU par défaut)
# Sans cette synchro, passer de int8 (force_cpu=1 hérité) à fp32 laisse
# le daemon en CPU malgré le quant qui veut le GPU. Bug signalé 2026-05-21.
if [ "$val" = "int8" ]; then
set_conf "DICTEE_FORCE_CPU" "1"
else
set_conf "DICTEE_FORCE_CPU" "0"
fi
# Restart the Parakeet daemon if it's the active backend, so the new
# model variant is actually loaded (env var alone isn't enough — the
# daemon reads it only at startup).
set +eu; source "$CONF"; set -eu
if [ "${DICTEE_ASR_BACKEND:-parakeet}" = "parakeet" ]; then
systemctl --user restart dictee.service 2>/dev/null || true
notify_dictee 0 audio-input-microphone-symbolic "Parakeet model: $val (daemon restarting)"
else
notify_dictee 0 audio-input-microphone-symbolic "Parakeet model: $val (takes effect when Parakeet is active)"
fi
(sleep 3; gdbus call --session --dest org.freedesktop.Notifications --object-path /org/freedesktop/Notifications --method org.freedesktop.Notifications.CloseNotification "$NOTIFY_SERVER_ID" >/dev/null 2>&1) &
echo "Parakeet quant: $val"
;;
force_cpu)
# Force CPU mode for all ASR backends (DICTEE_FORCE_CPU=1|0). Restarts
# the currently active ASR service so the daemon picks the right
# execution provider at load.
val="${2:-toggle}"
case "$val" in
true|1) val="1" ;;
false|0) val="0" ;;
toggle)
set +eu; source "$CONF"; set -eu
if [ "${DICTEE_FORCE_CPU:-0}" = "1" ] || [ "${DICTEE_FORCE_CPU:-0}" = "true" ]; then val="0"; else val="1"; fi
;;
*) echo "Error: force_cpu expects true, false, 1, 0 or toggle" >&2; usage ;;
esac
set_conf "DICTEE_FORCE_CPU" "$val"
_dbg "force_cpu: $val"
# Restart whichever ASR service is currently active
set +eu; source "$CONF"; set -eu
case "${DICTEE_ASR_BACKEND:-parakeet}" in
parakeet) svc="dictee.service" ;;
canary) svc="dictee-canary.service" ;;
vosk) svc="dictee-vosk.service" ;;
whisper) svc="dictee-whisper.service" ;;
whisper-rust) svc="dictee-whisper-rust.service" ;;
nemotron) svc="dictee-nemotron.service" ;;
kyutai) svc="dictee-kyutai.service" ;;
*) svc="dictee.service" ;;
esac
systemctl --user restart "$svc" 2>/dev/null || true
if [ "$val" = "1" ]; then
notify_dictee 0 cpu-symbolic "Force CPU: ON (GPU disabled, daemon restarting)"
else
notify_dictee 0 cpu-symbolic "Force CPU: OFF (GPU used if available, daemon restarting)"
fi
(sleep 3; gdbus call --session --dest org.freedesktop.Notifications --object-path /org/freedesktop/Notifications --method org.freedesktop.Notifications.CloseNotification "$NOTIFY_SERVER_ID" >/dev/null 2>&1) &
echo "Force CPU: $val"
;;
*) usage ;;
esac