From 104f7bde03099ca99c6813925f12357ca3962238 Mon Sep 17 00:00:00 2001 From: Quentin Fuxa Date: Mon, 16 Dec 2024 13:24:27 +0100 Subject: [PATCH] add fastapi server with live webm to pcm conversion and web page showing both complete transcription and partial transcription --- README.md | 45 ++++++++++ src/demo.png | Bin 0 -> 76920 bytes src/live_transcription.html | 111 ++++++++++++++++++++++++ whisper_fastapi_online_server.py | 140 +++++++++++++++++++++++++++++++ 4 files changed, 296 insertions(+) create mode 100644 src/demo.png create mode 100644 src/live_transcription.html create mode 100644 whisper_fastapi_online_server.py diff --git a/README.md b/README.md index 1670ba9..b0c7916 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,51 @@ arecord -f S16_LE -c1 -r 16000 -t raw -D default | nc localhost 43001 - nc is netcat with server's host and port +## Live Transcription Web Interface + +This repository also includes a **FastAPI server** and an **HTML/JavaScript client** for quick testing of live speech transcription in the browser. The client uses native WebSockets and the `MediaRecorder` API to capture microphone audio in **WebM** format and send it to the server—**no additional front-end framework** is required. + +![Demo Screenshot](src/demo.png) + +### How to Launch the Server + +1. **Install Dependencies**: + + ```bash + pip install -r requirements.txt + ``` + +2. **Run the FastAPI Server**: + + ```bash + python whisper_fastapi_online_server.py --host 0.0.0.0 --port 8000 + ``` + + - `--host` and `--port` let you specify the server’s IP/port. + +3. **Open the Provided HTML**: + + - By default, the server root endpoint `/` serves a simple `live_transcription.html` page. + - Open your browser at `http://localhost:8000` (or replace `localhost` and `8000` with whatever you specified). + - The page uses vanilla JavaScript and the WebSocket API to capture your microphone and stream audio to the server in real time. + +### How the Live Interface Works + +- Once you **allow microphone access**, the page records small chunks of audio using the **MediaRecorder** API in **webm/opus** format. +- These chunks are sent over a **WebSocket** to the FastAPI endpoint at `/ws`. +- The Python server decodes `.webm` chunks on the fly using **FFmpeg** and streams them into **Whisper** for transcription. +- **Partial transcription** appears as soon as enough audio is processed. The “unvalidated” text is shown in **lighter or grey color** (i.e., an ‘aperçu’) to indicate it’s still buffered partial output. Once Whisper finalizes that segment, it’s displayed in normal text. +- You can watch the transcription update in near real time, ideal for demos, prototyping, or quick debugging. + +### Deploying to a Remote Server + +If you want to **deploy** this setup: + +1. **Host the FastAPI app** behind a production-grade HTTP server (like **Uvicorn + Nginx** or Docker). +2. The **HTML/JS page** can be served by the same FastAPI app or a separate static host. +3. Users open the page in **Chrome/Firefox** (any modern browser that supports MediaRecorder + WebSocket). + +No additional front-end libraries or frameworks are required. The WebSocket logic in `live_transcription.html` is minimal enough to adapt for your own custom UI or embed in other pages. ## Background diff --git a/src/demo.png b/src/demo.png new file mode 100644 index 0000000000000000000000000000000000000000..1c58a4deab22f7a8907d37ff3fc7ae29d46027ba GIT binary patch literal 76920 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 KEf&`E{r>>jit!Tw literal 0 HcmV?d00001 diff --git a/src/live_transcription.html b/src/live_transcription.html new file mode 100644 index 0000000..8d215ae --- /dev/null +++ b/src/live_transcription.html @@ -0,0 +1,111 @@ + + + + + + Audio Transcription + + + +

Click to start transcription

+ +
+ + + + \ No newline at end of file diff --git a/whisper_fastapi_online_server.py b/whisper_fastapi_online_server.py new file mode 100644 index 0000000..273432e --- /dev/null +++ b/whisper_fastapi_online_server.py @@ -0,0 +1,140 @@ +import io +import argparse +import asyncio +import numpy as np +import ffmpeg + +from fastapi import FastAPI, WebSocket, WebSocketDisconnect +from fastapi.responses import HTMLResponse +from fastapi.middleware.cors import CORSMiddleware + +from whisper_online import asr_factory, add_shared_args + +app = FastAPI() +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +# Argument parsing +parser = argparse.ArgumentParser() +parser.add_argument("--host", type=str, default='localhost') +parser.add_argument("--port", type=int, default=8000) +parser.add_argument("--warmup-file", type=str, dest="warmup_file", + help="The path to a speech audio wav file to warm up Whisper so that the very first chunk processing is fast. It can be e.g. https://github.com/ggerganov/whisper.cpp/raw/master/samples/jfk.wav .") +add_shared_args(parser) +args = parser.parse_args() + +# Initialize Whisper +asr, online = asr_factory(args) + +# Load demo HTML for the root endpoint +with open("live_transcription.html", "r") as f: + html = f.read() + +@app.get("/") +async def get(): + return HTMLResponse(html) + +# Streaming constants +SAMPLE_RATE = 16000 +CHANNELS = 1 +SAMPLES_PER_SEC = SAMPLE_RATE * int(args.min_chunk_size) +BYTES_PER_SAMPLE = 2 # s16le = 2 bytes per sample +BYTES_PER_SEC = SAMPLES_PER_SEC * BYTES_PER_SAMPLE + +async def start_ffmpeg_decoder(): + """ + Start an FFmpeg process in async streaming mode that reads WebM from stdin + and outputs raw s16le PCM on stdout. Returns the process object. + """ + process = ( + ffmpeg + .input('pipe:0', format='webm') + .output('pipe:1', format='s16le', acodec='pcm_s16le', ac=CHANNELS, ar=str(SAMPLE_RATE)) + .run_async(pipe_stdin=True, pipe_stdout=True, pipe_stderr=True) + ) + return process + +@app.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket): + await websocket.accept() + print("WebSocket connection opened.") + + ffmpeg_process = await start_ffmpeg_decoder() + pcm_buffer = bytearray() + + # Continuously read decoded PCM from ffmpeg stdout in a background task + async def ffmpeg_stdout_reader(): + nonlocal pcm_buffer + loop = asyncio.get_event_loop() + while True: + try: + chunk = await loop.run_in_executor(None, ffmpeg_process.stdout.read, 4096) + if not chunk: # FFmpeg might have closed + print("FFmpeg stdout closed.") + break + + pcm_buffer.extend(chunk) + + # Process in 3-second batches + while len(pcm_buffer) >= BYTES_PER_SEC: + three_sec_chunk = pcm_buffer[:BYTES_PER_SEC] + del pcm_buffer[:BYTES_PER_SEC] + + # Convert int16 -> float32 + pcm_array = np.frombuffer(three_sec_chunk, dtype=np.int16).astype(np.float32) / 32768.0 + + # Send PCM data to Whisper + online.insert_audio_chunk(pcm_array) + transcription = online.process_iter() + buffer = online.to_flush(online.transcript_buffer.buffer) + + # Return partial transcription results to the client + await websocket.send_json({ + "transcription": transcription[2], + "buffer": buffer[2] + }) + except Exception as e: + print(f"Exception in ffmpeg_stdout_reader: {e}") + break + + print("Exiting ffmpeg_stdout_reader...") + + stdout_reader_task = asyncio.create_task(ffmpeg_stdout_reader()) + + try: + while True: + # Receive incoming WebM audio chunks from the client + message = await websocket.receive_bytes() + # Pass them to ffmpeg via stdin + ffmpeg_process.stdin.write(message) + ffmpeg_process.stdin.flush() + + except WebSocketDisconnect: + print("WebSocket connection closed.") + except Exception as e: + print(f"Error in websocket loop: {e}") + finally: + # Clean up ffmpeg and the reader task + try: + ffmpeg_process.stdin.close() + except: + pass + stdout_reader_task.cancel() + + try: + ffmpeg_process.stdout.close() + except: + pass + + ffmpeg_process.wait() + + +if __name__ == "__main__": + import uvicorn + uvicorn.run("whisper_fastapi_online_server:app", host=args.host, port=args.port, reload=True) \ No newline at end of file