Files
lang/ws_server.py
T

76 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""WebSocket Groq Whisper proxy — chunked text frames (DPI-proof)."""
import asyncio, json, websockets, requests, uuid, sys, time, base64
GROQ_BASE = "https://api.groq.com"
def log(msg):
print(f"[{time.strftime('%H:%M:%S')}] {msg}", file=sys.stderr, flush=True)
async def handler(ws):
chunks = {} # index -> base64_part
total = None
token = ""
mime = "audio/webm"
try:
log(f"conn from {ws.remote_address}")
async for raw in ws:
msg = json.loads(raw)
ci = msg.get("c", 0)
if ci == -1:
# Final chunk — reassemble
if not chunks:
await ws.send(json.dumps({"error": "no chunks"}))
return
audio_b64 = "".join(chunks[i] for i in sorted(chunks))
log(f"assembled {len(chunks)} chunks → {len(audio_b64)} chars, decoding...")
audio_data = base64.b64decode(audio_b64)
log(f"decoded {len(audio_data)}B")
ext = "webm"
if "ogg" in mime: ext = "ogg"
elif "mp4" in mime or "aac" in mime: ext = "mp4"
boundary = "----WKFBoundary" + uuid.uuid4().hex[:16]
payload = b"".join([
f'--{boundary}\r\nContent-Disposition: form-data; name="file"; filename="audio.{ext}"\r\nContent-Type: {mime}\r\n\r\n'.encode(),
audio_data,
f'\r\n--{boundary}\r\nContent-Disposition: form-data; name="model"\r\n\r\nwhisper-large-v3'.encode(),
f'\r\n--{boundary}\r\nContent-Disposition: form-data; name="language"\r\n\r\nit'.encode(),
f'\r\n--{boundary}--\r\n'.encode(),
])
t0 = time.time()
loop = asyncio.get_event_loop()
r = await loop.run_in_executor(None, lambda: requests.post(
f"{GROQ_BASE}/openai/v1/audio/transcriptions",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": f"multipart/form-data; boundary={boundary}",
},
data=payload, timeout=60
))
log(f"Groq {r.status_code} in {time.time()-t0:.2f}s: {r.text[:120]}")
await ws.send(json.dumps({"text": r.json().get("text", ""), "status": r.status_code}))
return
else:
# Data chunk
chunks[ci] = msg.get("b", "")
token = msg.get("t", token)
mime = msg.get("m", mime)
total = msg.get("n", total)
# log(f"chunk {ci+1}/{total}")
except Exception as e:
log(f"ERR: {e}")
try: await ws.send(json.dumps({"error": str(e)}))
except: pass
async def main():
async with websockets.serve(handler, "127.0.0.1", 8766):
log("groq-ws :8766 (chunked)")
await asyncio.Future()
asyncio.run(main())