Skip to content

Commit ff10387

Browse files
PurpleDoubleDclaude
andcommitted
feat: persistent whisper server + improved ComfyUI discovery
- Replace spawn-per-request with persistent whisper_server.py process that loads the model once (~170s) then transcribes in ~2s - Add stdin/stdout JSON IPC protocol for whisper communication - Recursive ComfyUI scan up to 4 levels deep (finds nested installs) - Extract shared getPythonBin() to top-level (eliminates 3x duplication) - Fix Ollama/ComfyUI spawn: shell:false + windowsHide:true (no extra console windows) - Add loading state to transcribe-status endpoint Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1 parent ed38492 commit ff10387

2 files changed

Lines changed: 262 additions & 144 deletions

File tree

public/whisper_server.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""
2+
Persistent faster-whisper server for Locally Uncensored.
3+
Communicates via stdin/stdout with line-based JSON protocol.
4+
5+
Input (one JSON per line on stdin):
6+
{"action": "transcribe", "path": "/tmp/audio.wav"}
7+
{"action": "status"}
8+
{"action": "quit"}
9+
10+
Output (one JSON per line on stdout):
11+
{"status": "ready", "backend": "faster-whisper"}
12+
{"transcript": "hello world", "language": "en"}
13+
{"error": "..."}
14+
"""
15+
16+
import sys
17+
import json
18+
import os
19+
20+
def main():
21+
# Unbuffered stdout for real-time communication with Node.js
22+
sys.stdout.reconfigure(line_buffering=True)
23+
sys.stderr.reconfigure(line_buffering=True)
24+
25+
print("Loading faster-whisper model...", file=sys.stderr, flush=True)
26+
27+
try:
28+
from faster_whisper import WhisperModel
29+
except ImportError:
30+
respond({"status": "error", "error": "faster-whisper not installed"})
31+
sys.exit(1)
32+
33+
# Load model once — this is the slow part (~170s on some systems)
34+
try:
35+
model = WhisperModel("base", device="cpu", compute_type="int8")
36+
print("Model loaded, ready for transcription.", file=sys.stderr, flush=True)
37+
except Exception as e:
38+
respond({"status": "error", "error": f"Model load failed: {e}"})
39+
sys.exit(1)
40+
41+
# Signal readiness
42+
respond({"status": "ready", "backend": "faster-whisper"})
43+
44+
# Main loop: read commands from stdin
45+
for line in sys.stdin:
46+
line = line.strip()
47+
if not line:
48+
continue
49+
50+
try:
51+
cmd = json.loads(line)
52+
except json.JSONDecodeError:
53+
respond({"error": "Invalid JSON"})
54+
continue
55+
56+
action = cmd.get("action", "")
57+
58+
if action == "status":
59+
respond({"status": "ready", "backend": "faster-whisper"})
60+
61+
elif action == "transcribe":
62+
audio_path = cmd.get("path", "")
63+
if not audio_path or not os.path.exists(audio_path):
64+
respond({"error": f"File not found: {audio_path}", "transcript": ""})
65+
continue
66+
67+
try:
68+
segments, info = model.transcribe(audio_path)
69+
text = " ".join([s.text for s in segments]).strip()
70+
respond({"transcript": text, "language": info.language})
71+
except Exception as e:
72+
respond({"error": str(e), "transcript": ""})
73+
74+
elif action == "quit":
75+
respond({"status": "stopped"})
76+
break
77+
78+
else:
79+
respond({"error": f"Unknown action: {action}"})
80+
81+
82+
def respond(data: dict):
83+
"""Write a JSON response line to stdout."""
84+
print(json.dumps(data), flush=True)
85+
86+
87+
if __name__ == "__main__":
88+
main()

0 commit comments

Comments
 (0)