Percy3822 commited on
Commit
c2abb2e
·
verified ·
1 Parent(s): 3b4600e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +25 -17
app.py CHANGED
@@ -1,14 +1,28 @@
1
- # --- add to your existing ActualTTS app.py ---
 
2
 
3
- from fastapi import WebSocket, WebSocketDisconnect
4
- import asyncio
5
- import base64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
  @app.websocket("/ws/tts")
8
  async def ws_tts(ws: WebSocket):
9
  await ws.accept()
10
  try:
11
- # First message must be JSON: {"text": "...", "voice": "en_US-amy-medium"}
12
  init = await ws.receive_json()
13
  text = (init.get("text") or "").strip()
14
  voice = (init.get("voice") or DEFAULT_VOICE).strip()
@@ -20,38 +34,32 @@ async def ws_tts(ws: WebSocket):
20
  return
21
 
22
  onnx, cfg = voice_paths(voice)
23
-
24
- # Piper in raw mode (S16LE PCM) -> stdout
25
  cmd = [
26
  PIPER, "-m", str(onnx), "-c", str(cfg),
27
- "--output-raw", "-",
28
- # You can pass knobs too, e.g. "--length-scale","1.0"
29
  ]
30
 
31
- # Start process; feed text via stdin
32
  proc = await asyncio.create_subprocess_exec(
33
  *cmd,
34
  stdin=asyncio.subprocess.PIPE,
35
  stdout=asyncio.subprocess.PIPE,
36
  stderr=asyncio.subprocess.PIPE,
37
  )
38
- # Tell client what to expect
39
- # Piper voices define sample rate in JSON; common: 22050 or 24000
40
- # If you want to read it, parse cfg; for now send a safe default 22050
41
  await ws.send_json({"type": "format", "encoding": "s16le", "sample_rate": 22050})
42
 
43
- # Send text
44
  proc.stdin.write(text.encode("utf-8"))
45
  await proc.stdin.drain()
46
  proc.stdin.close()
47
 
48
- # Stream stdout in chunks
49
- CHUNK = 4096 # bytes
50
  while True:
51
  chunk = await proc.stdout.read(CHUNK)
52
  if not chunk:
53
  break
54
- # Send as base64 so browser/Qt can decode
55
  await ws.send_json({"type": "audio", "data": base64.b64encode(chunk).decode("ascii")})
56
 
57
  rc = await proc.wait()
 
1
+ import os, asyncio, base64, json
2
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect
3
 
4
+ # Create app FIRST
5
+ app = FastAPI(title="ActualTTS Streaming")
6
+
7
+ PIPER = "/usr/local/bin/piper"
8
+ DEFAULT_VOICE = "en_US-amy-medium"
9
+ VOICE_DIR = "/home/user/voices"
10
+
11
+ def voice_paths(voice: str):
12
+ onnx = os.path.join(VOICE_DIR, f"{voice}.onnx")
13
+ cfg = os.path.join(VOICE_DIR, f"{voice}.onnx.json")
14
+ return onnx, cfg
15
+
16
+ def ensure_voice_available(voice: str):
17
+ onnx, cfg = voice_paths(voice)
18
+ if not os.path.exists(onnx) or not os.path.exists(cfg):
19
+ return f"Voice {voice} not found in {VOICE_DIR}"
20
+ return None
21
 
22
  @app.websocket("/ws/tts")
23
  async def ws_tts(ws: WebSocket):
24
  await ws.accept()
25
  try:
 
26
  init = await ws.receive_json()
27
  text = (init.get("text") or "").strip()
28
  voice = (init.get("voice") or DEFAULT_VOICE).strip()
 
34
  return
35
 
36
  onnx, cfg = voice_paths(voice)
 
 
37
  cmd = [
38
  PIPER, "-m", str(onnx), "-c", str(cfg),
39
+ "--output-raw", "-", # raw PCM
 
40
  ]
41
 
 
42
  proc = await asyncio.create_subprocess_exec(
43
  *cmd,
44
  stdin=asyncio.subprocess.PIPE,
45
  stdout=asyncio.subprocess.PIPE,
46
  stderr=asyncio.subprocess.PIPE,
47
  )
48
+
49
+ # Tell client expected audio format
 
50
  await ws.send_json({"type": "format", "encoding": "s16le", "sample_rate": 22050})
51
 
52
+ # Send text to Piper
53
  proc.stdin.write(text.encode("utf-8"))
54
  await proc.stdin.drain()
55
  proc.stdin.close()
56
 
57
+ # Stream audio chunks
58
+ CHUNK = 4096
59
  while True:
60
  chunk = await proc.stdout.read(CHUNK)
61
  if not chunk:
62
  break
 
63
  await ws.send_json({"type": "audio", "data": base64.b64encode(chunk).decode("ascii")})
64
 
65
  rc = await proc.wait()