demo1 / app.py
FatimaWaeli
Add application file
f14068e
import os
from TTS.utils.synthesizer import Synthesizer
import gradio as gr
from huggingface_hub import hf_hub_download
from huggingface_hub import login
import time
# Uncomment for private models if needed
# login(token=os.environ.get("HF_TOKEN"))
# Custom CSS for better styling - بهینه‌سازی شده با طراحی حرفه‌ای‌تر دسکتاپ
custom_css = """
/* فایل CSS مدرن و حرفه‌ای برای سیستم تبدیل متن فارسی به گفتار - نسخه دسکتاپ 3.0 */
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
:root {
/* پالت رنگی جدید - ترکیب آبی و بنفش حرفه‌ای */
--primary-color: #4f46e5;
--primary-light: #818cf8;
--primary-dark: #3730a3;
--primary-gradient: linear-gradient(135deg, #4f46e5, #6366f1);
--primary-glass: rgba(79, 70, 229, 0.1);
/* رنگ‌های ثانویه - طیف سبز آبی برای تضاد */
--secondary-color: #0ea5e9;
--secondary-light: #7dd3fc;
--secondary-dark: #0369a1;
--secondary-gradient: linear-gradient(135deg, #0ea5e9, #38bdf8);
/* رنگ‌های خنثی - طیف خاکستری سرد برای رابط دسکتاپ */
--background-color: #f8fafc;
--surface-color: #ffffff;
--text-color: #334155;
--text-light: #64748b;
--text-dark: #1e293b;
--heading-color: #0f172a;
--border-color: #e2e8f0;
--divider-color: #f1f5f9;
/* رنگ‌های وضعیت بهبود یافته */
--success-color: #10b981;
--success-light: #d1fae5;
--warning-color: #f59e0b;
--warning-light: #fef3c7;
--error-color: #ef4444;
--error-light: #fee2e2;
--info-color: #3b82f6;
--info-light: #dbeafe;
/* سایه‌های بهبود یافته برای حالت دسکتاپ */
--shadow-xs: 0 1px 2px rgba(15, 23, 42, 0.05);
--shadow-sm: 0 2px 4px rgba(15, 23, 42, 0.07);
--shadow-md: 0 4px 8px rgba(15, 23, 42, 0.08);
--shadow-lg: 0 12px 24px rgba(15, 23, 42, 0.09);
--shadow-xl: 0 20px 40px rgba(15, 23, 42, 0.1);
--shadow-focus: 0 0 0 3px rgba(79, 70, 229, 0.3);
/* انیمیشن‌ها */
--transition-fast: all 0.2s ease;
--transition: all 0.3s ease;
--transition-slow: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
/* اندازه‌های گوشه‌ها */
--border-radius-sm: 8px;
--border-radius: 12px;
--border-radius-lg: 16px;
--border-radius-xl: 24px;
--border-radius-full: 9999px;
/* اندازه فاصله‌ها */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
--spacing-2xl: 3rem;
}
/* تنظیمات پایه */
body {
font-family: 'Vazirmatn', system-ui, -apple-system, sans-serif;
background-color: var(--background-color);
margin: 0;
padding: 0;
color: var(--text-color);
line-height: 1.6;
}
/* کانتینر اصلی - طراحی دسکتاپ */
.gradio-container {
background: linear-gradient(135deg, #f0f9ff 0%, #e6f2ff 100%);
font-family: 'Vazirmatn', system-ui, sans-serif !important;
color: var(--text-color);
max-width: 1400px !important;
margin: 0 auto !important;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* سربرگ اصلی - سبک دسکتاپ */
.main-header {
color: var(--heading-color);
text-align: center;
margin: 2rem 0;
font-size: 2.8rem;
font-weight: 800;
line-height: 1.2;
letter-spacing: -0.025em;
text-shadow: 0px 2px 4px rgba(0,0,0,0.1);
position: relative;
font-family: 'Vazirmatn', system-ui, sans-serif !important;
}
.main-header:after {
content: "";
display: block;
width: 140px;
height: 5px;
background: var(--primary-gradient);
margin: 1rem auto;
border-radius: var(--border-radius-full);
}
/* لوگو و آیکون‌ها */
.logo-area {
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
margin-bottom: 1.5rem;
}
.app-logo {
font-size: 2rem;
background: var(--primary-gradient);
-webkit-background-clip: text;
color: transparent;
font-weight: 700;
}
/* کانتینر اصلی محتوا - طراحی دسکتاپ */
.app-container {
display: flex;
flex-direction: column;
max-width: 1200px;
margin: 0 auto 2.5rem;
padding: 0;
flex: 1;
}
.desktop-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
align-items: start;
}
/* پنل اصلی */
.main-panel {
background-color: rgba(255, 255, 255, 0.95);
border-radius: var(--border-radius-lg);
box-shadow: var(--shadow-xl);
backdrop-filter: blur(15px);
border: 1px solid rgba(255, 255, 255, 0.7);
padding: 2.5rem;
position: relative;
overflow: hidden;
}
/* افکت تزئینی پس‌زمینه */
.main-panel:before {
content: "";
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, var(--primary-glass) 0%, transparent 70%);
opacity: 0.5;
z-index: 0;
pointer-events: none;
}
/* نوار عنوان پنل‌ها */
.panel-title {
background: var(--primary-gradient);
color: white;
padding: 1rem 1.5rem;
border-radius: var(--border-radius) var(--border-radius) 0 0;
font-weight: 600;
font-size: 1.1rem;
margin: -2.5rem -2.5rem 1.5rem -2.5rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.panel-title-icon {
font-size: 1.2rem;
margin-left: 0.5rem;
}
/* فوتر */
.footer {
text-align: center;
margin-top: 3rem;
color: var(--text-light);
font-size: 0.95rem;
line-height: 1.6;
padding: 0.75rem;
position: relative;
z-index: 1;
background-color: rgba(255, 255, 255, 0.5);
backdrop-filter: blur(10px);
border-top: 1px solid var(--border-color);
}
/* تنظیمات متن فارسی */
textarea, .label, #text-input {
text-align: right !important;
direction: rtl !important;
font-family: 'Vazirmatn', system-ui, sans-serif !important;
}
.label {
font-size: 1.1rem !important;
font-weight: 600 !important;
color: var(--heading-color) !important;
margin-bottom: 0.75rem !important;
display: block;
font-family: 'Vazirmatn', system-ui, sans-serif !important;
}
/* استایل دکمه‌ها */
button.primary {
background: var(--primary-gradient) !important;
border: none !important;
border-radius: var(--border-radius) !important;
color: white !important;
font-weight: 600 !important;
font-size: 1.05rem !important;
padding: 0.8rem 1.75rem !important;
transition: var(--transition) !important;
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3) !important;
font-family: 'Vazirmatn', system-ui, sans-serif !important;
position: relative;
overflow: hidden;
}
button.primary:hover {
transform: translateY(-2px) !important;
box-shadow: 0 8px 16px rgba(79, 70, 229, 0.4) !important;
background: linear-gradient(45deg, #3730a3, #4f46e5) !important;
}
button.primary:active {
transform: translateY(0) !important;
box-shadow: 0 2px 8px rgba(79, 70, 229, 0.4) !important;
}
/* افکت موج دکمه */
button.primary:after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 5px;
height: 5px;
background: rgba(255, 255, 255, 0.7);
opacity: 0;
border-radius: 100%;
transform: scale(1, 1) translate(-50%);
transform-origin: 50% 50%;
}
button.primary:focus:not(:active)::after {
animation: ripple 1s ease-out;
}
@keyframes ripple {
0% {
transform: scale(0, 0);
opacity: 0.5;
}
20% {
transform: scale(25, 25);
opacity: 0.3;
}
100% {
opacity: 0;
transform: scale(40, 40);
}
}
/* پنل‌های ورودی و خروجی */
.input-panel, .output-panel {
background-color: var(--surface-color);
border-radius: var(--border-radius);
padding: 1.5rem;
margin-bottom: 1.5rem;
border: 1px solid var(--border-color);
transition: var(--transition);
box-shadow: var(--shadow-sm);
position: relative;
z-index: 2;
}
.input-panel:hover, .output-panel:hover {
box-shadow: var(--shadow-md);
border-color: var(--primary-light);
}
/* کادر متن */
.input-text textarea {
border: 2px solid var(--border-color) !important;
border-radius: var(--border-radius) !important;
padding: 1.25rem !important;
transition: var(--transition) !important;
font-size: 1.05rem !important;
background-color: rgba(255, 255, 255, 0.8) !important;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.03) !important;
font-family: 'Vazirmatn', system-ui, sans-serif !important;
color: var(--text-color) !important;
line-height: 1.8 !important;
}
.input-text textarea:focus {
border-color: var(--primary-color) !important;
box-shadow: var(--shadow-focus), inset 0 2px 4px rgba(0, 0, 0, 0.03) !important;
background-color: rgba(255, 255, 255, 1) !important;
outline: none !important;
}
/* رنگ متن در کانتینر */
.gradio-container textarea {
color: var(--text-color) !important;
}
/* اسلایدر سرعت */
.speed-slider-container {
background: linear-gradient(to right, rgba(255,255,255,0.9), rgba(240,246,255,0.9));
border-radius: var(--border-radius);
padding: 1.25rem;
margin-top: 1.5rem;
border: 1px solid var(--border-color);
box-shadow: var(--shadow-sm);
}
.speed-slider input[type="range"] {
height: 8px !important;
border-radius: var(--border-radius-full) !important;
background: linear-gradient(90deg, var(--primary-light), var(--secondary-light)) !important;
}
.speed-slider input[type="range"]::-webkit-slider-thumb {
background: var(--primary-gradient) !important;
border: 3px solid white !important;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15) !important;
height: 20px !important;
width: 20px !important;
border-radius: 50% !important;
}
.speed-slider .label {
font-size: 1rem !important;
color: var(--text-color) !important;
font-weight: 500 !important;
}
/* پنل مثال‌ها */
.examples-panel {
background-color: rgba(248, 250, 252, 0.8);
border-radius: var(--border-radius);
padding: 1.5rem;
border: 1px solid var(--border-color);
margin-top: 2rem;
position: relative;
z-index: 2;
}
/* پنل وضعیت با کلاس‌های مختلف */
.status-panel {
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
border-radius: var(--border-radius);
padding: 1rem;
margin: 0 0 1.5rem 0;
text-align: center;
font-weight: 500;
border-right: 4px solid var(--primary-color);
transition: var(--transition);
font-family: 'Vazirmatn', system-ui, sans-serif !important;
position: relative;
overflow: hidden;
box-shadow: var(--shadow-sm);
}
.status-success {
color: var(--success-color) !important;
background: linear-gradient(135deg, #f0fdf4, #dcfce7) !important;
border-right-color: var(--success-color) !important;
}
.status-error {
color: var(--error-color) !important;
background: linear-gradient(135deg, #fef2f2, #fee2e2) !important;
border-right-color: var(--error-color) !important;
}
.status-processing {
color: var(--info-color) !important;
background: linear-gradient(135deg, #eff6ff, #dbeafe) !important;
border-right-color: var(--info-color) !important;
background-size: 200% 200% !important;
animation: wave 2s ease infinite !important;
}
/* استایل پخش‌کننده صوتی */
audio {
border-radius: var(--border-radius) !important;
background: linear-gradient(to right, #dbeafe, #e0e7ff) !important;
box-shadow: var(--shadow-sm) !important;
width: 100% !important;
transition: var(--transition) !important;
}
audio:hover {
box-shadow: var(--shadow-md) !important;
}
/* استایل دکمه‌های مثال */
.examples-panel button {
border: 1px solid #e2e8f0 !important;
background: rgba(255, 255, 255, 0.8) !important;
border-radius: var(--border-radius-sm) !important;
transition: var(--transition-fast) !important;
font-family: 'Vazirmatn', system-ui, sans-serif !important;
color: var(--text-color) !important;
}
.examples-panel button:hover {
background: white !important;
border-color: var(--primary-light) !important;
transform: translateY(-2px) !important;
box-shadow: var(--shadow-sm) !important;
color: var(--primary-dark) !important;
}
/* طرح دکمه بزرگ */
.big-button {
font-size: 1.1rem !important;
padding: 1rem 2rem !important;
width: 100% !important;
margin-top: 1rem !important;
text-align: center !important;
}
/* نوار پیشرفت */
.progress-bar {
height: 8px;
background-color: var(--border-color);
border-radius: var(--border-radius-full);
margin: 0.5rem 0;
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
background: var(--primary-gradient);
border-radius: var(--border-radius-full);
transition: width 0.3s ease;
width: 0%;
}
/* انیمیشن‌های جلوه‌ای */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(15px); }
to { opacity: 1; transform: translateY(0); }
}
.desktop-layout {
animation: fadeIn 0.7s ease-out;
}
/* افکت موج برای پردازش صوتی */
@keyframes wave {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* حالت شب و روز */
.theme-switch {
position: absolute;
top: 1rem;
right: 1rem;
background: var(--secondary-gradient);
color: white;
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: var(--shadow-md);
z-index: 10;
transition: var(--transition);
}
.theme-switch:hover {
transform: scale(1.1);
}
/* پشتیبانی RTL برای تمام عناصر UI */
.gradio-container [dir="rtl"],
.gradio-container [style*="direction: rtl"] {
font-family: 'Vazirmatn', system-ui, sans-serif !important;
}
/* اصلاح المان‌های مارک‌داون */
.prose p, .prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 {
direction: rtl !important;
text-align: right !important;
font-family: 'Vazirmatn', system-ui, sans-serif !important;
color: var(--text-color) !important;
}
/* استایل لینک‌ها */
a {
color: var(--primary-color) !important;
text-decoration: none !important;
transition: var(--transition-fast) !important;
font-weight: 500 !important;
}
a:hover {
color: var(--primary-dark) !important;
text-decoration: underline !important;
}
/* کارت‌های اطلاعات */
.info-card {
background: linear-gradient(135deg, rgba(255,255,255,0.9), rgba(240,246,255,0.9));
border-radius: var(--border-radius);
padding: 1.25rem;
margin-bottom: 1.5rem;
border: 1px solid var(--border-color);
transition: var(--transition);
position: relative;
z-index: 2;
}
.info-card:hover {
box-shadow: var(--shadow-md);
border-color: var(--primary-light);
}
/* واکنش‌گرایی برای نمایشگرهای مختلف */
@media (max-width: 1024px) {
.desktop-layout {
grid-template-columns: 1fr;
}
.main-panel {
padding: 2rem;
}
.panel-title {
margin: -2rem -2rem 1.5rem -2rem;
}
}
@media (max-width: 768px) {
.main-panel {
padding: 1.5rem;
}
.panel-title {
margin: -1.5rem -1.5rem 1.25rem -1.5rem;
padding: 0.75rem 1.25rem;
}
.main-header {
font-size: 2rem;
margin: 1.5rem 0;
}
.label {
font-size: 1rem !important;
}
.input-panel, .output-panel {
padding: 1.25rem;
}
}
/* بهینه‌سازی برای نمایشگرهای کوچک */
@media (max-width: 480px) {
.main-panel {
padding: 1.25rem;
border-radius: var(--border-radius);
}
.panel-title {
margin: -1.25rem -1.25rem 1rem -1.25rem;
padding: 0.75rem 1rem;
}
.main-header {
font-size: 1.5rem;
}
button.primary {
width: 100%;
padding: 0.7rem 1rem !important;
}
}
/* اصلاحات سبک برای گردیو 4 */
.gr-padded {
padding: var(--spacing-lg) !important;
}
.gr-gap {
gap: var(--spacing-md) !important;
}
.gradio-button {
border-radius: var(--border-radius) !important;
}
/* اصلاح فاصله‌ها */
.gr-panel > .gr-block {
margin-bottom: var(--spacing-md) !important;
}
.gr-form > .gr-block {
margin-bottom: var(--spacing-lg) !important;
}
/* بهبود المان‌های گردیو 4 */
.gr-box, .gr-block, .gr-input, .gr-textbox {
border-radius: var(--border-radius) !important;
}
.gr-form {
border: none !important;
background: transparent !important;
}
.gr-accordion {
border-radius: var(--border-radius) !important;
overflow: hidden !important;
}
.gr-accordion-title {
font-weight: 600 !important;
color: var(--heading-color) !important;
}
.gr-panel {
border-radius: var(--border-radius) !important;
}
/* استایل‌های خاص برای گردیو 4 */
.gr-input, .gr-textbox {
transition: var(--transition) !important;
}
.gr-input:focus, .gr-textbox:focus {
border-color: var(--primary-color) !important;
box-shadow: var(--shadow-focus) !important;
}
.gr-padded-container {
padding: var(--spacing-lg) !important;
}
.gr-compact {
margin: 0 !important;
}
.gr-hover-container:hover {
transform: translateY(-2px) !important;
box-shadow: var(--shadow-md) !important;
}
.gr-interface {
margin: 0 !important;
}
/* برخی از اصلاحات مهم برای Gradio 4 */
.gr-form {
border: none !important;
background: transparent !important;
}
.gr-form-input {
border-radius: var(--border-radius) !important;
transition: var(--transition) !important;
}
.gr-form-input:focus {
border-color: var(--primary-color) !important;
box-shadow: var(--shadow-focus) !important;
}
.gr-block {
margin-bottom: var(--spacing-md) !important;
}
.gr-input-label {
color: var(--heading-color) !important;
font-weight: 600 !important;
margin-bottom: var(--spacing-xs) !important;
}
.gr-interface {
margin: 0 !important;
}
/* اصلاحات اضافی */
svg.gr-arrow {
fill: var(--primary-color) !important;
}
.dark-mode {
--background-color: #0f172a;
--surface-color: #1e293b;
--text-color: #e2e8f0;
--text-light: #94a3b8;
--text-dark: #f8fafc;
--heading-color: #f1f5f9;
--border-color: #334155;
--divider-color: #1e293b;
}
"""
def load_synthesizer():
# Update status message
status = "در حال بارگذاری مدل... لطفاً منتظر بمانید"
try:
# Download model files from Hugging Face Hub
model_path = hf_hub_download(
repo_id="QomSSLab/vits-fa-voice",
filename="best_model.pth",
cache_dir="models"
)
config_path = hf_hub_download(
repo_id="QomSSLab/vits-fa-voice",
filename="config.json",
cache_dir="models"
)
# Create synthesizer
synthesizer = Synthesizer(
tts_checkpoint=model_path,
tts_config_path=config_path,
use_cuda=False # Usually no GPU in free Spaces
)
status = "✅ مدل با موفقیت بارگذاری شد! اکنون می‌توانید از سیستم استفاده کنید."
return synthesizer, status
except Exception as e:
error_msg = f"خطا در بارگذاری مدل: {str(e)}"
status = f"❌ {error_msg}"
raise RuntimeError(error_msg)
def tts(text, speed):
if not text.strip():
return None, "لطفاً متنی وارد کنید.", "❌ لطفاً متنی وارد کنید."
try:
status = "در حال تبدیل متن به گفتار..."
# Generate speech
wav = synthesizer.tts(text, speed=speed)
output_path = "output.wav"
synthesizer.save_wav(wav, output_path)
status = "✅ صدا با موفقیت تولید شد!"
return output_path, "تبدیل با موفقیت انجام شد.", status
except Exception as e:
error_msg = f"خطا در تولید صدا: {str(e)}"
status = f"❌ {error_msg}"
return None, error_msg, status
# JavaScript function for updating status classes
def update_status_classes(status_text):
# This will be used with the gradio.js code
return status_text
# Create the interface with improved layout and professional design
with gr.Blocks(css=custom_css) as demo:
with gr.Column(elem_classes="container"):
gr.Markdown("# سامانه تبدیل متن فارسی به گفتار", elem_classes="main-header")
# Status area
with gr.Column(elem_classes="status-panel") as status_container:
status_output = gr.Markdown("در حال آماده‌سازی سیستم...", elem_id="status")
# Input panel
with gr.Column(elem_classes="input-panel"):
gr.Markdown("### متن ورودی", elem_classes="label")
text_input = gr.Textbox(
placeholder="متن فارسی خود را اینجا وارد کنید...",
lines=5,
label="",
elem_id="text-input",
elem_classes="input-text"
)
with gr.Row():
speed_slider = gr.Slider(
minimum=0.5,
maximum=2.0,
value=1.0,
step=0.1,
label="سرعت گفتار",
elem_classes="speed-slider"
)
submit_btn = gr.Button("تبدیل به گفتار", variant="primary", elem_classes="primary")
# Output panel
with gr.Column(elem_classes="output-panel"):
gr.Markdown("### خروجی صوتی", elem_classes="label")
output_audio = gr.Audio(label="")
result_text = gr.Markdown("")
# Examples panel
with gr.Column(elem_classes="examples-panel"):
gr.Markdown("### نمونه‌های متنی", elem_classes="label")
examples = gr.Examples(
examples=[
["سَلامْ دُنیا این یک آزمایش برای سیستم تبدیل متن به گفتارِ فارِسی است."],
["اِمروز هوا بسیار خوب است و من احساسِ شادی می‌کنم."],
["فَناوریِ هوشَ مصنوعیْ به سرعت در حالِ پیشرفت است و به زودی در تمام جنبه‌های زْندِگِیَ ما حضور خواهد داشت."]
],
inputs=text_input,
label="نمونه‌های متنی را امتحان کنید"
)
gr.Markdown(
"**راهنما**: متن فارسی خود را در کادر بالا وارد کنید و دکمه تبدیل را فشار دهید. "
"می‌توانید سرعت گفتار را با استفاده از نوار لغزنده تنظیم کنید.",
elem_classes="footer"
)
gr.Markdown(
"توسعه داده شده با استفاده از مدل VITS فارسی | [QomSSLab/vits-fa-voice](https://huggingface.co/QomSSLab/vits-fa-voice)",
elem_classes="footer"
)
# Add JavaScript for dynamic class changes - Gradio 4.0+ way
demo.load(js="""
function updateStatusClass(status_text) {
const statusPanel = document.querySelector('.status-panel');
if (!statusPanel) return;
// Remove all status classes
statusPanel.classList.remove('status-success', 'status-error', 'status-processing');
// Add appropriate class based on status content
if (status_text.includes('✅')) {
statusPanel.classList.add('status-success');
} else if (status_text.includes('❌')) {
statusPanel.classList.add('status-error');
} else if (status_text.includes('در حال')) {
statusPanel.classList.add('status-processing');
}
}
// Monitor changes to the status element
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
const statusEl = document.querySelector('#status');
if (statusEl) {
updateStatusClass(statusEl.textContent);
}
}
});
});
// Start observing when the DOM is ready
document.addEventListener('DOMContentLoaded', () => {
const statusEl = document.querySelector('#status');
if (statusEl) {
observer.observe(statusEl, { childList: true, subtree: true });
// Initial update
updateStatusClass(statusEl.textContent);
}
});
""")
# Initialize the synthesizer on app startup
synthesizer = None
@demo.load
def init_synthesizer():
global synthesizer
try:
synthesizer, status = load_synthesizer()
return status_output.update(status)
except Exception as e:
error_status = f"❌ خطا در بارگذاری مدل: {str(e)}"
return status_output.update(error_status)
# Define the tts_wrapper to handle status updates
def tts_wrapper(text, speed):
audio, result, status = tts(text, speed)
return audio, result, status
# Connect the function to the button
submit_btn.click(
fn=tts_wrapper,
inputs=[text_input, speed_slider],
outputs=[output_audio, result_text, status_output]
)
# Launch the interface
demo.launch()