muralipala1504 commited on
Commit
b10a301
·
1 Parent(s): d61ee9c

feat: Piper Hindi TTS + backend translate/tts endpoints, drop direct LibreTranslate from frontend

Browse files
Files changed (2) hide show
  1. app.js +15 -13
  2. deepshell-backend/deepshell/__main__.py +82 -0
app.js CHANGED
@@ -95,7 +95,7 @@ window.speakText = function(text) {
95
  .replace(/🎯|💻|🔍|⚠️|🚀|💡/g, "")
96
  .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
97
  .trim();
98
- // Translate sentence by sentence to reduce lag
99
  if (window.ttsLang !== 'en-US' && window.ttsLang !== 'en-GB') {
100
  const langCode = window.ttsLang.split('-')[0];
101
  const sentences = clean.match(/[^.!?]+[.!?]+/g) || [clean];
@@ -103,24 +103,26 @@ window.speakText = function(text) {
103
  function translateAndSpeakNext() {
104
  if (i >= sentences.length || !window.ttsEnabled) return;
105
  const sentence = sentences[i].trim();
106
- fetch('http://192.168.217.160:5000/translate', {
107
  method: 'POST',
108
  headers: {'Content-Type': 'application/json'},
109
- body: JSON.stringify({q: sentence, source: 'en', target: langCode})
110
  })
111
  .then(r => r.json())
112
  .then(data => {
113
  const translated = data.translatedText || sentence;
114
- const utt = new SpeechSynthesisUtterance(translated);
115
- utt.lang = window.ttsLang;
116
- const voices = window.speechSynthesis.getVoices();
117
- const match = voices.find(v => v.lang === window.ttsLang);
118
- if (match) utt.voice = match;
119
- utt.rate = 0.95;
120
- utt.pitch = 1.0;
121
- utt.volume = 1.0;
122
- utt.onend = () => { i++; translateAndSpeakNext(); };
123
- window.speechSynthesis.speak(utt);
 
 
124
  })
125
  .catch(() => {
126
  const utt = new SpeechSynthesisUtterance(sentence);
 
95
  .replace(/🎯|💻|🔍|⚠️|🚀|💡/g, "")
96
  .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
97
  .trim();
98
+ // Non-English: translate via backend then speak via Piper TTS
99
  if (window.ttsLang !== 'en-US' && window.ttsLang !== 'en-GB') {
100
  const langCode = window.ttsLang.split('-')[0];
101
  const sentences = clean.match(/[^.!?]+[.!?]+/g) || [clean];
 
103
  function translateAndSpeakNext() {
104
  if (i >= sentences.length || !window.ttsEnabled) return;
105
  const sentence = sentences[i].trim();
106
+ fetch('/translate', {
107
  method: 'POST',
108
  headers: {'Content-Type': 'application/json'},
109
+ body: JSON.stringify({text: sentence, target: langCode})
110
  })
111
  .then(r => r.json())
112
  .then(data => {
113
  const translated = data.translatedText || sentence;
114
+ return fetch('/tts', {
115
+ method: 'POST',
116
+ headers: {'Content-Type': 'application/json'},
117
+ body: JSON.stringify({text: translated, lang: langCode})
118
+ });
119
+ })
120
+ .then(r => r.blob())
121
+ .then(blob => {
122
+ const url = URL.createObjectURL(blob);
123
+ const audio = new Audio(url);
124
+ audio.onended = () => { URL.revokeObjectURL(url); i++; translateAndSpeakNext(); };
125
+ audio.play();
126
  })
127
  .catch(() => {
128
  const utt = new SpeechSynthesisUtterance(sentence);
deepshell-backend/deepshell/__main__.py CHANGED
@@ -371,6 +371,88 @@ async def run_agent_stream(req: ChatRequest, request: Request):
371
  }
372
  )
373
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  # ============================================
375
  # COMMAND EXECUTION - DISABLED FOR FREE VERSION
376
  # ============================================
 
371
  }
372
  )
373
 
374
+ # ============================================
375
+ # Translation + TTS Endpoints
376
+ # ============================================
377
+
378
+ import subprocess
379
+ import httpx
380
+
381
+ class TranslateRequest(BaseModel):
382
+ text: str
383
+ target: str = "hi"
384
+
385
+ class TTSRequest(BaseModel):
386
+ text: str
387
+ lang: str = "hi"
388
+
389
+ LIBRETRANSLATE_URL = os.getenv("LIBRETRANSLATE_URL", "http://localhost:5000/translate")
390
+ PIPER_BINARY = os.getenv("PIPER_BINARY", str(Path.home() / "piper/piper"))
391
+ PIPER_VOICE_DIR = os.getenv("PIPER_VOICE_DIR", str(Path.home() / "piper/voices"))
392
+
393
+ VOICE_MAP = {
394
+ "hi": "hi_IN-rohan-medium.onnx",
395
+ "ta": "ta_IN-cmu_indic-medium.onnx",
396
+ "te": "te_IN-cmu_indic-medium.onnx",
397
+ "ar": "ar_JO-kareem-medium.onnx",
398
+ }
399
+
400
+ @app.post("/translate")
401
+ async def translate_text(req: TranslateRequest):
402
+ """Translate text via LibreTranslate."""
403
+ try:
404
+ async with httpx.AsyncClient(timeout=10.0) as client:
405
+ resp = await client.post(
406
+ LIBRETRANSLATE_URL,
407
+ json={"q": req.text, "source": "en", "target": req.target}
408
+ )
409
+ data = resp.json()
410
+ return {"translatedText": data.get("translatedText", req.text)}
411
+ except Exception as e:
412
+ return {"translatedText": req.text, "error": str(e)}
413
+
414
+ @app.post("/tts")
415
+ async def text_to_speech(req: TTSRequest):
416
+ """Convert text to speech via Piper, return WAV audio."""
417
+ voice_file = VOICE_MAP.get(req.lang)
418
+ if not voice_file:
419
+ return JSONResponse({"error": f"No voice for lang: {req.lang}"}, status_code=400)
420
+
421
+ model_path = os.path.join(PIPER_VOICE_DIR, voice_file)
422
+ if not os.path.exists(model_path):
423
+ return JSONResponse({"error": f"Voice model not found: {model_path}"}, status_code=404)
424
+
425
+ if not os.path.exists(PIPER_BINARY):
426
+ return JSONResponse({"error": f"Piper binary not found: {PIPER_BINARY}"}, status_code=404)
427
+
428
+ try:
429
+ import tempfile
430
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp:
431
+ tmp_path = tmp.name
432
+
433
+ proc = await asyncio.create_subprocess_exec(
434
+ PIPER_BINARY,
435
+ "--model", model_path,
436
+ "--output_file", tmp_path,
437
+ stdin=asyncio.subprocess.PIPE,
438
+ stdout=asyncio.subprocess.DEVNULL,
439
+ stderr=asyncio.subprocess.DEVNULL,
440
+ env={**os.environ, "LD_LIBRARY_PATH": str(Path(PIPER_BINARY).parent)}
441
+ )
442
+ await asyncio.wait_for(proc.communicate(input=req.text.encode()), timeout=30)
443
+ with open(tmp_path, "rb") as f:
444
+ wav_data = f.read()
445
+ os.unlink(tmp_path)
446
+ return StreamingResponse(
447
+ iter([wav_data]),
448
+ media_type="audio/wav",
449
+ headers={"Content-Disposition": "inline; filename=tts.wav"}
450
+ )
451
+ except asyncio.TimeoutError:
452
+ return JSONResponse({"error": "TTS generation timed out"}, status_code=504)
453
+ except Exception as e:
454
+ return JSONResponse({"error": str(e)}, status_code=500)
455
+
456
  # ============================================
457
  # COMMAND EXECUTION - DISABLED FOR FREE VERSION
458
  # ============================================