Persist realtime transcription sessions to history
The websocket handler now accumulates the full session's PCM audio
(not just the per-chunk buffer that gets discarded after each
worker call) and, on stop, writes it as a wav to UPLOAD_DIR plus a
result JSON to RESULT_DIR via the new _save_realtime_history()
helper — same shape /transcribe already writes, tagged
gateway_backend="realtime" so the history table visually
distinguishes it from file uploads. This reuses /asr/history and
/asr/uploads/{filename} as-is; no new endpoints needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -268,6 +268,41 @@ async def _send_chunk(wav_bytes: bytes, *, model: str, language: str, beam_size:
|
||||
return resp.json()
|
||||
|
||||
|
||||
def _save_realtime_history(
|
||||
*,
|
||||
all_audio: bytearray,
|
||||
text: str,
|
||||
segments: List[Dict[str, Any]],
|
||||
model: str,
|
||||
language: str,
|
||||
duration: float,
|
||||
) -> Optional[str]:
|
||||
if not all_audio:
|
||||
return None
|
||||
|
||||
ensure_runtime_dirs()
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
|
||||
saved_upload = UPLOAD_DIR / f"{timestamp}.wav"
|
||||
saved_upload.write_bytes(_pcm_float32_to_wav(bytes(all_audio)))
|
||||
|
||||
payload = {
|
||||
"backend": "faster-whisper",
|
||||
"gateway_backend": "realtime",
|
||||
"model": model,
|
||||
"language": language,
|
||||
"duration": round(duration, 3),
|
||||
"text": text,
|
||||
"segments": segments,
|
||||
"diarized": False,
|
||||
"_filename": "실시간 녹음",
|
||||
"_timestamp": timestamp,
|
||||
"upload_file": str(saved_upload),
|
||||
}
|
||||
result_path = RESULT_DIR / f"{timestamp}.json"
|
||||
result_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
return timestamp
|
||||
|
||||
|
||||
@ws_router.websocket("/ws/realtime")
|
||||
async def realtime_ws(websocket: WebSocket) -> None:
|
||||
if not websocket.session.get("user"):
|
||||
@@ -276,6 +311,7 @@ async def realtime_ws(websocket: WebSocket) -> None:
|
||||
await websocket.accept()
|
||||
|
||||
audio_buf = bytearray()
|
||||
all_audio = bytearray()
|
||||
partial_texts: List[str] = []
|
||||
all_segments: List[Dict[str, Any]] = []
|
||||
time_offset = 0.0
|
||||
@@ -333,6 +369,7 @@ async def realtime_ws(websocket: WebSocket) -> None:
|
||||
break
|
||||
if "bytes" in data:
|
||||
audio_buf.extend(data["bytes"])
|
||||
all_audio.extend(data["bytes"])
|
||||
while len(audio_buf) >= chunk_bytes:
|
||||
await process_buffer(bytearray(audio_buf[:chunk_bytes]))
|
||||
audio_buf = audio_buf[chunk_bytes:]
|
||||
@@ -341,11 +378,21 @@ async def realtime_ws(websocket: WebSocket) -> None:
|
||||
if msg.get("cmd") == "stop":
|
||||
if audio_buf:
|
||||
await process_buffer(audio_buf)
|
||||
full_text = " ".join(x for x in partial_texts if x)
|
||||
result_id = _save_realtime_history(
|
||||
all_audio=all_audio,
|
||||
text=full_text,
|
||||
segments=all_segments,
|
||||
model=model,
|
||||
language=language,
|
||||
duration=time_offset,
|
||||
)
|
||||
await websocket.send_json({
|
||||
"type": "final",
|
||||
"text": " ".join(x for x in partial_texts if x),
|
||||
"text": full_text,
|
||||
"segments": all_segments,
|
||||
"diarized": False,
|
||||
"id": result_id,
|
||||
})
|
||||
break
|
||||
|
||||
|
||||
Reference in New Issue
Block a user