Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- Dockerfile +1 -1
- app.py +1 -105
- requirements.txt +1 -0
Dockerfile
CHANGED
|
@@ -4,4 +4,4 @@ COPY requirements.txt .
|
|
| 4 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 5 |
COPY app.py .
|
| 6 |
EXPOSE 7860
|
| 7 |
-
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"
|
|
|
|
| 4 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 5 |
COPY app.py .
|
| 6 |
EXPOSE 7860
|
| 7 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
app.py
CHANGED
|
@@ -49,7 +49,7 @@ for size, repo in MODEL_IDS.items():
|
|
| 49 |
local_dir = snapshot_download(repo_id=repo, token=HF_TOKEN)
|
| 50 |
PROCESSORS[size] = WhisperProcessor.from_pretrained(local_dir)
|
| 51 |
MODELS[size] = ctranslate2.models.Whisper(
|
| 52 |
-
local_dir, compute_type="int8", inter_threads=1, intra_threads=
|
| 53 |
)
|
| 54 |
log_event("model_loaded_ok", model_size=size)
|
| 55 |
except Exception as e:
|
|
@@ -247,110 +247,6 @@ async def transcribe(
|
|
| 247 |
return {"request_id": request_id, "model_size": model_size, "language": language, "text": text,
|
| 248 |
"audio_duration_s": round(duration_s, 2), "chunks_processed": n_chunks, "elapsed_s": round(elapsed, 2)}
|
| 249 |
|
| 250 |
-
@app.websocket("/ws/transcribe")
|
| 251 |
-
async def ws_transcribe(
|
| 252 |
-
websocket: WebSocket,
|
| 253 |
-
model_size: str = Query("large"),
|
| 254 |
-
language: str = Query(...),
|
| 255 |
-
token: str = Query(...),
|
| 256 |
-
):
|
| 257 |
-
try:
|
| 258 |
-
info = hf_api.whoami(token=token)
|
| 259 |
-
user_orgs = [o.get("name") for o in info.get("orgs", [])]
|
| 260 |
-
if ORG_NAME not in user_orgs:
|
| 261 |
-
await websocket.close(code=4403); return
|
| 262 |
-
username = info.get("name", "unknown")
|
| 263 |
-
except Exception:
|
| 264 |
-
await websocket.close(code=4401); return
|
| 265 |
-
|
| 266 |
-
if model_size not in MODELS or language not in VALID_LANGS or VAD_MODEL is None:
|
| 267 |
-
await websocket.close(code=4400); return
|
| 268 |
-
|
| 269 |
-
await websocket.accept()
|
| 270 |
-
send_lock = asyncio.Lock()
|
| 271 |
-
async def safe_send_json(payload):
|
| 272 |
-
async with send_lock:
|
| 273 |
-
await websocket.send_json(payload)
|
| 274 |
-
async def heartbeat(interval: float = 20.0):
|
| 275 |
-
while True:
|
| 276 |
-
await asyncio.sleep(interval)
|
| 277 |
-
try:
|
| 278 |
-
await safe_send_json({"type": "heartbeat"})
|
| 279 |
-
log_event("ws_heartbeat_sent", user=username)
|
| 280 |
-
except Exception as e:
|
| 281 |
-
log_event("ws_heartbeat_failed", user=username, error=str(e))
|
| 282 |
-
return
|
| 283 |
-
heartbeat_task = asyncio.create_task(heartbeat())
|
| 284 |
-
log_event("ws_connected", user=username, model_size=model_size, language=language)
|
| 285 |
-
|
| 286 |
-
SR = 16000
|
| 287 |
-
FRAME_SAMPLES = 512
|
| 288 |
-
FRAME_BYTES = FRAME_SAMPLES * 2
|
| 289 |
-
SILENCE_FRAMES_TO_FLUSH = max(1, int(600 / ((FRAME_SAMPLES / SR) * 1000)))
|
| 290 |
-
MAX_UTTERANCE_SAMPLES = 25 * SR
|
| 291 |
-
SPEECH_PROB_THRESHOLD = 0.5
|
| 292 |
-
|
| 293 |
-
byte_buffer = b""
|
| 294 |
-
speech_frames, speech_started, silence_run = [], False, 0
|
| 295 |
-
|
| 296 |
-
async def flush_and_transcribe():
|
| 297 |
-
nonlocal speech_frames, speech_started, silence_run
|
| 298 |
-
if not speech_frames:
|
| 299 |
-
speech_started, silence_run = False, 0
|
| 300 |
-
return
|
| 301 |
-
audio = np.concatenate(speech_frames)
|
| 302 |
-
speech_frames, speech_started, silence_run = [], False, 0
|
| 303 |
-
if len(audio) < int(0.2 * SR):
|
| 304 |
-
return
|
| 305 |
-
try:
|
| 306 |
-
text, duration_s, _ = await asyncio.to_thread(run_inference, audio, SR, model_size, language)
|
| 307 |
-
await safe_send_json({"type": "final", "text": text, "audio_duration_s": round(duration_s, 2)})
|
| 308 |
-
log_event("ws_utterance_transcribed", user=username, duration_s=round(duration_s, 2))
|
| 309 |
-
except Exception as e:
|
| 310 |
-
log_event("ws_transcribe_error", user=username, error=str(e))
|
| 311 |
-
try:
|
| 312 |
-
await safe_send_json({"type": "error", "detail": "transcription failed"})
|
| 313 |
-
except Exception:
|
| 314 |
-
pass # socket already closed - nothing to send to
|
| 315 |
-
|
| 316 |
-
try:
|
| 317 |
-
while True:
|
| 318 |
-
data = await websocket.receive_bytes()
|
| 319 |
-
byte_buffer += data
|
| 320 |
-
while len(byte_buffer) >= FRAME_BYTES:
|
| 321 |
-
frame_bytes, byte_buffer = byte_buffer[:FRAME_BYTES], byte_buffer[FRAME_BYTES:]
|
| 322 |
-
frame_f32 = np.frombuffer(frame_bytes, dtype=np.int16).astype(np.float32) / 32768.0
|
| 323 |
-
prob = VAD_MODEL(torch.from_numpy(frame_f32), SR).item()
|
| 324 |
-
|
| 325 |
-
if prob >= SPEECH_PROB_THRESHOLD:
|
| 326 |
-
if not speech_started:
|
| 327 |
-
speech_started = True
|
| 328 |
-
await safe_send_json({"type": "speech_start"})
|
| 329 |
-
speech_frames.append(frame_f32)
|
| 330 |
-
silence_run = 0
|
| 331 |
-
elif speech_started:
|
| 332 |
-
speech_frames.append(frame_f32)
|
| 333 |
-
silence_run += 1
|
| 334 |
-
if silence_run >= SILENCE_FRAMES_TO_FLUSH:
|
| 335 |
-
await flush_and_transcribe()
|
| 336 |
-
|
| 337 |
-
if sum(len(f) for f in speech_frames) >= MAX_UTTERANCE_SAMPLES:
|
| 338 |
-
log_event("ws_max_utterance_reached", user=username)
|
| 339 |
-
await flush_and_transcribe()
|
| 340 |
-
except WebSocketDisconnect:
|
| 341 |
-
log_event("ws_disconnected", user=username)
|
| 342 |
-
# client is gone - do not attempt to send anything further.
|
| 343 |
-
# just discard any partial in-flight utterance rather than
|
| 344 |
-
# transcribing into a socket that no longer exists.
|
| 345 |
-
except RuntimeError as e:
|
| 346 |
-
# server-initiated close (e.g. ping timeout) leaves the socket in
|
| 347 |
-
# a disconnected state; a subsequent receive_bytes() raises
|
| 348 |
-
# RuntimeError instead of WebSocketDisconnect. Treat the same way.
|
| 349 |
-
log_event("ws_disconnected_runtime", user=username, error=str(e))
|
| 350 |
-
finally:
|
| 351 |
-
heartbeat_task.cancel()
|
| 352 |
-
|
| 353 |
-
|
| 354 |
@app.exception_handler(HTTPException)
|
| 355 |
async def http_exception_handler(request: Request, exc: HTTPException):
|
| 356 |
log_event("request_error", path=str(request.url.path), status=exc.status_code, detail=exc.detail)
|
|
|
|
| 49 |
local_dir = snapshot_download(repo_id=repo, token=HF_TOKEN)
|
| 50 |
PROCESSORS[size] = WhisperProcessor.from_pretrained(local_dir)
|
| 51 |
MODELS[size] = ctranslate2.models.Whisper(
|
| 52 |
+
local_dir, compute_type="int8", inter_threads=1, intra_threads=2
|
| 53 |
)
|
| 54 |
log_event("model_loaded_ok", model_size=size)
|
| 55 |
except Exception as e:
|
|
|
|
| 247 |
return {"request_id": request_id, "model_size": model_size, "language": language, "text": text,
|
| 248 |
"audio_duration_s": round(duration_s, 2), "chunks_processed": n_chunks, "elapsed_s": round(elapsed, 2)}
|
| 249 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
@app.exception_handler(HTTPException)
|
| 251 |
async def http_exception_handler(request: Request, exc: HTTPException):
|
| 252 |
log_event("request_error", path=str(request.url.path), status=exc.status_code, detail=exc.detail)
|
requirements.txt
CHANGED
|
@@ -16,3 +16,4 @@ websockets
|
|
| 16 |
# force rebuild 1786901172
|
| 17 |
# force rebuild 1786902184
|
| 18 |
# force rebuild 1786902905
|
|
|
|
|
|
| 16 |
# force rebuild 1786901172
|
| 17 |
# force rebuild 1786902184
|
| 18 |
# force rebuild 1786902905
|
| 19 |
+
# revert streaming, force rebuild 1786903429
|