stt_worker.py 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. #!/usr/bin/env python3
  2. """STT worker subprocess — connects to stt.mm.mk via WebSocket.
  3. Reads raw PCM int16 binary chunks from stdin, sends to STT server.
  4. Writes JSON messages (one per line) to stdout.
  5. Usage: python3 stt_worker.py <ws_url>
  6. """
  7. import json
  8. import sys
  9. import threading
  10. import time
  11. import websocket
  12. def main():
  13. if len(sys.argv) < 2:
  14. sys.exit(1)
  15. url = sys.argv[1]
  16. connected = False
  17. ws = None
  18. def on_open(w):
  19. nonlocal connected
  20. connected = True
  21. _emit({"type": "stt_status", "connected": True})
  22. def on_message(w, message):
  23. try:
  24. msg = json.loads(message)
  25. _emit(msg)
  26. except Exception:
  27. pass
  28. def on_error(w, error):
  29. _emit({"type": "stt_error", "error": str(error)})
  30. def on_close(w, code, msg):
  31. nonlocal connected
  32. connected = False
  33. _emit({"type": "stt_status", "connected": False})
  34. def _emit(obj):
  35. try:
  36. line = json.dumps(obj, ensure_ascii=True)
  37. sys.stdout.write(line + "\n")
  38. sys.stdout.flush()
  39. except Exception:
  40. pass
  41. ws = websocket.WebSocketApp(
  42. url,
  43. on_open=on_open,
  44. on_message=on_message,
  45. on_error=on_error,
  46. on_close=on_close,
  47. )
  48. # Run WebSocket in background thread
  49. ws_thread = threading.Thread(
  50. target=ws.run_forever,
  51. kwargs={"ping_interval": 20, "ping_timeout": 10},
  52. daemon=True,
  53. )
  54. ws_thread.start()
  55. # Read PCM chunks from stdin and forward to WebSocket
  56. try:
  57. while True:
  58. # Read 4-byte length prefix, then that many bytes of PCM
  59. header = sys.stdin.buffer.read(4)
  60. if not header or len(header) < 4:
  61. break
  62. length = int.from_bytes(header, "little")
  63. if length <= 0 or length > 1_000_000:
  64. continue
  65. data = sys.stdin.buffer.read(length)
  66. if not data or len(data) < length:
  67. break
  68. if connected and ws:
  69. try:
  70. ws.send(data, opcode=0x2)
  71. except Exception:
  72. pass
  73. except (BrokenPipeError, KeyboardInterrupt):
  74. pass
  75. finally:
  76. if ws:
  77. ws.close()
  78. if __name__ == "__main__":
  79. main()