Spaces:
Sleeping
Sleeping
| 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()) | |