stt_worker.py 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. #!/usr/bin/env python3
  2. """STT worker subprocess — connects to stt.mm.mk via WebSocket.
  3. Receives PCM via UDP, sends to STT server.
  4. Posts JSON results back to Flask via HTTP POST.
  5. Usage: python3 stt_worker.py <ws_url> --audio-port 19876 --callback-url http://127.0.0.1:5000/internal/stt
  6. """
  7. import argparse
  8. import json
  9. import socket
  10. import threading
  11. import urllib.request
  12. import websocket
  13. def main():
  14. parser = argparse.ArgumentParser()
  15. parser.add_argument("url")
  16. parser.add_argument("--audio-port", type=int, default=19876)
  17. parser.add_argument("--callback-url", default="http://127.0.0.1:5000/internal/stt")
  18. args = parser.parse_args()
  19. connected = False
  20. ws = None
  21. def emit(obj):
  22. try:
  23. data = json.dumps(obj).encode("utf-8")
  24. req = urllib.request.Request(
  25. args.callback_url,
  26. data=data,
  27. headers={"Content-Type": "application/json"},
  28. method="POST",
  29. )
  30. urllib.request.urlopen(req, timeout=2)
  31. except Exception:
  32. pass
  33. def on_open(w):
  34. nonlocal connected
  35. connected = True
  36. emit({"type": "stt_status", "connected": True})
  37. def on_message(w, message):
  38. try:
  39. emit(json.loads(message))
  40. except Exception:
  41. pass
  42. def on_error(w, error):
  43. pass
  44. def on_close(w, code, msg):
  45. nonlocal connected
  46. connected = False
  47. emit({"type": "stt_status", "connected": False})
  48. ws = websocket.WebSocketApp(
  49. args.url,
  50. on_open=on_open,
  51. on_message=on_message,
  52. on_error=on_error,
  53. on_close=on_close,
  54. )
  55. ws_thread = threading.Thread(
  56. target=ws.run_forever,
  57. kwargs={"ping_interval": 20, "ping_timeout": 10},
  58. daemon=True,
  59. )
  60. ws_thread.start()
  61. # Listen for audio on UDP
  62. audio_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  63. audio_sock.bind(("127.0.0.1", args.audio_port))
  64. audio_sock.settimeout(1.0)
  65. try:
  66. while True:
  67. try:
  68. data, _ = audio_sock.recvfrom(65536)
  69. except socket.timeout:
  70. continue
  71. except OSError:
  72. break
  73. if connected and ws and data:
  74. try:
  75. ws.send(data, opcode=0x2)
  76. except Exception:
  77. pass
  78. except KeyboardInterrupt:
  79. pass
  80. finally:
  81. if ws:
  82. ws.close()
  83. audio_sock.close()
  84. if __name__ == "__main__":
  85. main()