arshambz commited on
Commit
a8d4280
·
1 Parent(s): 07b8226

update the ui

Browse files
Files changed (2) hide show
  1. app.py +241 -0
  2. requirements.txt +57 -0
app.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ import os
3
+ import mimetypes
4
+ import tempfile
5
+ from dotenv import load_dotenv
6
+ import requests
7
+ import gradio as gr
8
+
9
+ load_dotenv()
10
+ API_URL = os.getenv("ALAVAN_TTS_API", "https://service.alavan.co.ir/api/v3/Model/TTS")
11
+ API_TOKEN = os.getenv("ALAVAN_API_TOKEN")
12
+
13
+ VALID_SPEAKERS = ["nima", "ahmad", "raha", "setare"]
14
+ RECOMMENDED = ["ahmad", "setare"]
15
+
16
+
17
+ def download_audio_from_url(url):
18
+ try:
19
+ with requests.get(url, stream=True, timeout=30) as r:
20
+ r.raise_for_status()
21
+ content_type = r.headers.get("Content-Type", "")
22
+ ext = None
23
+ if content_type:
24
+ ext = mimetypes.guess_extension(content_type.split(";")[0].strip())
25
+ if not ext:
26
+ parsed_ext = os.path.splitext(url.split("?")[0])[1]
27
+ ext = parsed_ext or ".wav"
28
+
29
+ tmp_fd, tmp_path = tempfile.mkstemp(suffix=ext)
30
+ os.close(tmp_fd)
31
+ with open(tmp_path, "wb") as f:
32
+ for chunk in r.iter_content(chunk_size=8192):
33
+ if chunk:
34
+ f.write(chunk)
35
+ return tmp_path
36
+ except Exception:
37
+ return None
38
+
39
+
40
+ def extract_audio_url_from_json(j):
41
+ if not isinstance(j, dict):
42
+ return None
43
+ data = j.get("data")
44
+ if isinstance(data, dict):
45
+ for key in ("audioFileUrl", "audio_file_url", "audio_url", "file", "url", "result_url"):
46
+ if key in data and isinstance(data[key], str) and data[key].strip():
47
+ return data[key].strip()
48
+ for key in ("audioFileUrl", "audio_file_url", "audio_url", "file", "url", "result_url"):
49
+ if key in j and isinstance(j[key], str) and j[key].strip():
50
+ return j[key].strip()
51
+ return None
52
+
53
+
54
+ def pretty_server_error(resp):
55
+ try:
56
+ data = resp.json()
57
+ except Exception:
58
+ return f"کد {resp.status_code} — پاسخ غیرقابل‌خواندن"
59
+ parts = []
60
+ if isinstance(data, dict):
61
+ if "message" in data:
62
+ parts.append(str(data["message"]))
63
+ errors = data.get("errors") or data.get("error") or {}
64
+ if isinstance(errors, dict):
65
+ for k, v in errors.items():
66
+ if isinstance(v, list):
67
+ parts.append(f"{k}: {', '.join(map(str, v))}")
68
+ else:
69
+ parts.append(f"{k}: {v}")
70
+ elif errors:
71
+ parts.append(str(errors))
72
+ return " — ".join(parts) if parts else f"کد {resp.status_code}"
73
+ return f"کد {resp.status_code}"
74
+
75
+
76
+ def tts_call_and_fetch(text, speaker_input):
77
+ if not API_TOKEN or not API_URL:
78
+ return "❌ توکن یا آدرس API تنظیم نشده. لطفاً فایل .env یا Secrets را بررسی کنید.", None
79
+
80
+ if not text or not text.strip():
81
+ return "❌ متن ورودی خالی است.", None
82
+
83
+ speaker = (speaker_input or "").strip().lower()
84
+ if speaker not in VALID_SPEAKERS:
85
+ return (
86
+ "❌ گوینده نامعتبر است. گوینده‌های معتبر:\n"
87
+ f"{', '.join(VALID_SPEAKERS)}\n"
88
+ f"گویندگان پیشنهادی: {', '.join(RECOMMENDED)}"
89
+ ), None
90
+
91
+ headers = {
92
+ "Content-Type": "application/json",
93
+ "Authorization": f"Bearer {API_TOKEN}"
94
+ }
95
+
96
+ payload = {
97
+ "text": text,
98
+ "modelType": speaker
99
+ }
100
+
101
+ try:
102
+ resp = requests.post(API_URL, headers=headers, json=payload, timeout=30)
103
+ except Exception as e:
104
+ return f"❌ خطا در اتصال به API: {e}", None
105
+ resp_json = None
106
+ try:
107
+ resp_json = resp.json()
108
+ except Exception:
109
+ resp_json = None
110
+ data_json = None
111
+ if resp.status_code in (200, 201) or isinstance(resp_json, dict):
112
+ data_json = resp_json or {}
113
+ else:
114
+ pretty = pretty_server_error(resp)
115
+ return f"❌ خطا از سرور API: {pretty}", None
116
+
117
+ audio_url = extract_audio_url_from_json(data_json)
118
+ if not audio_url:
119
+ if isinstance(data_json.get("data"), str) and data_json.get("data").startswith("http"):
120
+ audio_url = data_json.get("data")
121
+ if not audio_url:
122
+ return "❌ پاسخ API شامل آدرس فایل صوتی نبود.", None
123
+
124
+ audio_path = download_audio_from_url(audio_url)
125
+ if not audio_path:
126
+ return "❌ خطا در دانلود فایل صوتی از URL دریافتی.", None
127
+
128
+ return "✅ تولید و دانلود صدا با موفقیت انجام شد.", audio_path
129
+
130
+
131
+ with gr.Blocks(analytics_enabled=False, css="""
132
+ body { font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; }
133
+ .header {
134
+ text-align: center;
135
+ padding: 14px 18px;
136
+ border-radius: 10px;
137
+ background: linear-gradient(rgb(255 114 114), rgb(251, 251, 255));
138
+ box-shadow: 0 6px 18px rgba(16,24,40,0.04);
139
+ margin-bottom: 18px;
140
+ }
141
+ .header h2 { margin: 0 0 6px 0; color:white;}
142
+ .header p { margin: 0; color: #6b7280; }
143
+
144
+ .card {
145
+ padding: 12px;
146
+ border-radius: 10px;
147
+ background: linear-gradient(180deg, #ffffff, #fcfcff);
148
+ box-shadow: 0 6px 18px rgba(16,24,40,0.04);
149
+ }
150
+
151
+ .badge {
152
+ display:inline-block;
153
+ padding:6px 10px;
154
+ border-radius:999px;
155
+ font-size:13px;
156
+ margin:3px 6px 3px 0;
157
+ background:#f1f5f9;
158
+ color:#111827;
159
+ }
160
+
161
+ .footer {
162
+ text-align:center;
163
+ margin-top:18px;
164
+ color:#6b7280;
165
+ font-size:13px;
166
+ }
167
+ """) as demo:
168
+ with gr.Row():
169
+ with gr.Column(scale=3):
170
+ gr.Markdown(
171
+ "<div class='header'>"
172
+ "<h2>🎤 Alavan TTS — تبدیل متن به گفتار (فارسی)</h2>"
173
+ "<p>مدل تبدیل نوشتار به گفتار آلاوان با دریافت متون فارسی، آن ها را به خروجی صوتی با صدایی واضح، قابل فهم و نزدیک به گفتار انسانی تبدیل می کند. این مدل به گونه ای طراحی شده است که لحن و آهنگ طبیعی گفتار حفظ شود و برای شنونده تجربه ای روان و دلنشین فراهم گردد. کاربرد اصلی این مدل در تولید پادکست، کتاب صوتی، راهنمای صوتی اپلیکیشن ها، سامانه های پاسخگویی خودکار و دستیارهای صوتی است.</p>"
174
+ "</div>"
175
+ , rtl=True
176
+ )
177
+ with gr.Column(scale=1, min_width=220):
178
+ gr.HTML(
179
+ "<div style='text-align:right;align-item:right;'>"
180
+ "<img src='https://alavan.co.ir/wp-content/uploads/2024/12/NEW-LOGO-ALAVAN.png' alt='تبدیل متن به صوت آلاوان' width='50%' height='50%' style='margin-left:10rem;margin-bottom:20px;'/>"
181
+ "<a href='https://alavan.co.ir' target='_blank'>🌐 وب ‌سایت آلاوان</a><br>"
182
+ "<a href='https://portal.alavan.co.ir' target='_blank'>🔑 پرتال کاربران</a>"
183
+ "</div>"
184
+ )
185
+
186
+ gr.Markdown("---")
187
+ with gr.Row():
188
+ # left column: inputs + results
189
+ with gr.Column(scale=2):
190
+ with gr.Row():
191
+ with gr.Column():
192
+ gr.Markdown("### تنظیمات و تولید صدا", rtl=True)
193
+ # کارت ورودی
194
+ gr.HTML("<div class='card'>")
195
+ text_in = gr.Textbox(label="متن ورودی", placeholder="متن فارسی یا انگلیسی... (حداقل یک جمله)",
196
+ lines=6, rtl=True)
197
+ speaker = gr.Dropdown(choices=VALID_SPEAKERS, label="گوینده", value="setare")
198
+ with gr.Row():
199
+ btn = gr.Button("🔊 تولید صدا", variant="primary")
200
+ clear_btn = gr.Button("🧹 پاک‌کردن ورودی")
201
+ status = gr.Textbox(label="وضعیت", interactive=False, rtl=True)
202
+ audio_out = gr.Audio(label="خروجی صوت", type="filepath")
203
+ gr.HTML("</div>")
204
+ gr.Markdown("#### نمونه‌های آماده", rtl=True)
205
+ examples = [
206
+ ["سلام! امروز حال شما چطور است؟", "nima"],
207
+ ["این یک نمونهٔ تبدیل متن به گفتار فارسی است.", "setare"],
208
+ ["لطفاً صدای آزمایشی را با گوینده احمد تولید کن.", "ahmad"],
209
+ ]
210
+ gr.Examples(examples=examples, inputs=[text_in, speaker], label="کلیک کن تا فیلدها پر شوند")
211
+
212
+ with gr.Column(scale=1):
213
+ gr.HTML("<div class='card'>")
214
+ gr.Markdown("### گوینده‌ها", rtl=True)
215
+ # badges گوینده‌ها
216
+ badges_html = " ".join([f"<span class='badge'>{s}</span>" for s in VALID_SPEAKERS])
217
+ gr.HTML(badges_html)
218
+ gr.Markdown("---")
219
+ gr.Markdown(
220
+ "**نکات مهم:**\n\n"
221
+ "- فایل صوتی بلافاصله دانلود شده و فقط نسخهٔ محلی پخش می‌شود\n"
222
+ "- برای استفاده در اپ های خود وارد سایت آلاوان شوید\n"
223
+ "- در صورت دریافت پیام سرور با متن خطا، پیام خلاصه‌شده به شما نمایش داده خواهد شد.\n"
224
+ "- خیلی از مدل های تبدیل متن به صوت فارسی تاریخ هارو ساپورت نمیکنند ولی مدل ستاره ما از تمامای واژگان اعداد و تاریخ های فارسی پشتیبانی میکند\n"
225
+ "- برای تغیر نوع خوانش از علائم نگارشی و , برای مکس استفاده کنید.\n"
226
+ , rtl=True
227
+ )
228
+ gr.Markdown("---")
229
+ gr.Markdown("### گویندگان پیشنهادی", rtl=True)
230
+ rec_html = " ".join([f"<span class='badge'>{s}</span>" for s in RECOMMENDED])
231
+ gr.HTML(rec_html)
232
+ gr.HTML("</div>")
233
+ gr.HTML("<div class='footer'>ساخته شده با ❤️ توسط تیم <a href=https://alavan.co.ir/>آلاوان</a></div>")
234
+ clear_btn.click(lambda: ("", "setare"), inputs=None, outputs=[text_in, speaker], api_name=False,
235
+ show_api=False)
236
+
237
+ btn.click(fn=tts_call_and_fetch, inputs=[text_in, speaker], outputs=[status, audio_out], api_name=False,
238
+ show_api=False)
239
+
240
+ if __name__ == "__main__":
241
+ demo.launch(server_name="0.0.0.0", server_port=7860, show_api=False)
requirements.txt ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiofiles==24.1.0
2
+ annotated-types==0.7.0
3
+ anyio==4.10.0
4
+ Brotli==1.1.0
5
+ certifi==2025.8.3
6
+ charset-normalizer==3.4.3
7
+ click==8.2.1
8
+ exceptiongroup==1.3.0
9
+ fastapi==0.116.1
10
+ ffmpy==0.6.1
11
+ filelock==3.19.1
12
+ fsspec==2025.9.0
13
+ gradio==5.45.0
14
+ gradio_client==1.13.0
15
+ groovy==0.1.2
16
+ h11==0.16.0
17
+ hf-xet==1.1.10
18
+ httpcore==1.0.9
19
+ httpx==0.28.1
20
+ huggingface-hub==0.34.6
21
+ idna==3.10
22
+ Jinja2==3.1.6
23
+ markdown-it-py==4.0.0
24
+ MarkupSafe==3.0.2
25
+ mdurl==0.1.2
26
+ numpy==2.2.6
27
+ orjson==3.11.3
28
+ packaging==25.0
29
+ pandas==2.3.2
30
+ pillow==11.3.0
31
+ pydantic==2.11.9
32
+ pydantic_core==2.33.2
33
+ pydub==0.25.1
34
+ Pygments==2.19.2
35
+ python-dateutil==2.9.0.post0
36
+ python-dotenv==1.1.1
37
+ python-multipart==0.0.20
38
+ pytz==2025.2
39
+ PyYAML==6.0.2
40
+ requests==2.32.5
41
+ rich==14.1.0
42
+ ruff==0.13.0
43
+ safehttpx==0.1.6
44
+ semantic-version==2.10.0
45
+ shellingham==1.5.4
46
+ six==1.17.0
47
+ sniffio==1.3.1
48
+ starlette==0.47.3
49
+ tomlkit==0.13.3
50
+ tqdm==4.67.1
51
+ typer==0.17.4
52
+ typing-inspection==0.4.1
53
+ typing_extensions==4.15.0
54
+ tzdata==2025.2
55
+ urllib3==2.5.0
56
+ uvicorn==0.35.0
57
+ websockets==15.0.1