yash184 commited on
Commit
e531950
·
verified ·
1 Parent(s): d179ee2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +71 -17
app.py CHANGED
@@ -11,22 +11,60 @@ from typing import Optional
11
  torch.set_num_threads(int(os.environ.get("TORCH_THREADS", "2")))
12
 
13
  HERE = os.path.dirname(os.path.abspath(__file__))
 
 
14
  SECRET = os.environ.get("TTS_SECRET_KEY", "") # khali he to koi key nahi lagegi
 
15
  MODEL_REPO = "saryps-labs/pocket-tts-hindi"
16
  LOCAL_DIR = "/home/user/hindi_model"
17
- VOICE_FILE = os.path.join(HERE, "voice.wav") # apni (permission wali) awaaz ka 10-20 sec sample
18
  FALLBACK_VOICE = "hf://kyutai/tts-voices/alba-mackenna/casual.wav"
19
  MAX_CHARS = int(os.environ.get("MAX_CHARS", "6000"))
20
  GAP_SECONDS = 0.22
21
 
22
  model = None
23
- voice_state = None
24
  SR = 24000
25
  state_msg = "loading"
26
  last_error = ""
27
  gen_lock = threading.Lock()
 
28
 
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  def get_config_path() -> str:
31
  cfg = os.path.join(LOCAL_DIR, "config.yaml")
32
  if not os.path.exists(cfg):
@@ -43,13 +81,13 @@ def try_load(TTSModel, arg):
43
 
44
 
45
  def load_everything():
46
- global model, voice_state, SR, state_msg, last_error
47
  try:
48
  from pocket_tts import TTSModel
49
  cfg = get_config_path()
50
  print("Config:", cfg)
51
  m, err = None, None
52
- for arg in (Path(cfg), cfg): # pehle Path object, phir string
53
  try:
54
  m = try_load(TTSModel, arg)
55
  break
@@ -58,11 +96,10 @@ def load_everything():
58
  traceback.print_exc()
59
  if m is None:
60
  raise err
61
- voice_path = VOICE_FILE if os.path.exists(VOICE_FILE) else FALLBACK_VOICE
62
- print("Voice prompt:", voice_path)
63
- voice_state = m.get_state_for_audio_prompt(voice_path)
64
- SR = int(getattr(m, "sample_rate", 24000))
65
  model = m
 
 
 
66
  state_msg = "ready"
67
  print("Pocket TTS Hindi ready, sample rate", SR)
68
  except Exception as e:
@@ -85,7 +122,7 @@ app.add_middleware(
85
 
86
  def authorized(request: Request) -> bool:
87
  if not SECRET:
88
- return True # koi secret set nahi he -> sab ko allow
89
  cands = [
90
  request.headers.get("x-secret-key", ""),
91
  request.query_params.get("key", ""),
@@ -96,8 +133,8 @@ def authorized(request: Request) -> bool:
96
  return any(hmac.compare_digest(c.encode(), SECRET.encode()) for c in cands if c)
97
 
98
 
 
99
  def split_clean(text: str):
100
- """Is model ko punctuation nahi chahiye: vakyon me todo, sirf Devanagari + digits rakho."""
101
  text = text.replace("\r", "\n")
102
  parts = re.split(r"[\u0964\u0965\.\?\!\n;:]+", text)
103
  out = []
@@ -116,12 +153,12 @@ def split_clean(text: str):
116
  return out
117
 
118
 
119
- def iter_audio(text: str):
120
  if hasattr(model, "generate_audio_stream"):
121
- for a in model.generate_audio_stream(voice_state, text):
122
  yield a
123
  else:
124
- yield model.generate_audio(voice_state, text)
125
 
126
 
127
  def to_pcm16(a) -> bytes:
@@ -130,12 +167,12 @@ def to_pcm16(a) -> bytes:
130
  return (arr * 32767.0).astype("<i2").tobytes()
131
 
132
 
133
- def pcm_stream(sentences):
134
  silence = np.zeros(int(GAP_SECONDS * SR), dtype="<i2").tobytes()
135
  with gen_lock: # ek samay me ek hi generation
136
  for s in sentences:
137
  try:
138
- for a in iter_audio(s):
139
  yield to_pcm16(a)
140
  yield silence
141
  except Exception:
@@ -143,9 +180,10 @@ def pcm_stream(sentences):
143
  traceback.print_exc()
144
 
145
 
 
146
  class TTSReq(BaseModel):
147
  text: str
148
- voice: Optional[str] = None # HTML bhejta he, par is model me ignore hota he
149
  speed: Optional[float] = None
150
 
151
 
@@ -159,6 +197,15 @@ def health():
159
  return {"state": state_msg, "error": last_error}
160
 
161
 
 
 
 
 
 
 
 
 
 
162
  @app.post("/tts")
163
  def tts(req: TTSReq, request: Request):
164
  if not authorized(request):
@@ -172,8 +219,15 @@ def tts(req: TTSReq, request: Request):
172
  sentences = split_clean(req.text)
173
  if not sentences:
174
  return JSONResponse({"error": "text me Hindi (देवनागरी) shabd nahi mile"}, status_code=400)
 
 
 
 
 
 
 
175
  return StreamingResponse(
176
- pcm_stream(sentences),
177
  media_type="application/octet-stream",
178
  headers={"X-Sample-Rate": str(SR), "Cache-Control": "no-store"},
179
  )
 
11
  torch.set_num_threads(int(os.environ.get("TORCH_THREADS", "2")))
12
 
13
  HERE = os.path.dirname(os.path.abspath(__file__))
14
+ VOICES_DIR = os.path.join(HERE, "voices")
15
+ AUDIO_EXTS = (".wav", ".mp3", ".flac", ".ogg")
16
  SECRET = os.environ.get("TTS_SECRET_KEY", "") # khali he to koi key nahi lagegi
17
+ DEFAULT_VOICE = os.environ.get("DEFAULT_VOICE", "") # chahe to default awaaz ka naam yaha daalo
18
  MODEL_REPO = "saryps-labs/pocket-tts-hindi"
19
  LOCAL_DIR = "/home/user/hindi_model"
 
20
  FALLBACK_VOICE = "hf://kyutai/tts-voices/alba-mackenna/casual.wav"
21
  MAX_CHARS = int(os.environ.get("MAX_CHARS", "6000"))
22
  GAP_SECONDS = 0.22
23
 
24
  model = None
 
25
  SR = 24000
26
  state_msg = "loading"
27
  last_error = ""
28
  gen_lock = threading.Lock()
29
+ voice_cache = {}
30
 
31
 
32
+ # ---------------- voices ----------------
33
+ def discover_voices() -> dict:
34
+ """voices/ folder aur root me padi saari audio files -> {naam: path}"""
35
+ found = {}
36
+ for folder in (VOICES_DIR, HERE):
37
+ if not os.path.isdir(folder):
38
+ continue
39
+ for fn in sorted(os.listdir(folder)):
40
+ if fn.lower().endswith(AUDIO_EXTS):
41
+ base = os.path.splitext(fn)[0]
42
+ name, i = base, 2
43
+ while name in found:
44
+ name = f"{base}_{i}"
45
+ i += 1
46
+ found[name] = os.path.join(folder, fn)
47
+ return found
48
+
49
+
50
+ def resolve_voice(name: Optional[str]):
51
+ voices = discover_voices()
52
+ if not voices:
53
+ return "default", FALLBACK_VOICE
54
+ if not name or name not in voices:
55
+ name = DEFAULT_VOICE if DEFAULT_VOICE in voices else next(iter(voices))
56
+ return name, voices[name]
57
+
58
+
59
+ def get_voice_state(path: str):
60
+ key = path if path.startswith("hf://") else f"{path}:{os.path.getmtime(path)}"
61
+ if key not in voice_cache:
62
+ print("Voice process ho rahi he:", path)
63
+ voice_cache[key] = model.get_state_for_audio_prompt(path)
64
+ return voice_cache[key]
65
+
66
+
67
+ # ---------------- model ----------------
68
  def get_config_path() -> str:
69
  cfg = os.path.join(LOCAL_DIR, "config.yaml")
70
  if not os.path.exists(cfg):
 
81
 
82
 
83
  def load_everything():
84
+ global model, SR, state_msg, last_error
85
  try:
86
  from pocket_tts import TTSModel
87
  cfg = get_config_path()
88
  print("Config:", cfg)
89
  m, err = None, None
90
+ for arg in (Path(cfg), cfg):
91
  try:
92
  m = try_load(TTSModel, arg)
93
  break
 
96
  traceback.print_exc()
97
  if m is None:
98
  raise err
 
 
 
 
99
  model = m
100
+ SR = int(getattr(m, "sample_rate", 24000))
101
+ _, path = resolve_voice(DEFAULT_VOICE)
102
+ get_voice_state(path) # default awaaz pehle se taiyar
103
  state_msg = "ready"
104
  print("Pocket TTS Hindi ready, sample rate", SR)
105
  except Exception as e:
 
122
 
123
  def authorized(request: Request) -> bool:
124
  if not SECRET:
125
+ return True
126
  cands = [
127
  request.headers.get("x-secret-key", ""),
128
  request.query_params.get("key", ""),
 
133
  return any(hmac.compare_digest(c.encode(), SECRET.encode()) for c in cands if c)
134
 
135
 
136
+ # ---------------- text / audio ----------------
137
  def split_clean(text: str):
 
138
  text = text.replace("\r", "\n")
139
  parts = re.split(r"[\u0964\u0965\.\?\!\n;:]+", text)
140
  out = []
 
153
  return out
154
 
155
 
156
+ def iter_audio(text: str, vstate):
157
  if hasattr(model, "generate_audio_stream"):
158
+ for a in model.generate_audio_stream(vstate, text):
159
  yield a
160
  else:
161
+ yield model.generate_audio(vstate, text)
162
 
163
 
164
  def to_pcm16(a) -> bytes:
 
167
  return (arr * 32767.0).astype("<i2").tobytes()
168
 
169
 
170
+ def pcm_stream(sentences, vstate):
171
  silence = np.zeros(int(GAP_SECONDS * SR), dtype="<i2").tobytes()
172
  with gen_lock: # ek samay me ek hi generation
173
  for s in sentences:
174
  try:
175
+ for a in iter_audio(s, vstate):
176
  yield to_pcm16(a)
177
  yield silence
178
  except Exception:
 
180
  traceback.print_exc()
181
 
182
 
183
+ # ---------------- API ----------------
184
  class TTSReq(BaseModel):
185
  text: str
186
+ voice: Optional[str] = None
187
  speed: Optional[float] = None
188
 
189
 
 
197
  return {"state": state_msg, "error": last_error}
198
 
199
 
200
+ @app.get("/voices")
201
+ def voices(request: Request):
202
+ if not authorized(request):
203
+ return JSONResponse({"error": "unauthorized"}, status_code=401)
204
+ names = list(discover_voices().keys()) or ["default"]
205
+ default, _ = resolve_voice(DEFAULT_VOICE)
206
+ return {"voices": names, "default": default}
207
+
208
+
209
  @app.post("/tts")
210
  def tts(req: TTSReq, request: Request):
211
  if not authorized(request):
 
219
  sentences = split_clean(req.text)
220
  if not sentences:
221
  return JSONResponse({"error": "text me Hindi (देवनागरी) shabd nahi mile"}, status_code=400)
222
+ name, path = resolve_voice(req.voice)
223
+ try:
224
+ with gen_lock: # nayi awaaz pehli baar process hogi (kuch second lagenge)
225
+ vstate = get_voice_state(path)
226
+ except Exception as e:
227
+ traceback.print_exc()
228
+ return JSONResponse({"error": f"awaaz '{name}' load nahi hui: {e}"}, status_code=500)
229
  return StreamingResponse(
230
+ pcm_stream(sentences, vstate),
231
  media_type="application/octet-stream",
232
  headers={"X-Sample-Rate": str(SR), "Cache-Control": "no-store"},
233
  )