urdu-s2s-mvp / app.py
sufinity's picture
Deploy Urdu S2S MVP
6f6b5d7 verified
Raw
History Blame Contribute Delete
8.99 kB
from __future__ import annotations
import os
from pathlib import Path
import sys
from uuid import uuid4
import gradio as gr
try:
import spaces
except ModuleNotFoundError: # Local and paid-GPU runtimes do not require the package.
spaces = None
ROOT = Path(__file__).resolve().parent
for relative in ("vendor/chatterbox_tts_0_1_7", "src", "scripts"):
path = ROOT / relative
if str(path) not in sys.path:
sys.path.insert(0, str(path))
from urdu_s2s.api import DEFAULT_PRAXY_ANCHOR, result_to_api_payload, run_live_tts
from urdu_s2s.live_providers import (
OpenAIBridgeProvider,
OpenAICompatibleChatClient,
neutralize_devanagari_user_address,
normalize_devanagari_tts_pronunciation,
)
from urdu_s2s.schemas import BridgeResult, ReplyResult, SpeechToSpeechRequest
from urdu_s2s.tts_providers import ChatterboxPraxyTTSProvider
OUTPUT_DIR = ROOT / "artifacts/gradio_tts_outputs"
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
def env(name: str, default: str) -> str:
return os.environ.get(name, default)
def gpu_runtime(func):
if spaces is None:
return func
return spaces.GPU(duration=int(env("S2S_ZEROGPU_DURATION", "120")))(func)
def is_devanagari(text: str) -> bool:
return any("\u0900" <= char <= "\u097f" for char in text)
def voice_prompt_audio_path() -> Path:
return Path(env("S2S_VOICE_PROMPT_AUDIO_PATH", str(DEFAULT_PRAXY_ANCHOR)))
def chatterbox_device() -> str:
return env("S2S_CHATTERBOX_DEVICE", "cuda")
def chatterbox_t3_model() -> str:
return env("S2S_CHATTERBOX_T3_MODEL", "v3")
@gpu_runtime
def synthesize_s2s(mic_audio_path: str | None, upload_audio_path: str | None, prompt_roman_urdu: str):
audio_path = mic_audio_path or upload_audio_path
if not audio_path:
raise gr.Error("Please stop the recorder or upload an audio file first.")
request_id = f"hf_{uuid4().hex}"
input_audio = Path(audio_path)
common = {
"audio_path": input_audio,
"request_id": request_id,
"prompt_roman_urdu": prompt_roman_urdu or "",
"whisper_model": env("S2S_WHISPER_MODEL", "small"),
"whisper_language": env("S2S_WHISPER_LANGUAGE", "ur"),
"whisper_device": env("S2S_WHISPER_DEVICE", "cpu"),
"whisper_compute_type": env("S2S_WHISPER_COMPUTE_TYPE", "int8"),
}
result = run_live_tts(
**common,
output_audio_path=OUTPUT_DIR / f"{request_id}.wav",
voice_prompt_audio_path=voice_prompt_audio_path(),
chatterbox_device=chatterbox_device(),
chatterbox_t3_model=chatterbox_t3_model(),
)
payload = result_to_api_payload(result, repo_root=ROOT)
output_audio = payload["tts_audio_path"] if payload["tts_audio_exists"] else None
return (
output_audio,
payload["asr_transcript"],
payload["assistant_reply_urdu"],
)
@gpu_runtime
def synthesize_tts(input_text: str):
text = (input_text or "").strip()
if not text:
raise gr.Error("Please enter the text you want spoken.")
request_id = f"hf_tts_{uuid4().hex}"
request = SpeechToSpeechRequest(
request_id=request_id,
audio_path=Path(""),
metadata={"input_mode": "text"},
)
reply = ReplyResult(text_urdu=text, provider="user_text", model="typed_input")
if is_devanagari(text):
speech_text = neutralize_devanagari_user_address(
normalize_devanagari_tts_pronunciation(text)
)
bridge = BridgeResult(
text_devanagari=speech_text,
provider="direct_devanagari_text",
model="user_input",
)
else:
bridge = OpenAIBridgeProvider(chat_client=OpenAICompatibleChatClient()).convert(
request,
reply,
)
tts_result = ChatterboxPraxyTTSProvider(
output_audio_path=OUTPUT_DIR / f"{request_id}.wav",
voice_prompt_audio_path=voice_prompt_audio_path(),
device=chatterbox_device(),
t3_model=chatterbox_t3_model(),
).synthesize(request, reply, bridge)
output_audio = str(tts_result.audio_path) if tts_result.audio_path.exists() else None
return output_audio
with gr.Blocks(title="Urdu S2S") as demo:
gr.Markdown("# Urdu S2S")
gr.Markdown(
"""
A Pakistani Urdu speech model demo for conversational voice responses. It supports
both speech-to-speech and text-to-speech testing with the same Urdu voice style.
"""
)
with gr.Accordion("About this model", open=False):
gr.Markdown(
"""
**What it is**
Urdu S2S is an experimental Urdu voice assistant model focused on natural
Pakistani Urdu pronunciation, short spoken replies, and a warm female assistant
voice. It is designed for interactive product testing rather than long-form
narration.
**Architecture**
Urdu S2S uses a modular speech pipeline with dedicated components for speech
recognition, Urdu response generation, pronunciation-aware speech preparation,
and neural speech synthesis. The user-facing experience is a single
speech-to-speech model demo, while the underlying design lets each part improve
independently as stronger Urdu speech models become available.
At a high level, incoming audio is normalized and transcribed, the dialogue
layer produces a concise Urdu assistant response, a pronunciation layer prepares
the response for stable speech generation, and the synthesis layer renders the
final spoken voice.
**What it can do**
- Record or upload Urdu speech and receive a spoken Urdu response.
- Generate speech directly from typed Urdu, Roman Urdu, or Devanagari text.
- Preserve common names, numbers, dates, and English terms where possible.
- Use Urdu-first phrasing such as “baraye meherbani” instead of overly Hindi wording.
- Avoid assuming the user's gender in normal assistant replies.
**Best test inputs**
Use short, natural prompts of 2 to 10 seconds. The current demo works best for
assistant-style requests such as greetings, clarifying questions, simple customer
support, scheduling drafts, travel questions, and account/helpdesk style prompts.
**Current limits**
This is an MVP. Very noisy audio, long recordings, mixed languages, unusual names,
and dense factual questions may still produce transcription or pronunciation errors.
The first generation after the Space wakes up can be slower while the speech model
loads.
"""
)
with gr.Tabs():
with gr.Tab("Speech to speech"):
with gr.Row():
with gr.Column():
mic_audio = gr.Audio(
label="Record speech",
sources=["microphone"],
type="filepath",
)
upload_audio = gr.Audio(
label="Upload speech",
sources=["upload"],
type="filepath",
)
prompt = gr.Textbox(
label="Optional Roman Urdu hint",
placeholder="Example: reply like a polite Pakistani Urdu assistant",
)
run_s2s = gr.Button("Generate speech response", variant="primary")
with gr.Column():
output_audio = gr.Audio(label="Response audio", type="filepath")
transcript = gr.Textbox(label="Transcript", rtl=True)
reply = gr.Textbox(label="Reply", rtl=True)
run_s2s.click(
fn=synthesize_s2s,
inputs=[mic_audio, upload_audio, prompt],
outputs=[output_audio, transcript, reply],
api_name="s2s",
concurrency_limit=1,
)
with gr.Tab("Text to speech"):
with gr.Row():
with gr.Column():
text_input = gr.Textbox(
label="Text",
lines=5,
placeholder="Type Urdu, Roman Urdu, or Devanagari text to speak.",
)
run_tts = gr.Button("Generate speech", variant="primary")
with gr.Column():
tts_output_audio = gr.Audio(label="Speech audio", type="filepath")
run_tts.click(
fn=synthesize_tts,
inputs=[text_input],
outputs=[tts_output_audio],
api_name="tts",
concurrency_limit=1,
)
if __name__ == "__main__":
demo.queue(max_size=8).launch(server_name="0.0.0.0", server_port=7860)