yash184 commited on
Commit
c51b309
·
verified ·
1 Parent(s): ee55d74

Update App.py

Browse files
Files changed (1) hide show
  1. App.py +55 -72
App.py CHANGED
@@ -5,59 +5,34 @@ from fastapi import FastAPI, Request
5
  from fastapi.middleware.cors import CORSMiddleware
6
  from fastapi.responses import JSONResponse, StreamingResponse
7
  from pydantic import BaseModel
 
8
 
9
  torch.set_num_threads(int(os.environ.get("TORCH_THREADS", "2")))
10
 
11
- HERE = os.path.dirname(os.path.abspath(__file__))
12
- SECRET = os.environ.get("TTS_SECRET_KEY", "") # Space Settings -> Secrets me daalo
13
- MODEL_REPO = "saryps-labs/pocket-tts-hindi"
14
- LOCAL_DIR = "/home/user/hindi_model"
15
- VOICE_FILE = os.path.join(HERE, "voice.wav")
16
- FALLBACK_VOICE = "hf://kyutai/tts-voices/alba-mackenna/casual.wav"
17
- QUANTIZE = os.environ.get("TTS_QUANTIZE", "0") == "1"
18
  MAX_CHARS = int(os.environ.get("MAX_CHARS", "6000"))
19
- GAP_SECONDS = 0.22
20
-
21
- model = None
22
- voice_state = None
23
  SR = 24000
 
 
 
24
  state_msg = "loading"
25
  last_error = ""
26
  gen_lock = threading.Lock()
27
 
28
 
29
- def get_config_path() -> str:
30
- cfg = os.environ.get("TTS_CONFIG") or os.path.join(LOCAL_DIR, "config.yaml")
31
- if not os.path.exists(cfg):
32
- from huggingface_hub import snapshot_download
33
- snapshot_download(MODEL_REPO, local_dir=LOCAL_DIR)
34
- cfg = os.path.join(LOCAL_DIR, "config.yaml")
35
- return cfg
36
-
37
-
38
  def load_everything():
39
- global model, voice_state, SR, state_msg, last_error
40
  try:
41
- from pocket_tts import TTSModel
42
- cfg = get_config_path()
43
- print("Config:", cfg)
44
- try:
45
- m = TTSModel.load_model(cfg, temp=0.3, lsd_decode_steps=1, eos_threshold=-1.0)
46
- except TypeError:
47
- m = TTSModel.load_model(cfg)
48
- if QUANTIZE:
49
- try:
50
- m = torch.quantization.quantize_dynamic(m, {torch.nn.Linear}, dtype=torch.qint8)
51
- print("int8 quantization ON")
52
- except Exception as e:
53
- print("quantize fail, fp32 chal raha he:", e)
54
- voice_path = VOICE_FILE if os.path.exists(VOICE_FILE) else FALLBACK_VOICE
55
- print("Voice prompt:", voice_path)
56
- voice_state = m.get_state_for_audio_prompt(voice_path)
57
- SR = int(getattr(m, "sample_rate", 24000))
58
- model = m
59
  state_msg = "ready"
60
- print("Model ready, sample rate", SR)
61
  except Exception as e:
62
  state_msg = "error"
63
  last_error = f"{type(e).__name__}: {e}"
@@ -90,31 +65,32 @@ def authorized(request: Request) -> bool:
90
 
91
 
92
  def split_clean(text: str):
93
- """Text ko vakyon me todo aur punctuation/English hata do (model punctuation nahi samajhta)."""
94
- text = text.replace("\r", "\n")
95
- parts = re.split(r"[\u0964\u0965\.\?\!\n;:]+", text)
96
- out = []
97
- for p in parts:
98
- p = re.sub(r"[^\u0900-\u0963\u0966-\u097F0-9\s]", " ", p)
99
  p = re.sub(r"\s+", " ", p).strip()
100
- if len(p) < 3:
101
  continue
102
- while len(p) > 220:
103
- cut = p.rfind(" ", 0, 200)
104
- cut = cut if cut > 50 else 200
105
- out.append(p[:cut].strip())
106
  p = p[cut:].strip()
107
  if p:
108
- out.append(p)
109
- return out
110
-
111
-
112
- def iter_audio(text: str):
113
- if hasattr(model, "generate_audio_stream"):
114
- for a in model.generate_audio_stream(voice_state, text):
115
- yield a
116
- else:
117
- yield model.generate_audio(voice_state, text)
 
118
 
119
 
120
  def to_pcm16(a) -> bytes:
@@ -123,21 +99,26 @@ def to_pcm16(a) -> bytes:
123
  return (arr * 32767.0).astype("<i2").tobytes()
124
 
125
 
126
- def pcm_stream(sentences):
127
  silence = np.zeros(int(GAP_SECONDS * SR), dtype="<i2").tobytes()
128
- with gen_lock:
129
- for s in sentences:
130
  try:
131
- for a in iter_audio(s):
132
- yield to_pcm16(a)
 
 
 
133
  yield silence
134
  except Exception:
135
- print("Sentence fail:", s[:60])
136
  traceback.print_exc()
137
 
138
 
139
  class TTSReq(BaseModel):
140
  text: str
 
 
141
 
142
 
143
  @app.get("/")
@@ -156,15 +137,17 @@ def tts(req: TTSReq, request: Request):
156
  return JSONResponse({"error": "unauthorized"}, status_code=401)
157
  if state_msg == "loading":
158
  return JSONResponse({"error": "model abhi load ho raha he, 1 minute baad try karo"}, status_code=503)
159
- if state_msg == "error" or model is None:
160
  return JSONResponse({"error": "model load fail hua: " + last_error}, status_code=500)
161
  if len(req.text) > MAX_CHARS:
162
  return JSONResponse({"error": f"text bahut lamba he (max {MAX_CHARS} characters)"}, status_code=413)
163
- sentences = split_clean(req.text)
164
- if not sentences:
165
- return JSONResponse({"error": "text me Hindi (देवनागरी) shabd nahi mile"}, status_code=400)
 
 
166
  return StreamingResponse(
167
- pcm_stream(sentences),
168
  media_type="application/octet-stream",
169
  headers={"X-Sample-Rate": str(SR), "Cache-Control": "no-store"},
170
  )
 
5
  from fastapi.middleware.cors import CORSMiddleware
6
  from fastapi.responses import JSONResponse, StreamingResponse
7
  from pydantic import BaseModel
8
+ from typing import Optional
9
 
10
  torch.set_num_threads(int(os.environ.get("TORCH_THREADS", "2")))
11
 
12
+ SECRET = os.environ.get("TTS_SECRET_KEY", "") # Space Settings -> Secrets me daalo
13
+ DEFAULT_VOICE = os.environ.get("VOICE", "hf_alpha") # hf_alpha, hf_beta (female) | hm_omega, hm_psi (male)
14
+ ALLOWED_VOICES = {"hf_alpha", "hf_beta", "hm_omega", "hm_psi"}
15
+ DEFAULT_SPEED = float(os.environ.get("SPEED", "1.0"))
 
 
 
16
  MAX_CHARS = int(os.environ.get("MAX_CHARS", "6000"))
 
 
 
 
17
  SR = 24000
18
+ GAP_SECONDS = 0.15
19
+
20
+ pipeline = None
21
  state_msg = "loading"
22
  last_error = ""
23
  gen_lock = threading.Lock()
24
 
25
 
 
 
 
 
 
 
 
 
 
26
  def load_everything():
27
+ global pipeline, state_msg, last_error
28
  try:
29
+ from kokoro import KPipeline
30
+ p = KPipeline(lang_code="h", repo_id="hexgrad/Kokoro-82M")
31
+ for _ in p("नमस्ते दोस्तों", voice=DEFAULT_VOICE): # warm-up
32
+ break
33
+ pipeline = p
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  state_msg = "ready"
35
+ print("Kokoro ready")
36
  except Exception as e:
37
  state_msg = "error"
38
  last_error = f"{type(e).__name__}: {e}"
 
65
 
66
 
67
  def split_clean(text: str):
68
+ """Text saaf karo, vakyon me todo, phir ~200 chars ke tukdon me jodo (Kokoro ko medium length pasand he)."""
69
+ text = re.sub(r"[\*#_`~>\[\]\{\}\(\)<>|\\/]", " ", text)
70
+ text = text.replace("\u0964", ".").replace("\u0965", ".").replace("\r", "\n")
71
+ pieces = re.split(r"(?<=[\.\?\!])\s+|\n+", text)
72
+ sents = []
73
+ for p in pieces:
74
  p = re.sub(r"\s+", " ", p).strip()
75
+ if len(p) < 2:
76
  continue
77
+ while len(p) > 300:
78
+ cut = p.rfind(" ", 0, 260)
79
+ cut = cut if cut > 80 else 260
80
+ sents.append(p[:cut].strip())
81
  p = p[cut:].strip()
82
  if p:
83
+ sents.append(p)
84
+ chunks, cur = [], ""
85
+ for s in sents:
86
+ if cur and len(cur) + len(s) + 1 > 200:
87
+ chunks.append(cur)
88
+ cur = s
89
+ else:
90
+ cur = (cur + " " + s).strip()
91
+ if cur:
92
+ chunks.append(cur)
93
+ return chunks
94
 
95
 
96
  def to_pcm16(a) -> bytes:
 
99
  return (arr * 32767.0).astype("<i2").tobytes()
100
 
101
 
102
+ def pcm_stream(chunks, voice, speed):
103
  silence = np.zeros(int(GAP_SECONDS * SR), dtype="<i2").tobytes()
104
+ with gen_lock: # ek samay me ek hi generation
105
+ for c in chunks:
106
  try:
107
+ for item in pipeline(c, voice=voice, speed=speed, split_pattern=r"\n+"):
108
+ audio = item.audio if hasattr(item, "audio") else item[2]
109
+ if audio is None:
110
+ continue
111
+ yield to_pcm16(audio)
112
  yield silence
113
  except Exception:
114
+ print("Chunk fail:", c[:60])
115
  traceback.print_exc()
116
 
117
 
118
  class TTSReq(BaseModel):
119
  text: str
120
+ voice: Optional[str] = None
121
+ speed: Optional[float] = None
122
 
123
 
124
  @app.get("/")
 
137
  return JSONResponse({"error": "unauthorized"}, status_code=401)
138
  if state_msg == "loading":
139
  return JSONResponse({"error": "model abhi load ho raha he, 1 minute baad try karo"}, status_code=503)
140
+ if state_msg == "error" or pipeline is None:
141
  return JSONResponse({"error": "model load fail hua: " + last_error}, status_code=500)
142
  if len(req.text) > MAX_CHARS:
143
  return JSONResponse({"error": f"text bahut lamba he (max {MAX_CHARS} characters)"}, status_code=413)
144
+ chunks = split_clean(req.text)
145
+ if not chunks:
146
+ return JSONResponse({"error": "text me padhne layak kuch nahi mila"}, status_code=400)
147
+ voice = req.voice if req.voice in ALLOWED_VOICES else DEFAULT_VOICE
148
+ speed = min(max(req.speed or DEFAULT_SPEED, 0.7), 1.3)
149
  return StreamingResponse(
150
+ pcm_stream(chunks, voice, speed),
151
  media_type="application/octet-stream",
152
  headers={"X-Sample-Rate": str(SR), "Cache-Control": "no-store"},
153
  )