TTS / app.py
Prof-Hunter's picture
Upload app.py
35f1d7f verified
Raw
History Blame Contribute Delete
4.06 kB
import gradio as gr
import edge_tts
import asyncio
import tempfile
from piper_engine import PiperEngine
# -----------------------------
# Load Piper Once
# -----------------------------
piper_engine = PiperEngine(
"models/en_US-amy-medium.onnx"
)
# -----------------------------
# Voice Loader (Edge)
# -----------------------------
async def get_voices():
voices = await edge_tts.list_voices()
return {
f"{v['ShortName']} - {v['Locale']} ({v['Gender']})": v["ShortName"]
for v in voices
}
# -----------------------------
# Edge TTS (Neutral)
# -----------------------------
async def edge_tts_engine(text, voice, rate, pitch):
if not text.strip():
return None, "Please enter text."
if not voice:
return None, "Please select a voice."
voice_short = voice.split(" - ")[0]
rate_str = f"{rate:+d}%"
pitch_str = f"{pitch:+d}Hz"
communicate = edge_tts.Communicate(
text,
voice_short,
rate=rate_str,
pitch=pitch_str,
)
with tempfile.NamedTemporaryFile(
delete=False,
suffix=".mp3",
) as tmp:
path = tmp.name
await communicate.save(path)
return path, None
# -----------------------------
# Piper TTS (Expressive)
# -----------------------------
def piper_engine_wrapper(text, rate, pitch):
if not text.strip():
return None, "Please enter text."
speed = 1.0 + rate / 100 # rate from slider
pitch_scale = 1.0 + pitch / 50
audio = piper_engine.synthesize(text, speed, pitch_scale)
return audio, None
# -----------------------------
# Unified Interface
# -----------------------------
async def unified_tts(
text,
voice,
rate,
pitch,
engine,
):
if engine == "Edge (Neutral)":
return await edge_tts_engine(
text,
voice,
rate,
pitch,
)
else:
return piper_engine_wrapper(
text,
rate,
pitch,
)
# -----------------------------
# UI Builder
# -----------------------------
async def create_demo():
voices = await get_voices()
with gr.Blocks(analytics_enabled=False) as demo:
gr.Markdown("""
# 🎙️ AI Text-to-Speech Lab
Compare traditional and expressive AI voices.
""")
# Text
text_input = gr.Textbox(
label="Text",
lines=6,
placeholder="Paste lecture notes, narration, or scripts here...",
)
# Engine Selector
engine_radio = gr.Radio(
["Edge (Neutral)", "Piper (Expressive)"],
value="Edge (Neutral)",
label="Generation",
)
# Voice (Edge Only)
voice_dropdown = gr.Dropdown(
choices=[""] + list(voices.keys()),
label="Edge Voice",
value="",
)
with gr.Row():
rate_slider = gr.Slider(
-50, 50, 0,
step=1,
label="Emotion / Speed",
)
pitch_slider = gr.Slider(
-20, 20, 0,
step=1,
label="Intonation / Pitch",
)
generate_btn = gr.Button(
"Generate Audio",
variant="primary",
)
audio_output = gr.Audio(
label="Output",
)
warning_md = gr.Markdown()
generate_btn.click(
fn=unified_tts,
inputs=[
text_input,
voice_dropdown,
rate_slider,
pitch_slider,
engine_radio,
],
outputs=[
audio_output,
warning_md,
],
)
return demo
# -----------------------------
# Runner
# -----------------------------
async def main():
demo = await create_demo()
demo.queue(default_concurrency_limit=20)
demo.launch()
if __name__ == "__main__":
asyncio.run(main())