Mercurial
diff dictation/webrtc_test.py @ 275:78699f810817
Add Qwen3-VL and WebRTC dictation services
Add Bazel targets for the CUDA-backed Qwen3-VL server and a local WebRTC faster-whisper dictation service.
Co-authored-by: Copilot <[email protected]>
Copilot-Session: e3d8cb06-6c95-4ae0-9757-651d3796ab00
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 17 Aug 2026 10:58:47 -0700 |
| parents | |
| children | 49e9e591c9bb |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dictation/webrtc_test.py Mon Aug 17 10:58:47 2026 -0700 @@ -0,0 +1,142 @@ +import asyncio +import json +import math +from pathlib import Path +import tempfile +import unittest +import wave + +from aiortc import RTCPeerConnection, RTCSessionDescription +from aiortc.contrib.media import MediaPlayer +import numpy as np + +from dictation.config import DictationConfig +from dictation.server import DictationService, Offer +from dictation.transcriber import Transcript + + +class FakeTranscriber: + async def warmup(self): + return None + + async def transcribe(self, samples, *, final): + return Transcript( + "final transcript" if final else "partial transcript", + "en", + 0.99, + ) + + +def write_test_audio(path: str) -> None: + sample_rate = 16000 + speech = np.array( + [ + int(1600 * math.sin(2 * math.pi * 440 * index / sample_rate)) + for index in range(sample_rate * 2) + ], + dtype=np.int16, + ) + silence = np.zeros(sample_rate, dtype=np.int16) + with wave.open(path, "wb") as output: + output.setnchannels(1) + output.setsampwidth(2) + output.setframerate(sample_rate) + output.writeframes(np.concatenate((speech, silence)).tobytes()) + + +def test_config() -> DictationConfig: + return DictationConfig( + host="127.0.0.1", + port=8090, + model_dir=Path("/tmp/model"), + compute_type="int8_float16", + max_sessions=1, + partial_interval_ms=500, + silence_ms=200, + max_utterance_seconds=5, + speech_threshold=0.01, + ) + + +class WebRtcTest(unittest.IsolatedAsyncioTestCase): + async def test_audio_track_returns_transcript_events(self): + service = DictationService(test_config(), FakeTranscriber()) + await service.start() + client = RTCPeerConnection() + channel = client.createDataChannel("transcripts") + messages = [] + final_received = asyncio.Event() + audio_file = tempfile.NamedTemporaryFile(suffix=".wav", delete=False) + audio_file.close() + write_test_audio(audio_file.name) + player = MediaPlayer(audio_file.name) + + @channel.on("message") + def on_message(message): + event = json.loads(message) + messages.append(event) + if event["type"] == "transcript.final": + final_received.set() + + client.addTrack(player.audio) + offer = await client.createOffer() + await client.setLocalDescription(offer) + answer = await service.accept_offer( + Offer( + sdp=client.localDescription.sdp, + type=client.localDescription.type, + ) + ) + await client.setRemoteDescription( + RTCSessionDescription( + sdp=answer["sdp"], + type=answer["type"], + ) + ) + + try: + try: + await asyncio.wait_for(final_received.wait(), timeout=10) + except TimeoutError as error: + server_states = [ + { + "connection": session.peer.connectionState, + "ice": session.peer.iceConnectionState, + "channel": ( + session.channel.readyState + if session.channel is not None + else None + ), + "audioTask": ( + "missing" + if session.audio_task is None + else ( + repr(session.audio_task.exception()) + if session.audio_task.done() + and not session.audio_task.cancelled() + else "running" + ) + ), + } + for session in service.sessions.values() + ] + self.fail( + "Timed out waiting for transcript: " + f"client={client.connectionState}/" + f"{client.iceConnectionState}, " + f"server={server_states}, messages={messages}" + ) + event_types = [message["type"] for message in messages] + self.assertIn("ready", event_types) + self.assertIn("speech.started", event_types) + self.assertIn("transcript.partial", event_types) + self.assertIn("transcript.final", event_types) + finally: + await client.close() + await service.close() + Path(audio_file.name).unlink(missing_ok=True) + self.assertEqual(service.sessions, {}) + + +if __name__ == "__main__": + unittest.main()