Phoe2004 commited on
Commit
e73ffd4
·
verified ·
1 Parent(s): 266e145

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +28 -36
  2. index.html +488 -563
  3. voices_data.py +330 -0
app.py CHANGED
@@ -2,24 +2,26 @@ import os
2
  import uuid
3
  import asyncio
4
  import tempfile
 
5
 
6
  import edge_tts
7
  from flask import Flask, request, jsonify, send_file, after_this_request
8
 
 
 
9
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
10
  app = Flask(__name__)
11
 
12
- # --- Myanmar voices available in Edge TTS ---
13
- VOICES = {
14
- "my-MM-NilarNeural": {"label": "Nilar (Female)", "gender": "Female"},
15
- "my-MM-ThihaNeural": {"label": "Thiha (Male)", "gender": "Male"},
16
- }
17
 
18
  DEFAULT_VOICE = "my-MM-NilarNeural"
19
  TMP_DIR = os.path.join(tempfile.gettempdir(), "mm_tts_outputs")
20
  os.makedirs(TMP_DIR, exist_ok=True)
21
 
22
- MAX_CHARS = 10000
23
 
24
 
25
  def clamp_pct(value, lo=-50, hi=50):
@@ -30,39 +32,29 @@ def clamp_pct(value, lo=-50, hi=50):
30
  return max(lo, min(hi, value))
31
 
32
 
 
 
 
 
 
 
 
 
 
33
  @app.route("/")
34
  def index():
35
  return send_file(os.path.join(BASE_DIR, "index.html"))
36
 
37
 
38
- @app.route("/about")
39
- def about():
40
- return send_file(os.path.join(BASE_DIR, "about.html"))
41
-
42
-
43
- @app.route("/privacy")
44
- def privacy():
45
- return send_file(os.path.join(BASE_DIR, "privacy.html"))
46
-
47
-
48
- @app.route("/terms")
49
- def terms():
50
- return send_file(os.path.join(BASE_DIR, "terms.html"))
51
-
52
-
53
- @app.route("/style.css")
54
- def style_css():
55
- return send_file(os.path.join(BASE_DIR, "style.css"), mimetype="text/css")
56
-
57
-
58
- @app.route("/app.js")
59
- def app_js():
60
- return send_file(os.path.join(BASE_DIR, "app.js"), mimetype="application/javascript")
61
-
62
-
63
  @app.route("/api/voices")
64
  def get_voices():
65
- return jsonify(VOICES)
 
 
 
 
 
 
66
 
67
 
68
  @app.route("/api/tts", methods=["POST"])
@@ -75,10 +67,10 @@ def tts():
75
  pitch = clamp_pct(data.get("pitch", 0))
76
 
77
  if not text:
78
- return jsonify({"error": "Please enter some text."}), 400
79
 
80
  if len(text) > MAX_CHARS:
81
- return jsonify({"error": f"Text must be {MAX_CHARS} characters or fewer."}), 400
82
 
83
  if voice not in VOICES:
84
  voice = DEFAULT_VOICE
@@ -99,7 +91,7 @@ def tts():
99
  return jsonify({"error": str(e)}), 500
100
 
101
  if not os.path.exists(filepath) or os.path.getsize(filepath) == 0:
102
- return jsonify({"error": "Audio generation failed. Please try again."}), 500
103
 
104
  @after_this_request
105
  def cleanup(response):
@@ -113,7 +105,7 @@ def tts():
113
  filepath,
114
  mimetype="audio/mpeg",
115
  as_attachment=False,
116
- download_name="myanmar_voice.mp3",
117
  conditional=False,
118
  )
119
 
 
2
  import uuid
3
  import asyncio
4
  import tempfile
5
+ from collections import OrderedDict
6
 
7
  import edge_tts
8
  from flask import Flask, request, jsonify, send_file, after_this_request
9
 
10
+ from voices_data import VOICE_LIST
11
+
12
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
13
  app = Flask(__name__)
14
 
15
+ # --- Build voice catalogue: short_name -> {lang, gender} ---
16
+ VOICES = OrderedDict()
17
+ for short_name, lang, gender in VOICE_LIST:
18
+ VOICES[short_name] = {"lang": lang, "gender": gender}
 
19
 
20
  DEFAULT_VOICE = "my-MM-NilarNeural"
21
  TMP_DIR = os.path.join(tempfile.gettempdir(), "mm_tts_outputs")
22
  os.makedirs(TMP_DIR, exist_ok=True)
23
 
24
+ MAX_CHARS = 3000
25
 
26
 
27
  def clamp_pct(value, lo=-50, hi=50):
 
32
  return max(lo, min(hi, value))
33
 
34
 
35
+ def display_name(short_name):
36
+ # e.g. "en-US-AvaMultilingualNeural" -> "Ava (Multilingual)"
37
+ part = short_name.split("-")[-1]
38
+ part = part.replace("Neural", "")
39
+ if part.endswith("Multilingual"):
40
+ part = part.replace("Multilingual", "") + " (Multilingual)"
41
+ return part
42
+
43
+
44
  @app.route("/")
45
  def index():
46
  return send_file(os.path.join(BASE_DIR, "index.html"))
47
 
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  @app.route("/api/voices")
50
  def get_voices():
51
+ """Return voices grouped by language for the UI dropdowns."""
52
+ grouped = OrderedDict()
53
+ for short_name, lang, gender in VOICE_LIST:
54
+ grouped.setdefault(lang, []).append(
55
+ {"id": short_name, "name": display_name(short_name), "gender": gender}
56
+ )
57
+ return jsonify(grouped)
58
 
59
 
60
  @app.route("/api/tts", methods=["POST"])
 
67
  pitch = clamp_pct(data.get("pitch", 0))
68
 
69
  if not text:
70
+ return jsonify({"error": "စာသား ထည့်ပေးပါ (text is required)"}), 400
71
 
72
  if len(text) > MAX_CHARS:
73
+ return jsonify({"error": f"စာသား {MAX_CHARS} လုံးအထိသာ ထည့်နိုင်ပါသည်"}), 400
74
 
75
  if voice not in VOICES:
76
  voice = DEFAULT_VOICE
 
91
  return jsonify({"error": str(e)}), 500
92
 
93
  if not os.path.exists(filepath) or os.path.getsize(filepath) == 0:
94
+ return jsonify({"error": "အသံဖိုင် ထုတ်ယူခြင်း မအောင်မြင်ပါ"}), 500
95
 
96
  @after_this_request
97
  def cleanup(response):
 
105
  filepath,
106
  mimetype="audio/mpeg",
107
  as_attachment=False,
108
+ download_name="voice.mp3",
109
  conditional=False,
110
  )
111
 
index.html CHANGED
@@ -3,399 +3,334 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Myanmar Voice Studio Free Myanmar Text to Speech (TTS)</title>
7
- <meta name="description" content="Convert Myanmar (Burmese) text into natural-sounding speech for free. Choose from neural voices, adjust speed and pitch, and download MP3 audio instantly — no sign-up required.">
8
-
9
- <!-- Google AdSense Verification -->
10
- <meta name="google-adsense-account" content="ca-pub-1188893611988786">
11
 
12
  <link rel="preconnect" href="https://fonts.googleapis.com">
13
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
- <link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400..700;1,9..144,400..600&family=Inter:wght@300;400;500;600;700&family=Noto+Sans+Myanmar:wght@400;500;600;700&display=swap" rel="stylesheet">
15
- <link rel="stylesheet" href="/style.css">
16
-
17
- <!-- Google AdSense -->
18
- <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-1188893611988786" crossorigin="anonymous"></script>
19
 
20
  <style>
21
- /* ===== NAVBAR REDESIGN ===== */
22
- :root {
23
- --nav-h: 64px;
24
- --brand-color: #7C3AED;
25
- --brand-light: #EDE9FE;
26
- --brand-mid: #A78BFA;
27
- --accent: #F59E0B;
28
- --surface: rgba(255,255,255,0.85);
29
- --text-main: #1E1B4B;
30
- --text-muted: #6B7280;
31
- --radius: 12px;
32
- --shadow-nav: 0 1px 0 rgba(0,0,0,.06), 0 4px 24px rgba(124,58,237,.08);
33
- }
34
-
35
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
36
-
37
- /* ── Navbar shell ── */
38
- .nav {
39
- position: sticky;
40
- top: 0;
41
- z-index: 100;
42
- height: var(--nav-h);
43
- background: var(--surface);
44
- backdrop-filter: blur(18px) saturate(180%);
45
- -webkit-backdrop-filter: blur(18px) saturate(180%);
46
- border-bottom: 1px solid rgba(124,58,237,.12);
47
- box-shadow: var(--shadow-nav);
48
- }
49
-
50
- .nav .wrap {
51
- max-width: 1120px;
52
- margin: 0 auto;
53
- padding: 0 24px;
54
- height: 100%;
55
- display: flex;
56
- align-items: center;
57
- justify-content: space-between;
58
- gap: 16px;
59
- }
60
-
61
- /* ── Brand / Logo ── */
62
- .brand {
63
- display: flex;
64
- align-items: center;
65
- gap: 10px;
66
- text-decoration: none;
67
- flex-shrink: 0;
68
- }
69
-
70
- .brand-icon {
71
- width: 36px;
72
- height: 36px;
73
- background: linear-gradient(135deg, #7C3AED 0%, #A78BFA 100%);
74
- border-radius: 10px;
75
- display: flex;
76
- align-items: center;
77
- justify-content: center;
78
- box-shadow: 0 2px 8px rgba(124,58,237,.35);
79
- flex-shrink: 0;
80
- }
81
-
82
- .brand-icon svg {
83
- width: 20px;
84
- height: 20px;
85
- fill: white;
86
- }
87
-
88
- .brand-text {
89
- display: flex;
90
- flex-direction: column;
91
- line-height: 1.1;
92
- }
93
-
94
- .brand-name {
95
- font-family: 'Fraunces', serif;
96
- font-size: 1rem;
97
- font-weight: 700;
98
- color: var(--text-main);
99
- letter-spacing: -0.01em;
100
- }
101
-
102
- .brand-tagline {
103
- font-family: 'Inter', sans-serif;
104
- font-size: 0.65rem;
105
- font-weight: 500;
106
- color: var(--brand-color);
107
- text-transform: uppercase;
108
- letter-spacing: 0.08em;
109
- }
110
-
111
- /* ── Nav links ── */
112
- .nav-links {
113
- display: flex;
114
- align-items: center;
115
- gap: 2px;
116
- }
117
-
118
- .nav-links a {
119
- font-family: 'Inter', sans-serif;
120
- font-size: 0.875rem;
121
- font-weight: 500;
122
- color: var(--text-muted);
123
- text-decoration: none;
124
- padding: 6px 14px;
125
- border-radius: 8px;
126
- transition: color .18s ease, background .18s ease;
127
- white-space: nowrap;
128
- }
129
-
130
- .nav-links a:hover {
131
- color: var(--brand-color);
132
- background: var(--brand-light);
133
- }
134
-
135
- .nav-links a.active {
136
- color: var(--brand-color);
137
- background: var(--brand-light);
138
- font-weight: 600;
139
- }
140
-
141
- /* ── CTA button in nav ── */
142
- .nav-cta {
143
- font-family: 'Inter', sans-serif;
144
- font-size: 0.85rem;
145
- font-weight: 600;
146
- color: #fff !important;
147
- background: linear-gradient(135deg, #7C3AED 0%, #6D28D9 100%) !important;
148
- padding: 8px 18px !important;
149
- border-radius: 8px !important;
150
- box-shadow: 0 2px 8px rgba(124,58,237,.3);
151
- transition: transform .15s ease, box-shadow .15s ease !important;
152
- margin-left: 6px;
153
- }
154
-
155
- .nav-cta:hover {
156
- transform: translateY(-1px);
157
- box-shadow: 0 4px 16px rgba(124,58,237,.4) !important;
158
- background: linear-gradient(135deg, #6D28D9 0%, #5B21B6 100%) !important;
159
- color: #fff !important;
160
- }
161
-
162
- /* ── Badge chip ── */
163
- .nav-badge {
164
- display: inline-flex;
165
- align-items: center;
166
- gap: 4px;
167
- font-family: 'Inter', sans-serif;
168
- font-size: 0.7rem;
169
- font-weight: 700;
170
- color: #059669;
171
- background: #D1FAE5;
172
- padding: 2px 7px;
173
- border-radius: 99px;
174
- letter-spacing: 0.04em;
175
- text-transform: uppercase;
176
- margin-left: 4px;
177
- }
178
-
179
- .nav-badge::before {
180
- content: '';
181
- width: 6px;
182
- height: 6px;
183
- background: #10B981;
184
- border-radius: 50%;
185
- animation: pulse-dot 2s infinite;
186
- }
187
-
188
- @keyframes pulse-dot {
189
- 0%, 100% { opacity: 1; transform: scale(1); }
190
- 50% { opacity: .6; transform: scale(.85); }
191
- }
192
-
193
- /* ��─ Hamburger (mobile) ── */
194
- .nav-toggle {
195
- display: none;
196
- flex-direction: column;
197
- gap: 5px;
198
- background: none;
199
- border: none;
200
- cursor: pointer;
201
- padding: 6px;
202
- border-radius: 8px;
203
- transition: background .15s;
204
- }
205
-
206
- .nav-toggle:hover { background: var(--brand-light); }
207
-
208
- .nav-toggle span {
209
- display: block;
210
- width: 22px;
211
- height: 2px;
212
- background: var(--text-main);
213
- border-radius: 2px;
214
- transition: transform .25s ease, opacity .25s ease;
215
- transform-origin: center;
216
- }
217
-
218
- .nav-toggle.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
219
- .nav-toggle.open span:nth-child(2) { opacity: 0; transform: scaleX(0); }
220
- .nav-toggle.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
221
-
222
- /* ── Mobile menu ── */
223
- @media (max-width: 720px) {
224
- .nav-toggle { display: flex; }
225
- .brand-tagline { display: none; }
226
-
227
- .nav-links {
228
- display: none;
229
- position: absolute;
230
- top: var(--nav-h);
231
- left: 0;
232
- right: 0;
233
- flex-direction: column;
234
- align-items: stretch;
235
- background: rgba(255,255,255,0.97);
236
- backdrop-filter: blur(20px);
237
- border-bottom: 1px solid rgba(124,58,237,.12);
238
- padding: 12px 16px 20px;
239
- gap: 4px;
240
- box-shadow: 0 8px 32px rgba(0,0,0,.1);
241
- }
242
-
243
- .nav-links.open { display: flex; }
244
-
245
- .nav-links a {
246
- font-size: 0.95rem;
247
- padding: 10px 16px;
248
- border-radius: 10px;
249
- }
250
-
251
- .nav-cta {
252
- margin-left: 0 !important;
253
- text-align: center;
254
- margin-top: 4px;
255
- }
256
-
257
- .nav-badge { display: none; }
258
- }
259
-
260
- /* ===== AD SLOT POLISH ===== */
261
- .ad-slot {
262
- max-width: 1120px;
263
- margin: 32px auto;
264
- padding: 0 24px;
265
- }
266
-
267
- .ad-label {
268
- font-family: 'Inter', sans-serif;
269
- font-size: 0.7rem;
270
- font-weight: 600;
271
- color: #9CA3AF;
272
- text-transform: uppercase;
273
- letter-spacing: 0.1em;
274
- text-align: center;
275
- margin-bottom: 8px;
276
- }
277
-
278
- .ad-inner {
279
- background: #F9FAFB;
280
- border: 1px solid #E5E7EB;
281
- border-radius: var(--radius);
282
- overflow: hidden;
283
- min-height: 90px;
284
- display: flex;
285
- align-items: center;
286
- justify-content: center;
287
- }
 
 
288
  </style>
289
  </head>
290
  <body>
291
 
292
- <!-- ===================== NAVBAR ===================== -->
293
- <nav class="nav">
294
  <div class="wrap">
295
-
296
- <!-- Brand -->
297
- <a href="/" class="brand">
298
- <div class="brand-icon">
299
- <!-- Waveform / speaker SVG icon -->
300
- <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
301
- <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3A4.5 4.5 0 0 0 14 7.97v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
302
- </svg>
303
- </div>
304
- <div class="brand-text">
305
- <span class="brand-name">Myanmar Voice Studio</span>
306
- <span class="brand-tagline">Free TTS · Neural Voices</span>
 
 
 
 
 
 
 
 
 
307
  </div>
308
- </a>
309
-
310
- <!-- Desktop nav links -->
311
- <div class="nav-links" id="navLinks">
312
- <a href="/" class="active">Home</a>
313
- <a href="#tool">Try It</a>
314
- <a href="#how-it-works">How It Works</a>
315
- <a href="#faq">FAQ</a>
316
- <a href="/about">About</a>
317
- <span class="nav-badge">Free</span>
318
- <a href="#tool" class="nav-cta">Generate Voice →</a>
319
- </div>
320
-
321
- <!-- Hamburger toggle -->
322
- <button class="nav-toggle" id="navToggle" aria-label="Toggle menu">
323
- <span></span>
324
- <span></span>
325
- <span></span>
326
- </button>
327
 
328
- </div>
329
- </nav>
330
-
331
- <div class="wrap">
332
-
333
- <header class="hero">
334
- <p class="eyebrow">Free Online Tool</p>
335
- <h1>Turn Myanmar Text Into Natural Sounding Speech</h1>
336
- <p class="lead">Myanmar Voice Studio uses high-quality neural voices to read your
337
- Burmese text aloud. Paste up to 10,000 characters, fine-tune the speed and
338
- pitch, and download a ready-to-use MP3 — completely free, no account needed.</p>
339
- <div class="wave" id="wave"></div>
340
- </header>
341
-
342
- </div>
343
-
344
- <!-- Top ad slot -->
345
- <div class="ad-slot">
346
- <div class="ad-label">Advertisement</div>
347
- <div class="ad-inner">
348
- <ins class="adsbygoogle"
349
- style="display:block;width:100%"
350
- data-ad-client="ca-pub-1188893611988786"
351
- data-ad-slot="1111111111"
352
- data-ad-format="auto"
353
- data-full-width-responsive="true"></ins>
354
- <script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
355
- </div>
356
- </div>
357
-
358
- <div class="wrap" id="tool">
359
-
360
- <!-- Text input -->
361
- <section class="tight">
362
- <div class="panel">
363
- <h3>Your Script</h3>
364
- <textarea id="text" placeholder="Type or paste Myanmar text here..." maxlength="10000"></textarea>
365
- <div class="char-count" id="charCount">0 / 10000</div>
366
- </div>
367
- </section>
368
-
369
- <!-- Voice + settings -->
370
- <section class="tight">
371
- <div class="panel">
372
- <h3>Voice</h3>
373
- <div class="voices" id="voices">
374
- <label class="voice-card selected" data-voice="my-MM-NilarNeural">
375
- <input type="radio" name="voice" value="my-MM-NilarNeural" checked>
376
- <div class="name">နီလာ — Nilar</div>
377
- <div class="role">Female</div>
378
- </label>
379
- <label class="voice-card" data-voice="my-MM-ThihaNeural">
380
- <input type="radio" name="voice" value="my-MM-ThihaNeural">
381
- <div class="name">သီဟ — Thiha</div>
382
- <div class="role">Male</div>
383
- </label>
384
  </div>
385
 
386
  <div class="controls">
387
  <div class="control">
388
- <label>Speed <span class="val" id="rateVal">+0%</span></label>
389
  <input type="range" id="rate" min="-50" max="50" value="0" step="5">
390
  </div>
391
  <div class="control">
392
- <label>Pitch <span class="val" id="pitchVal">+0Hz</span></label>
393
  <input type="range" id="pitch" min="-50" max="50" value="0" step="5">
394
  </div>
395
  </div>
396
 
397
  <div class="actions">
398
- <button class="btn-primary" id="generateBtn">Generate Voice</button>
399
  </div>
400
 
401
  <div class="status" id="status"></div>
@@ -403,209 +338,199 @@
403
  <div class="output" id="output">
404
  <audio id="player" controls></audio>
405
  <div class="actions">
406
- <a class="btn-secondary" id="downloadLink" download="myanmar_voice.mp3">Download MP3</a>
407
  </div>
408
  </div>
409
- </div>
410
- </section>
411
-
412
- <!-- How it works -->
413
- <section id="how-it-works">
414
- <div class="section-head">
415
- <p class="eyebrow">How It Works</p>
416
- <h2>From text to voice in three steps</h2>
417
- <p>No installation, no registration. Everything runs directly in your browser.</p>
418
- </div>
419
- <div class="grid">
420
- <div class="card">
421
- <div class="index">01</div>
422
- <h3>Write or paste your text</h3>
423
- <p>Add up to 10,000 characters of Myanmar text — scripts, articles, captions, or study notes.</p>
424
- </div>
425
- <div class="card">
426
- <div class="index">02</div>
427
- <h3>Pick a voice &amp; tune it</h3>
428
- <p>Choose between Nilar (female) and Thiha (male), then adjust the speaking speed and pitch.</p>
429
- </div>
430
- <div class="card">
431
- <div class="index">03</div>
432
- <h3>Generate &amp; download</h3>
433
- <p>Click Generate, preview the audio instantly, and download it as an MP3 file.</p>
434
- </div>
435
- </div>
436
- </section>
437
-
438
- <!-- Features -->
439
- <section>
440
- <div class="section-head">
441
- <p class="eyebrow">Features</p>
442
- <h2>Built for creators &amp; learners</h2>
443
- <p>Everything you need for quick, natural Myanmar voiceovers.</p>
444
- </div>
445
- <div class="grid">
446
- <div class="card">
447
- <h3>Natural Neural Voices</h3>
448
- <p>Powered by Microsoft's neural text-to-speech engine for clear, lifelike Myanmar pronunciation.</p>
449
- </div>
450
- <div class="card">
451
- <h3>Adjustable Speed &amp; Pitch</h3>
452
- <p>Slow it down for learning material, or speed it up for short-form video narration.</p>
453
- </div>
454
- <div class="card">
455
- <h3>Up to 10,000 Characters</h3>
456
- <p>Convert long articles, scripts, or chapters in a single request.</p>
457
- </div>
458
- <div class="card">
459
- <h3>Instant MP3 Download</h3>
460
- <p>Your audio file is ready to download and use the moment it's generated.</p>
461
- </div>
462
- <div class="card">
463
- <h3>No Sign-up Required</h3>
464
- <p>Start using the tool immediately — there are no accounts, passwords, or limits to unlock.</p>
465
- </div>
466
- <div class="card">
467
- <h3>Free to Use</h3>
468
- <p>Myanmar Voice Studio is free for personal, educational, and commercial projects.</p>
469
- </div>
470
- </div>
471
- </section>
472
-
473
- <!-- Use cases -->
474
- <section>
475
- <div class="section-head">
476
- <p class="eyebrow">Use Cases</p>
477
- <h2>Who is this for?</h2>
478
- </div>
479
- <div class="grid">
480
- <div class="card">
481
- <h3>Content Creators</h3>
482
- <p>Generate voiceovers for YouTube videos, TikToks, and Reels in Myanmar language.</p>
483
- </div>
484
- <div class="card">
485
- <h3>Educators &amp; Students</h3>
486
- <p>Turn lesson notes and reading material into audio for revision and accessibility.</p>
487
- </div>
488
- <div class="card">
489
- <h3>Developers</h3>
490
- <p>Prototype Myanmar-language apps, games, and IVR systems with quick sample audio.</p>
491
- </div>
492
- <div class="card">
493
- <h3>Podcasters &amp; Writers</h3>
494
- <p>Preview how your written Myanmar content sounds when read aloud.</p>
495
- </div>
496
- <div class="card">
497
- <h3>Accessibility</h3>
498
- <p>Help readers who are visually impaired or prefer listening over reading.</p>
499
- </div>
500
- <div class="card">
501
- <h3>Translators</h3>
502
- <p>Check pronunciation and pacing of translated Myanmar scripts before recording.</p>
503
- </div>
504
- </div>
505
- </section>
506
-
507
- <!-- In-content ad slot -->
508
- <div class="ad-slot" style="padding:0">
509
- <div class="ad-label">Advertisement</div>
510
- <div class="ad-inner">
511
- <ins class="adsbygoogle"
512
- style="display:block;width:100%"
513
- data-ad-client="ca-pub-1188893611988786"
514
- data-ad-slot="2222222222"
515
- data-ad-format="auto"
516
- data-full-width-responsive="true"></ins>
517
- <script>(adsbygoogle = window.adsbygoogle || []).push({});</script>
518
- </div>
519
- </div>
520
-
521
- <!-- FAQ -->
522
- <section id="faq">
523
- <div class="section-head">
524
- <p class="eyebrow">FAQ</p>
525
- <h2>Frequently asked questions</h2>
526
- </div>
527
-
528
- <details class="faq-item" open>
529
- <summary>Is Myanmar Voice Studio free to use?</summary>
530
- <p>Yes. The tool is completely free for personal, educational, and commercial use. There are no hidden fees or subscriptions.</p>
531
- </details>
532
-
533
- <details class="faq-item">
534
- <summary>Which Myanmar voices are available?</summary>
535
- <p>You can choose between two neural voices: Nilar, a female voice, and Thiha, a male voice. Both are tuned for natural Burmese pronunciation.</p>
536
- </details>
537
-
538
- <details class="faq-item">
539
- <summary>Can I change the speaking speed and pitch?</summary>
540
- <p>Yes. Use the Speed and Pitch sliders before generating audio to make the voice sound slower, faster, deeper, or lighter.</p>
541
- </details>
542
-
543
- <details class="faq-item">
544
- <summary>What is the maximum text length?</summary>
545
- <p>You can convert up to 10,000 characters of Myanmar text in a single request.</p>
546
- </details>
547
-
548
- <details class="faq-item">
549
- <summary>Can I use the audio in my videos or projects?</summary>
550
- <p>Yes, generated audio files can be downloaded and used in your own videos, presentations, apps, and other projects.</p>
551
- </details>
552
-
553
- <details class="faq-item">
554
- <summary>Is my text stored or shared with anyone?</summary>
555
- <p>No. Text you submit is sent only to generate the audio file and is not stored on our servers afterwards. See our <a href="/privacy">Privacy Policy</a> for details.</p>
556
- </details>
557
- </section>
558
-
559
- </div>
560
-
561
- <footer>
562
- <div class="wrap">
563
- <div class="footer-grid">
564
- <div class="footer-col">
565
- <h4>Myanmar Voice Studio</h4>
566
- <a href="/">Home</a>
567
- <a href="#tool">Text to Speech Tool</a>
568
- <a href="#how-it-works">How It Works</a>
569
- </div>
570
- <div class="footer-col">
571
- <h4>Resources</h4>
572
- <a href="#faq">FAQ</a>
573
- <a href="/about">About</a>
574
- </div>
575
- <div class="footer-col">
576
- <h4>Legal</h4>
577
- <a href="/privacy">Privacy Policy</a>
578
- <a href="/terms">Terms of Use</a>
579
- </div>
580
- </div>
581
- <div class="footer-bottom">
582
- &copy; 2026 Myanmar Voice Studio. Voices powered by neural text-to-speech technology.
583
- </div>
584
  </div>
585
- </footer>
586
 
587
- <script src="/app.js"></script>
 
 
588
 
589
- <!-- Hamburger toggle script -->
590
  <script>
591
- (function(){
592
- var btn = document.getElementById('navToggle');
593
- var menu = document.getElementById('navLinks');
594
- if(!btn || !menu) return;
595
- btn.addEventListener('click', function(){
596
- var open = menu.classList.toggle('open');
597
- btn.classList.toggle('open', open);
598
- btn.setAttribute('aria-expanded', open);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
599
  });
600
- // Close on link click (mobile)
601
- menu.querySelectorAll('a').forEach(function(a){
602
- a.addEventListener('click', function(){
603
- menu.classList.remove('open');
604
- btn.classList.remove('open');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
605
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
606
  });
607
- })();
608
  </script>
609
-
610
  </body>
611
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>အသံစာပေVoice Studio</title>
 
 
 
 
7
 
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Noto+Sans+Myanmar:wght@400;500;600;700&display=swap" rel="stylesheet">
 
 
 
 
11
 
12
  <style>
13
+ :root{
14
+ --bg:#FFFFFF;
15
+ --surface:#FFFFFF;
16
+ --surface-2:#F5F6F8;
17
+ --ink:#16181D;
18
+ --ink-soft:#7B828F;
19
+ --accent:#2563EB;
20
+ --accent-soft:#EAF0FE;
21
+ --line:#E7E9EE;
22
+ --radius:14px;
23
+ --sans:'Inter', sans-serif;
24
+ --mm:'Noto Sans Myanmar', sans-serif;
25
+ }
26
+
27
+ *{ box-sizing:border-box; }
28
+
29
+ body{
30
+ margin:0;
31
+ background:var(--bg);
32
+ color:var(--ink);
33
+ font-family:var(--mm);
34
+ line-height:1.6;
35
+ -webkit-font-smoothing:antialiased;
36
+ }
37
+
38
+ .wrap{
39
+ max-width:640px;
40
+ margin:0 auto;
41
+ padding:0 18px 48px;
42
+ }
43
+
44
+ /* ---------- Header ---------- */
45
+ header{
46
+ padding:32px 0 20px;
47
+ text-align:center;
48
+ }
49
+ .eyebrow{
50
+ font-family:var(--sans);
51
+ font-weight:600;
52
+ font-size:12px;
53
+ letter-spacing:.14em;
54
+ text-transform:uppercase;
55
+ color:var(--accent);
56
+ margin:0 0 8px;
57
+ }
58
+ h1{
59
+ font-family:var(--mm);
60
+ font-weight:700;
61
+ font-size:clamp(24px,6vw,32px);
62
+ margin:0 0 8px;
63
+ }
64
+ .tagline{
65
+ color:var(--ink-soft);
66
+ font-size:14px;
67
+ margin:0;
68
+ }
69
+
70
+ .wave{
71
+ display:flex;
72
+ align-items:center;
73
+ justify-content:center;
74
+ gap:4px;
75
+ height:28px;
76
+ margin:18px auto 0;
77
+ }
78
+ .wave .bar{
79
+ width:4px;
80
+ border-radius:3px;
81
+ background:var(--accent);
82
+ height:8px;
83
+ opacity:.35;
84
+ animation:breathe 2.6s ease-in-out infinite;
85
+ transition:height .12s ease;
86
+ }
87
+ @keyframes breathe{
88
+ 0%, 100% { height:6px; opacity:.3; }
89
+ 50% { height:22px; opacity:.7; }
90
+ }
91
+ .wave.is-active .bar{ animation:none; opacity:1; }
92
+
93
+ /* ---------- Card ---------- */
94
+ .panel{
95
+ background:var(--surface);
96
+ border:1px solid var(--line);
97
+ border-radius:var(--radius);
98
+ padding:18px;
99
+ margin-top:16px;
100
+ }
101
+
102
+ .panel h2{
103
+ font-family:var(--sans);
104
+ font-weight:600;
105
+ font-size:12px;
106
+ letter-spacing:.1em;
107
+ text-transform:uppercase;
108
+ color:var(--ink-soft);
109
+ margin:0 0 12px;
110
+ }
111
+
112
+ textarea{
113
+ width:100%;
114
+ min-height:140px;
115
+ resize:vertical;
116
+ border:1px solid var(--line);
117
+ border-radius:10px;
118
+ background:var(--surface-2);
119
+ padding:14px 16px;
120
+ font-family:var(--mm);
121
+ font-size:16px;
122
+ line-height:1.7;
123
+ color:var(--ink);
124
+ outline:none;
125
+ }
126
+ textarea:focus{ border-color:var(--accent); background:var(--surface); }
127
+ textarea::placeholder{ color:var(--ink-soft); }
128
+
129
+ .char-count{
130
+ text-align:right;
131
+ font-size:12px;
132
+ color:var(--ink-soft);
133
+ margin-top:6px;
134
+ font-family:var(--sans);
135
+ }
136
+ .char-count.over{ color:#D14343; }
137
+
138
+ /* ---------- Selects ---------- */
139
+ .field{ margin-bottom:14px; }
140
+ .field:last-child{ margin-bottom:0; }
141
+ .field label{
142
+ display:block;
143
+ font-family:var(--sans);
144
+ font-size:12px;
145
+ font-weight:600;
146
+ letter-spacing:.04em;
147
+ color:var(--ink-soft);
148
+ margin-bottom:6px;
149
+ }
150
+ select{
151
+ width:100%;
152
+ border:1px solid var(--line);
153
+ border-radius:10px;
154
+ background:var(--surface-2);
155
+ padding:12px 14px;
156
+ font-family:var(--mm);
157
+ font-size:15px;
158
+ color:var(--ink);
159
+ outline:none;
160
+ appearance:none;
161
+ background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="14" height="9" viewBox="0 0 14 9"><path d="M1 1l6 6 6-6" stroke="%237B828F" stroke-width="1.6" fill="none" fill-rule="evenodd"/></svg>');
162
+ background-repeat:no-repeat;
163
+ background-position:right 14px center;
164
+ padding-right:34px;
165
+ }
166
+ select:focus{ border-color:var(--accent); background-color:var(--surface); }
167
+
168
+ .lang-search{
169
+ width:100%;
170
+ border:1px solid var(--line);
171
+ border-radius:10px;
172
+ background:var(--surface-2);
173
+ padding:10px 14px;
174
+ font-family:var(--sans);
175
+ font-size:14px;
176
+ color:var(--ink);
177
+ outline:none;
178
+ margin-bottom:8px;
179
+ }
180
+ .lang-search:focus{ border-color:var(--accent); background:var(--surface); }
181
+
182
+ .row2{
183
+ display:grid;
184
+ grid-template-columns:1fr 1fr;
185
+ gap:12px;
186
+ }
187
+ @media (max-width:420px){
188
+ .row2{ grid-template-columns:1fr; }
189
+ }
190
+
191
+ /* ---------- Sliders ---------- */
192
+ .controls{
193
+ display:grid;
194
+ grid-template-columns:1fr 1fr;
195
+ gap:16px;
196
+ margin-top:16px;
197
+ }
198
+ .control label{
199
+ display:flex;
200
+ justify-content:space-between;
201
+ font-size:12px;
202
+ color:var(--ink-soft);
203
+ margin-bottom:6px;
204
+ font-family:var(--sans);
205
+ font-weight:600;
206
+ }
207
+ .control label span.val{
208
+ font-family:var(--mm);
209
+ color:var(--ink);
210
+ font-weight:600;
211
+ }
212
+ input[type="range"]{
213
+ width:100%;
214
+ accent-color:var(--accent);
215
+ }
216
+
217
+ @media (max-width:480px){
218
+ .controls{ grid-template-columns:1fr; }
219
+ }
220
+
221
+ /* ---------- Button ---------- */
222
+ .actions{
223
+ margin-top:18px;
224
+ display:flex;
225
+ gap:10px;
226
+ flex-wrap:wrap;
227
+ }
228
+ button{
229
+ font-family:var(--mm);
230
+ font-size:15px;
231
+ font-weight:600;
232
+ border:none;
233
+ border-radius:10px;
234
+ padding:13px 22px;
235
+ cursor:pointer;
236
+ transition:opacity .15s ease, transform .05s ease;
237
+ }
238
+ button:active{ transform:scale(.98); }
239
+ button:disabled{ opacity:.5; cursor:wait; }
240
+
241
+ .btn-primary{
242
+ background:var(--accent);
243
+ color:#fff;
244
+ flex:1;
245
+ }
246
+ .btn-primary:hover:not(:disabled){ opacity:.9; }
247
+
248
+ .btn-secondary{
249
+ background:var(--surface-2);
250
+ color:var(--ink);
251
+ border:1px solid var(--line);
252
+ }
253
+
254
+ /* ---------- Output ---------- */
255
+ .output{
256
+ margin-top:16px;
257
+ display:none;
258
+ }
259
+ .output.visible{ display:block; }
260
+
261
+ .output audio{
262
+ width:100%;
263
+ margin-top:12px;
264
+ }
265
+
266
+ .status{
267
+ font-family:var(--sans);
268
+ font-size:13px;
269
+ color:var(--ink-soft);
270
+ margin-top:10px;
271
+ min-height:18px;
272
+ }
273
+ .status.error{ color:#D14343; }
274
+
275
+ footer{
276
+ text-align:center;
277
+ margin-top:32px;
278
+ font-size:12px;
279
+ color:var(--ink-soft);
280
+ font-family:var(--sans);
281
+ }
282
  </style>
283
  </head>
284
  <body>
285
 
 
 
286
  <div class="wrap">
287
+ <header>
288
+ <p class="eyebrow">Voice Studio · Free Text-to-Speech</p>
289
+ <h1>အသံ Studio</h1>
290
+ <p class="tagline">ဘာသာစကား 140+ မျိုးဖြင့် စာသားကို အသံအဖြစ် ပြောင်းလဲပါ</p>
291
+ <div class="wave" id="wave"></div>
292
+ </header>
293
+
294
+ <!-- Text input -->
295
+ <section class="panel">
296
+ <h2>Script</h2>
297
+ <textarea id="text" placeholder="စာသား ဒီနေရာတွင် ရိုက်ထည့်ပါ..." maxlength="3000"></textarea>
298
+ <div class="char-count" id="charCount">0 / 3000</div>
299
+ </section>
300
+
301
+ <!-- Voice + settings -->
302
+ <section class="panel">
303
+ <h2>Voice</h2>
304
+
305
+ <div class="field">
306
+ <label>ဘာသာစကား ရှာရန် (Search language)</label>
307
+ <input type="text" class="lang-search" id="langSearch" placeholder="e.g. Myanmar, English, Thai...">
308
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
 
310
+ <div class="row2">
311
+ <div class="field">
312
+ <label>ဘာသာစကား (Language)</label>
313
+ <select id="langSelect"></select>
314
+ </div>
315
+ <div class="field">
316
+ <label>အသံ (Voice)</label>
317
+ <select id="voiceSelect"></select>
318
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  </div>
320
 
321
  <div class="controls">
322
  <div class="control">
323
+ <label>အသံနှုန်း (Speed) <span class="val" id="rateVal">+0%</span></label>
324
  <input type="range" id="rate" min="-50" max="50" value="0" step="5">
325
  </div>
326
  <div class="control">
327
+ <label>အသံအနိမ့်အမြင့် (Pitch) <span class="val" id="pitchVal">+0Hz</span></label>
328
  <input type="range" id="pitch" min="-50" max="50" value="0" step="5">
329
  </div>
330
  </div>
331
 
332
  <div class="actions">
333
+ <button class="btn-primary" id="generateBtn">အသံ ထုတ်မည်</button>
334
  </div>
335
 
336
  <div class="status" id="status"></div>
 
338
  <div class="output" id="output">
339
  <audio id="player" controls></audio>
340
  <div class="actions">
341
+ <a class="btn-secondary" id="downloadLink" download="voice.mp3" style="text-decoration:none; display:inline-block; text-align:center;">ဒေါင်းလုဒ် (mp3)</a>
342
  </div>
343
  </div>
344
+ </section>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  </div>
 
346
 
347
+ <footer>
348
+ Powered by Edge TTS · Free &amp; Open Voice Studio
349
+ </footer>
350
 
 
351
  <script>
352
+ // ---- Build signature waveform ----
353
+ const wave = document.getElementById('wave');
354
+ const BAR_COUNT = 20;
355
+ for (let i = 0; i < BAR_COUNT; i++) {
356
+ const bar = document.createElement('div');
357
+ bar.className = 'bar';
358
+ bar.style.animationDelay = (i * 0.06) + 's';
359
+ wave.appendChild(bar);
360
+ }
361
+ const bars = wave.querySelectorAll('.bar');
362
+
363
+ function setWaveActive(active) {
364
+ wave.classList.toggle('is-active', active);
365
+ if (!active) {
366
+ bars.forEach(b => b.style.height = '');
367
+ return;
368
+ }
369
+ }
370
+
371
+ let waveInterval = null;
372
+ function startWaveAnimation() {
373
+ setWaveActive(true);
374
+ waveInterval = setInterval(() => {
375
+ bars.forEach(b => {
376
+ const h = 6 + Math.random() * 22;
377
+ b.style.height = h + 'px';
378
+ });
379
+ }, 110);
380
+ }
381
+ function stopWaveAnimation() {
382
+ if (waveInterval) clearInterval(waveInterval);
383
+ waveInterval = null;
384
+ setWaveActive(false);
385
+ }
386
+
387
+ // ---- Sliders ----
388
+ const rate = document.getElementById('rate');
389
+ const pitch = document.getElementById('pitch');
390
+ const rateVal = document.getElementById('rateVal');
391
+ const pitchVal = document.getElementById('pitchVal');
392
+
393
+ function fmt(v, unit) {
394
+ const n = Number(v);
395
+ return (n >= 0 ? '+' : '') + n + unit;
396
+ }
397
+ rate.addEventListener('input', () => rateVal.textContent = fmt(rate.value, '%'));
398
+ pitch.addEventListener('input', () => pitchVal.textContent = fmt(pitch.value, 'Hz'));
399
+
400
+ // ---- Character counter ----
401
+ const text = document.getElementById('text');
402
+ const charCount = document.getElementById('charCount');
403
+ text.addEventListener('input', () => {
404
+ const len = text.value.length;
405
+ charCount.textContent = `${len} / 3000`;
406
+ charCount.classList.toggle('over', len > 3000);
407
  });
408
+
409
+ // ---- Voices: fetch grouped by language, populate selects ----
410
+ const langSelect = document.getElementById('langSelect');
411
+ const voiceSelect = document.getElementById('voiceSelect');
412
+ const langSearch = document.getElementById('langSearch');
413
+
414
+ let voiceData = {}; // { language: [ {id, name, gender}, ... ] }
415
+ let languages = []; // sorted language names
416
+ const DEFAULT_LANG = 'Myanmar (Burmese)';
417
+ const DEFAULT_VOICE_ID = 'my-MM-NilarNeural';
418
+
419
+ function populateVoicesFor(lang) {
420
+ voiceSelect.innerHTML = '';
421
+ const list = voiceData[lang] || [];
422
+ list.forEach(v => {
423
+ const opt = document.createElement('option');
424
+ opt.value = v.id;
425
+ opt.textContent = `${v.name} (${v.gender})`;
426
+ voiceSelect.appendChild(opt);
427
  });
428
+ }
429
+
430
+ function populateLanguages(filter) {
431
+ langSelect.innerHTML = '';
432
+ const f = (filter || '').trim().toLowerCase();
433
+ const filtered = f ? languages.filter(l => l.toLowerCase().includes(f)) : languages;
434
+ filtered.forEach(lang => {
435
+ const opt = document.createElement('option');
436
+ opt.value = lang;
437
+ opt.textContent = lang;
438
+ langSelect.appendChild(opt);
439
+ });
440
+ if (filtered.length) {
441
+ const preferred = filtered.includes(DEFAULT_LANG) && !f ? DEFAULT_LANG : filtered[0];
442
+ langSelect.value = preferred;
443
+ populateVoicesFor(preferred);
444
+ } else {
445
+ voiceSelect.innerHTML = '';
446
+ }
447
+ }
448
+
449
+ langSearch.addEventListener('input', () => populateLanguages(langSearch.value));
450
+ langSelect.addEventListener('change', () => populateVoicesFor(langSelect.value));
451
+
452
+ async function loadVoices() {
453
+ try {
454
+ const res = await fetch('/api/voices');
455
+ voiceData = await res.json();
456
+ languages = Object.keys(voiceData);
457
+ populateLanguages('');
458
+ // select default Myanmar voice if present
459
+ if (voiceData[DEFAULT_LANG] && voiceData[DEFAULT_LANG].some(v => v.id === DEFAULT_VOICE_ID)) {
460
+ langSelect.value = DEFAULT_LANG;
461
+ populateVoicesFor(DEFAULT_LANG);
462
+ voiceSelect.value = DEFAULT_VOICE_ID;
463
+ }
464
+ } catch (e) {
465
+ status.textContent = 'ဘာသာစကား စာရင်း ရယူခြင်း မအောင်မြင်ပါ။';
466
+ status.classList.add('error');
467
+ }
468
+ }
469
+ loadVoices();
470
+
471
+ // ---- Generate ----
472
+ const generateBtn = document.getElementById('generateBtn');
473
+ const status = document.getElementById('status');
474
+ const output = document.getElementById('output');
475
+ const player = document.getElementById('player');
476
+ const downloadLink = document.getElementById('downloadLink');
477
+
478
+ let currentObjectUrl = null;
479
+
480
+ generateBtn.addEventListener('click', async () => {
481
+ const value = text.value.trim();
482
+ if (!value) {
483
+ status.textContent = 'စာသား ထည့်ပေးပါ။';
484
+ status.classList.add('error');
485
+ return;
486
+ }
487
+
488
+ const voice = voiceSelect.value;
489
+ if (!voice) {
490
+ status.textContent = 'အသံတစ်ခု ရွေးပေးပါ။';
491
+ status.classList.add('error');
492
+ return;
493
+ }
494
+
495
+ status.classList.remove('error');
496
+ status.textContent = 'အသံထုတ်နေပါသည်...';
497
+ generateBtn.disabled = true;
498
+ startWaveAnimation();
499
+
500
+ try {
501
+ const res = await fetch('/api/tts', {
502
+ method: 'POST',
503
+ headers: { 'Content-Type': 'application/json' },
504
+ body: JSON.stringify({
505
+ text: value,
506
+ voice: voice,
507
+ rate: Number(rate.value),
508
+ pitch: Number(pitch.value)
509
+ })
510
+ });
511
+
512
+ if (!res.ok) {
513
+ const err = await res.json().catch(() => ({}));
514
+ throw new Error(err.error || 'အသံထုတ်ခြင်း မအောင်မြင်ပါ');
515
+ }
516
+
517
+ const blob = await res.blob();
518
+ if (currentObjectUrl) URL.revokeObjectURL(currentObjectUrl);
519
+ currentObjectUrl = URL.createObjectURL(blob);
520
+
521
+ player.src = currentObjectUrl;
522
+ downloadLink.href = currentObjectUrl;
523
+ output.classList.add('visible');
524
+ status.textContent = 'အသံ အောင်မြင်စွာ ထုတ်ပြီးပါပြီ။';
525
+ player.play().catch(() => {});
526
+ } catch (e) {
527
+ status.textContent = e.message;
528
+ status.classList.add('error');
529
+ } finally {
530
+ generateBtn.disabled = false;
531
+ stopWaveAnimation();
532
+ }
533
  });
 
534
  </script>
 
535
  </body>
536
  </html>
voices_data.py ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Comprehensive Edge TTS voice catalogue (ShortName -> language label, gender)
2
+ # Format: (short_name, language_display_name, gender)
3
+ VOICE_LIST = [
4
+ ("af-ZA-AdriNeural", "Afrikaans", "Female"),
5
+ ("af-ZA-WillemNeural", "Afrikaans", "Male"),
6
+ ("am-ET-AmehaNeural", "Amharic", "Male"),
7
+ ("am-ET-MekdesNeural", "Amharic", "Female"),
8
+ ("ar-AE-FatimaNeural", "Arabic (UAE)", "Female"),
9
+ ("ar-AE-HamdanNeural", "Arabic (UAE)", "Male"),
10
+ ("ar-BH-AliNeural", "Arabic (Bahrain)", "Male"),
11
+ ("ar-BH-LailaNeural", "Arabic (Bahrain)", "Female"),
12
+ ("ar-DZ-AminaNeural", "Arabic (Algeria)", "Female"),
13
+ ("ar-DZ-IsmaelNeural", "Arabic (Algeria)", "Male"),
14
+ ("ar-EG-SalmaNeural", "Arabic (Egypt)", "Female"),
15
+ ("ar-EG-ShakirNeural", "Arabic (Egypt)", "Male"),
16
+ ("ar-IQ-BasselNeural", "Arabic (Iraq)", "Male"),
17
+ ("ar-IQ-RanaNeural", "Arabic (Iraq)", "Female"),
18
+ ("ar-JO-SanaNeural", "Arabic (Jordan)", "Female"),
19
+ ("ar-JO-TaimNeural", "Arabic (Jordan)", "Male"),
20
+ ("ar-KW-FahedNeural", "Arabic (Kuwait)", "Male"),
21
+ ("ar-KW-NouraNeural", "Arabic (Kuwait)", "Female"),
22
+ ("ar-LB-LaylaNeural", "Arabic (Lebanon)", "Female"),
23
+ ("ar-LB-RamiNeural", "Arabic (Lebanon)", "Male"),
24
+ ("ar-LY-ImanNeural", "Arabic (Libya)", "Female"),
25
+ ("ar-LY-OmarNeural", "Arabic (Libya)", "Male"),
26
+ ("ar-MA-JamalNeural", "Arabic (Morocco)", "Male"),
27
+ ("ar-MA-MounaNeural", "Arabic (Morocco)", "Female"),
28
+ ("ar-OM-AbdullahNeural", "Arabic (Oman)", "Male"),
29
+ ("ar-OM-AyshaNeural", "Arabic (Oman)", "Female"),
30
+ ("ar-QA-AmalNeural", "Arabic (Qatar)", "Female"),
31
+ ("ar-QA-MoazNeural", "Arabic (Qatar)", "Male"),
32
+ ("ar-SA-HamedNeural", "Arabic (Saudi Arabia)", "Male"),
33
+ ("ar-SA-ZariyahNeural", "Arabic (Saudi Arabia)", "Female"),
34
+ ("ar-SY-AmanyNeural", "Arabic (Syria)", "Female"),
35
+ ("ar-SY-LaithNeural", "Arabic (Syria)", "Male"),
36
+ ("ar-TN-HediNeural", "Arabic (Tunisia)", "Male"),
37
+ ("ar-TN-ReemNeural", "Arabic (Tunisia)", "Female"),
38
+ ("ar-YE-MaryamNeural", "Arabic (Yemen)", "Female"),
39
+ ("ar-YE-SalehNeural", "Arabic (Yemen)", "Male"),
40
+ ("az-AZ-BabekNeural", "Azerbaijani", "Male"),
41
+ ("az-AZ-BanuNeural", "Azerbaijani", "Female"),
42
+ ("bg-BG-BorislavNeural", "Bulgarian", "Male"),
43
+ ("bg-BG-KalinaNeural", "Bulgarian", "Female"),
44
+ ("bn-BD-NabanitaNeural", "Bengali (Bangladesh)", "Female"),
45
+ ("bn-BD-PradeepNeural", "Bengali (Bangladesh)", "Male"),
46
+ ("bn-IN-BashkarNeural", "Bengali (India)", "Male"),
47
+ ("bn-IN-TanishaaNeural", "Bengali (India)", "Female"),
48
+ ("bs-BA-GoranNeural", "Bosnian", "Male"),
49
+ ("bs-BA-VesnaNeural", "Bosnian", "Female"),
50
+ ("ca-ES-AlbaNeural", "Catalan", "Female"),
51
+ ("ca-ES-EnricNeural", "Catalan", "Male"),
52
+ ("ca-ES-JoanaNeural", "Catalan", "Female"),
53
+ ("cs-CZ-AntoninNeural", "Czech", "Male"),
54
+ ("cs-CZ-VlastaNeural", "Czech", "Female"),
55
+ ("cy-GB-AledNeural", "Welsh", "Male"),
56
+ ("cy-GB-NiaNeural", "Welsh", "Female"),
57
+ ("da-DK-ChristelNeural", "Danish", "Female"),
58
+ ("da-DK-JeppeNeural", "Danish", "Male"),
59
+ ("de-AT-IngridNeural", "German (Austria)", "Female"),
60
+ ("de-AT-JonasNeural", "German (Austria)", "Male"),
61
+ ("de-CH-JanNeural", "German (Switzerland)", "Male"),
62
+ ("de-CH-LeniNeural", "German (Switzerland)", "Female"),
63
+ ("de-DE-AmalaNeural", "German", "Female"),
64
+ ("de-DE-ConradNeural", "German", "Male"),
65
+ ("de-DE-FlorianMultilingualNeural", "German", "Male"),
66
+ ("de-DE-KatjaNeural", "German", "Female"),
67
+ ("de-DE-KillianNeural", "German", "Male"),
68
+ ("de-DE-SeraphinaMultilingualNeural", "German", "Female"),
69
+ ("el-GR-AthinaNeural", "Greek", "Female"),
70
+ ("el-GR-NestorasNeural", "Greek", "Male"),
71
+ ("en-AU-NatashaNeural", "English (Australia)", "Female"),
72
+ ("en-AU-WilliamNeural", "English (Australia)", "Male"),
73
+ ("en-CA-ClaraNeural", "English (Canada)", "Female"),
74
+ ("en-CA-LiamNeural", "English (Canada)", "Male"),
75
+ ("en-GB-LibbyNeural", "English (UK)", "Female"),
76
+ ("en-GB-MaisieNeural", "English (UK)", "Female"),
77
+ ("en-GB-RyanNeural", "English (UK)", "Male"),
78
+ ("en-GB-SoniaNeural", "English (UK)", "Female"),
79
+ ("en-GB-ThomasNeural", "English (UK)", "Male"),
80
+ ("en-HK-SamNeural", "English (Hong Kong)", "Male"),
81
+ ("en-HK-YanNeural", "English (Hong Kong)", "Female"),
82
+ ("en-IE-ConnorNeural", "English (Ireland)", "Male"),
83
+ ("en-IE-EmilyNeural", "English (Ireland)", "Female"),
84
+ ("en-IN-NeerjaNeural", "English (India)", "Female"),
85
+ ("en-IN-PrabhatNeural", "English (India)", "Male"),
86
+ ("en-KE-AsiliaNeural", "English (Kenya)", "Female"),
87
+ ("en-KE-ChilembaNeural", "English (Kenya)", "Male"),
88
+ ("en-NG-AbeoNeural", "English (Nigeria)", "Male"),
89
+ ("en-NG-EzinneNeural", "English (Nigeria)", "Female"),
90
+ ("en-NZ-MitchellNeural", "English (New Zealand)", "Male"),
91
+ ("en-NZ-MollyNeural", "English (New Zealand)", "Female"),
92
+ ("en-PH-JamesNeural", "English (Philippines)", "Male"),
93
+ ("en-PH-RosaNeural", "English (Philippines)", "Female"),
94
+ ("en-SG-LunaNeural", "English (Singapore)", "Female"),
95
+ ("en-SG-WayneNeural", "English (Singapore)", "Male"),
96
+ ("en-TZ-ElimuNeural", "English (Tanzania)", "Male"),
97
+ ("en-TZ-ImaniNeural", "English (Tanzania)", "Female"),
98
+ ("en-US-AnaNeural", "English (US)", "Female"),
99
+ ("en-US-AndrewNeural", "English (US)", "Male"),
100
+ ("en-US-AndrewMultilingualNeural", "English (US)", "Male"),
101
+ ("en-US-AriaNeural", "English (US)", "Female"),
102
+ ("en-US-AvaNeural", "English (US)", "Female"),
103
+ ("en-US-AvaMultilingualNeural", "English (US)", "Female"),
104
+ ("en-US-BrianNeural", "English (US)", "Male"),
105
+ ("en-US-BrianMultilingualNeural", "English (US)", "Male"),
106
+ ("en-US-ChristopherNeural", "English (US)", "Male"),
107
+ ("en-US-EmmaNeural", "English (US)", "Female"),
108
+ ("en-US-EmmaMultilingualNeural", "English (US)", "Female"),
109
+ ("en-US-EricNeural", "English (US)", "Male"),
110
+ ("en-US-GuyNeural", "English (US)", "Male"),
111
+ ("en-US-JennyNeural", "English (US)", "Female"),
112
+ ("en-US-MichelleNeural", "English (US)", "Female"),
113
+ ("en-US-RogerNeural", "English (US)", "Male"),
114
+ ("en-US-SteffanNeural", "English (US)", "Male"),
115
+ ("en-ZA-LeahNeural", "English (South Africa)", "Female"),
116
+ ("en-ZA-LukeNeural", "English (South Africa)", "Male"),
117
+ ("es-AR-ElenaNeural", "Spanish (Argentina)", "Female"),
118
+ ("es-AR-TomasNeural", "Spanish (Argentina)", "Male"),
119
+ ("es-BO-MarceloNeural", "Spanish (Bolivia)", "Male"),
120
+ ("es-BO-SofiaNeural", "Spanish (Bolivia)", "Female"),
121
+ ("es-CL-CatalinaNeural", "Spanish (Chile)", "Female"),
122
+ ("es-CL-LorenzoNeural", "Spanish (Chile)", "Male"),
123
+ ("es-CO-GonzaloNeural", "Spanish (Colombia)", "Male"),
124
+ ("es-CO-SalomeNeural", "Spanish (Colombia)", "Female"),
125
+ ("es-CR-JuanNeural", "Spanish (Costa Rica)", "Male"),
126
+ ("es-CR-MariaNeural", "Spanish (Costa Rica)", "Female"),
127
+ ("es-CU-BelkysNeural", "Spanish (Cuba)", "Female"),
128
+ ("es-CU-ManuelNeural", "Spanish (Cuba)", "Male"),
129
+ ("es-DO-EmilioNeural", "Spanish (Dominican Republic)", "Male"),
130
+ ("es-DO-RamonaNeural", "Spanish (Dominican Republic)", "Female"),
131
+ ("es-EC-AndreaNeural", "Spanish (Ecuador)", "Female"),
132
+ ("es-EC-LuisNeural", "Spanish (Ecuador)", "Male"),
133
+ ("es-ES-AlvaroNeural", "Spanish (Spain)", "Male"),
134
+ ("es-ES-ElviraNeural", "Spanish (Spain)", "Female"),
135
+ ("es-ES-XimenaNeural", "Spanish (Spain)", "Female"),
136
+ ("es-GQ-JavierNeural", "Spanish (Equatorial Guinea)", "Male"),
137
+ ("es-GQ-TeresaNeural", "Spanish (Equatorial Guinea)", "Female"),
138
+ ("es-GT-AndresNeural", "Spanish (Guatemala)", "Male"),
139
+ ("es-GT-MartaNeural", "Spanish (Guatemala)", "Female"),
140
+ ("es-HN-CarlosNeural", "Spanish (Honduras)", "Male"),
141
+ ("es-HN-KarlaNeural", "Spanish (Honduras)", "Female"),
142
+ ("es-MX-DaliaNeural", "Spanish (Mexico)", "Female"),
143
+ ("es-MX-JorgeNeural", "Spanish (Mexico)", "Male"),
144
+ ("es-NI-FedericoNeural", "Spanish (Nicaragua)", "Male"),
145
+ ("es-NI-YolandaNeural", "Spanish (Nicaragua)", "Female"),
146
+ ("es-PA-MargaritaNeural", "Spanish (Panama)", "Female"),
147
+ ("es-PA-RobertoNeural", "Spanish (Panama)", "Male"),
148
+ ("es-PE-AlexNeural", "Spanish (Peru)", "Male"),
149
+ ("es-PE-CamilaNeural", "Spanish (Peru)", "Female"),
150
+ ("es-PR-KarinaNeural", "Spanish (Puerto Rico)", "Female"),
151
+ ("es-PR-VictorNeural", "Spanish (Puerto Rico)", "Male"),
152
+ ("es-PY-MarioNeural", "Spanish (Paraguay)", "Male"),
153
+ ("es-PY-TaniaNeural", "Spanish (Paraguay)", "Female"),
154
+ ("es-SV-LorenaNeural", "Spanish (El Salvador)", "Female"),
155
+ ("es-SV-RodrigoNeural", "Spanish (El Salvador)", "Male"),
156
+ ("es-US-AlonsoNeural", "Spanish (US)", "Male"),
157
+ ("es-US-PalomaNeural", "Spanish (US)", "Female"),
158
+ ("es-UY-MateoNeural", "Spanish (Uruguay)", "Male"),
159
+ ("es-UY-ValentinaNeural", "Spanish (Uruguay)", "Female"),
160
+ ("es-VE-PaolaNeural", "Spanish (Venezuela)", "Female"),
161
+ ("es-VE-SebastianNeural", "Spanish (Venezuela)", "Male"),
162
+ ("et-EE-AnuNeural", "Estonian", "Female"),
163
+ ("et-EE-KertNeural", "Estonian", "Male"),
164
+ ("eu-ES-AinhoaNeural", "Basque", "Female"),
165
+ ("eu-ES-AnderNeural", "Basque", "Male"),
166
+ ("fa-IR-DilaraNeural", "Persian", "Female"),
167
+ ("fa-IR-FaridNeural", "Persian", "Male"),
168
+ ("fi-FI-HarriNeural", "Finnish", "Male"),
169
+ ("fi-FI-NooraNeural", "Finnish", "Female"),
170
+ ("fil-PH-AngeloNeural", "Filipino", "Male"),
171
+ ("fil-PH-BlessicaNeural", "Filipino", "Female"),
172
+ ("fr-BE-CharlineNeural", "French (Belgium)", "Female"),
173
+ ("fr-BE-GerardNeural", "French (Belgium)", "Male"),
174
+ ("fr-CA-AntoineNeural", "French (Canada)", "Male"),
175
+ ("fr-CA-JeanNeural", "French (Canada)", "Male"),
176
+ ("fr-CA-SylvieNeural", "French (Canada)", "Female"),
177
+ ("fr-CA-ThierryNeural", "French (Canada)", "Male"),
178
+ ("fr-CH-ArianeNeural", "French (Switzerland)", "Female"),
179
+ ("fr-CH-FabriceNeural", "French (Switzerland)", "Male"),
180
+ ("fr-FR-DeniseNeural", "French", "Female"),
181
+ ("fr-FR-EloiseNeural", "French", "Female"),
182
+ ("fr-FR-HenriNeural", "French", "Male"),
183
+ ("fr-FR-RemyMultilingualNeural", "French", "Male"),
184
+ ("fr-FR-VivienneMultilingualNeural", "French", "Female"),
185
+ ("ga-IE-ColmNeural", "Irish", "Male"),
186
+ ("ga-IE-OrlaNeural", "Irish", "Female"),
187
+ ("gl-ES-RoiNeural", "Galician", "Male"),
188
+ ("gl-ES-SabelaNeural", "Galician", "Female"),
189
+ ("gu-IN-DhwaniNeural", "Gujarati", "Female"),
190
+ ("gu-IN-NiranjanNeural", "Gujarati", "Male"),
191
+ ("he-IL-AvriNeural", "Hebrew", "Male"),
192
+ ("he-IL-HilaNeural", "Hebrew", "Female"),
193
+ ("hi-IN-MadhurNeural", "Hindi", "Male"),
194
+ ("hi-IN-SwaraNeural", "Hindi", "Female"),
195
+ ("hr-HR-GabrijelaNeural", "Croatian", "Female"),
196
+ ("hr-HR-SreckoNeural", "Croatian", "Male"),
197
+ ("hu-HU-NoemiNeural", "Hungarian", "Female"),
198
+ ("hu-HU-TamasNeural", "Hungarian", "Male"),
199
+ ("id-ID-ArdiNeural", "Indonesian", "Male"),
200
+ ("id-ID-GadisNeural", "Indonesian", "Female"),
201
+ ("is-IS-GudrunNeural", "Icelandic", "Female"),
202
+ ("is-IS-GunnarNeural", "Icelandic", "Male"),
203
+ ("it-IT-DiegoNeural", "Italian", "Male"),
204
+ ("it-IT-ElsaNeural", "Italian", "Female"),
205
+ ("it-IT-GiuseppeNeural", "Italian", "Male"),
206
+ ("it-IT-GiuseppeMultilingualNeural", "Italian", "Male"),
207
+ ("it-IT-IsabellaNeural", "Italian", "Female"),
208
+ ("iu-Cans-CA-SiqiniqNeural", "Inuktitut (Syllabics)", "Female"),
209
+ ("iu-Cans-CA-TaqqiqNeural", "Inuktitut (Syllabics)", "Male"),
210
+ ("iu-Latn-CA-SiqiniqNeural", "Inuktitut (Latin)", "Female"),
211
+ ("iu-Latn-CA-TaqqiqNeural", "Inuktitut (Latin)", "Male"),
212
+ ("ja-JP-KeitaNeural", "Japanese", "Male"),
213
+ ("ja-JP-NanamiNeural", "Japanese", "Female"),
214
+ ("jv-ID-DimasNeural", "Javanese", "Male"),
215
+ ("jv-ID-SitiNeural", "Javanese", "Female"),
216
+ ("ka-GE-EkaNeural", "Georgian", "Female"),
217
+ ("ka-GE-GiorgiNeural", "Georgian", "Male"),
218
+ ("kk-KZ-AigulNeural", "Kazakh", "Female"),
219
+ ("kk-KZ-DauletNeural", "Kazakh", "Male"),
220
+ ("km-KH-PisethNeural", "Khmer", "Male"),
221
+ ("km-KH-SreymomNeural", "Khmer", "Female"),
222
+ ("kn-IN-GaganNeural", "Kannada", "Male"),
223
+ ("kn-IN-SapnaNeural", "Kannada", "Female"),
224
+ ("ko-KR-HyunsuNeural", "Korean", "Male"),
225
+ ("ko-KR-InJoonNeural", "Korean", "Male"),
226
+ ("ko-KR-SunHiNeural", "Korean", "Female"),
227
+ ("lo-LA-ChanthavongNeural", "Lao", "Male"),
228
+ ("lo-LA-KeomanyNeural", "Lao", "Female"),
229
+ ("lt-LT-LeonasNeural", "Lithuanian", "Male"),
230
+ ("lt-LT-OnaNeural", "Lithuanian", "Female"),
231
+ ("lv-LV-EveritaNeural", "Latvian", "Female"),
232
+ ("lv-LV-NilsNeural", "Latvian", "Male"),
233
+ ("mk-MK-AleksandarNeural", "Macedonian", "Male"),
234
+ ("mk-MK-MarijaNeural", "Macedonian", "Female"),
235
+ ("ml-IN-MidhunNeural", "Malayalam", "Male"),
236
+ ("ml-IN-SobhanaNeural", "Malayalam", "Female"),
237
+ ("mn-MN-BataaNeural", "Mongolian", "Male"),
238
+ ("mn-MN-YesuiNeural", "Mongolian", "Female"),
239
+ ("mr-IN-AarohiNeural", "Marathi", "Female"),
240
+ ("mr-IN-ManoharNeural", "Marathi", "Male"),
241
+ ("ms-MY-OsmanNeural", "Malay", "Male"),
242
+ ("ms-MY-YasminNeural", "Malay", "Female"),
243
+ ("mt-MT-GraceNeural", "Maltese", "Female"),
244
+ ("mt-MT-JosephNeural", "Maltese", "Male"),
245
+ ("my-MM-NilarNeural", "Myanmar (Burmese)", "Female"),
246
+ ("my-MM-ThihaNeural", "Myanmar (Burmese)", "Male"),
247
+ ("nb-NO-FinnNeural", "Norwegian", "Male"),
248
+ ("nb-NO-PernilleNeural", "Norwegian", "Female"),
249
+ ("ne-NP-HemkalaNeural", "Nepali", "Female"),
250
+ ("ne-NP-SagarNeural", "Nepali", "Male"),
251
+ ("nl-BE-ArnaudNeural", "Dutch (Belgium)", "Male"),
252
+ ("nl-BE-DenaNeural", "Dutch (Belgium)", "Female"),
253
+ ("nl-NL-ColetteNeural", "Dutch", "Female"),
254
+ ("nl-NL-FennaNeural", "Dutch", "Female"),
255
+ ("nl-NL-MaartenNeural", "Dutch", "Male"),
256
+ ("pl-PL-MarekNeural", "Polish", "Male"),
257
+ ("pl-PL-ZofiaNeural", "Polish", "Female"),
258
+ ("ps-AF-GulNawazNeural", "Pashto", "Male"),
259
+ ("ps-AF-LatifaNeural", "Pashto", "Female"),
260
+ ("pt-BR-AntonioNeural", "Portuguese (Brazil)", "Male"),
261
+ ("pt-BR-FranciscaNeural", "Portuguese (Brazil)", "Female"),
262
+ ("pt-BR-ThalitaNeural", "Portuguese (Brazil)", "Female"),
263
+ ("pt-BR-ThalitaMultilingualNeural", "Portuguese (Brazil)", "Female"),
264
+ ("pt-PT-DuarteNeural", "Portuguese (Portugal)", "Male"),
265
+ ("pt-PT-RaquelNeural", "Portuguese (Portugal)", "Female"),
266
+ ("ro-RO-AlinaNeural", "Romanian", "Female"),
267
+ ("ro-RO-EmilNeural", "Romanian", "Male"),
268
+ ("ru-RU-DmitryNeural", "Russian", "Male"),
269
+ ("ru-RU-SvetlanaNeural", "Russian", "Female"),
270
+ ("si-LK-SameeraNeural", "Sinhala", "Male"),
271
+ ("si-LK-ThiliniNeural", "Sinhala", "Female"),
272
+ ("sk-SK-LukasNeural", "Slovak", "Male"),
273
+ ("sk-SK-ViktoriaNeural", "Slovak", "Female"),
274
+ ("sl-SI-PetraNeural", "Slovenian", "Female"),
275
+ ("sl-SI-RokNeural", "Slovenian", "Male"),
276
+ ("so-SO-MuuseNeural", "Somali", "Male"),
277
+ ("so-SO-UbaxNeural", "Somali", "Female"),
278
+ ("sq-AL-AnilaNeural", "Albanian", "Female"),
279
+ ("sq-AL-IlirNeural", "Albanian", "Male"),
280
+ ("sr-RS-NicholasNeural", "Serbian", "Male"),
281
+ ("sr-RS-SophieNeural", "Serbian", "Female"),
282
+ ("su-ID-JajangNeural", "Sundanese", "Male"),
283
+ ("su-ID-TutiNeural", "Sundanese", "Female"),
284
+ ("sv-SE-MattiasNeural", "Swedish", "Male"),
285
+ ("sv-SE-SofieNeural", "Swedish", "Female"),
286
+ ("sw-KE-RafikiNeural", "Swahili (Kenya)", "Male"),
287
+ ("sw-KE-ZuriNeural", "Swahili (Kenya)", "Female"),
288
+ ("sw-TZ-DaudiNeural", "Swahili (Tanzania)", "Male"),
289
+ ("sw-TZ-RehemaNeural", "Swahili (Tanzania)", "Female"),
290
+ ("ta-IN-PallaviNeural", "Tamil (India)", "Female"),
291
+ ("ta-IN-ValluvarNeural", "Tamil (India)", "Male"),
292
+ ("ta-LK-KumarNeural", "Tamil (Sri Lanka)", "Male"),
293
+ ("ta-LK-SaranyaNeural", "Tamil (Sri Lanka)", "Female"),
294
+ ("ta-MY-KaniNeural", "Tamil (Malaysia)", "Female"),
295
+ ("ta-MY-SuryaNeural", "Tamil (Malaysia)", "Male"),
296
+ ("ta-SG-AnbuNeural", "Tamil (Singapore)", "Male"),
297
+ ("ta-SG-VenbaNeural", "Tamil (Singapore)", "Female"),
298
+ ("te-IN-MohanNeural", "Telugu", "Male"),
299
+ ("te-IN-ShrutiNeural", "Telugu", "Female"),
300
+ ("th-TH-NiwatNeural", "Thai", "Male"),
301
+ ("th-TH-PremwadeeNeural", "Thai", "Female"),
302
+ ("tr-TR-AhmetNeural", "Turkish", "Male"),
303
+ ("tr-TR-EmelNeural", "Turkish", "Female"),
304
+ ("uk-UA-OstapNeural", "Ukrainian", "Male"),
305
+ ("uk-UA-PolinaNeural", "Ukrainian", "Female"),
306
+ ("ur-IN-GulNeural", "Urdu (India)", "Female"),
307
+ ("ur-IN-SalmanNeural", "Urdu (India)", "Male"),
308
+ ("ur-PK-AsadNeural", "Urdu (Pakistan)", "Male"),
309
+ ("ur-PK-UzmaNeural", "Urdu (Pakistan)", "Female"),
310
+ ("uz-UZ-MadinaNeural", "Uzbek", "Female"),
311
+ ("uz-UZ-SardorNeural", "Uzbek", "Male"),
312
+ ("vi-VN-HoaiMyNeural", "Vietnamese", "Female"),
313
+ ("vi-VN-NamMinhNeural", "Vietnamese", "Male"),
314
+ ("zh-CN-XiaoxiaoNeural", "Chinese (Mandarin)", "Female"),
315
+ ("zh-CN-XiaoyiNeural", "Chinese (Mandarin)", "Female"),
316
+ ("zh-CN-YunjianNeural", "Chinese (Mandarin)", "Male"),
317
+ ("zh-CN-YunxiNeural", "Chinese (Mandarin)", "Male"),
318
+ ("zh-CN-YunxiaNeural", "Chinese (Mandarin)", "Male"),
319
+ ("zh-CN-YunyangNeural", "Chinese (Mandarin)", "Male"),
320
+ ("zh-CN-liaoning-XiaobeiNeural", "Chinese (Liaoning)", "Female"),
321
+ ("zh-CN-shaanxi-XiaoniNeural", "Chinese (Shaanxi)", "Female"),
322
+ ("zh-HK-HiuGaaiNeural", "Chinese (Cantonese, HK)", "Female"),
323
+ ("zh-HK-HiuMaanNeural", "Chinese (Cantonese, HK)", "Female"),
324
+ ("zh-HK-WanLungNeural", "Chinese (Cantonese, HK)", "Male"),
325
+ ("zh-TW-HsiaoChenNeural", "Chinese (Taiwan)", "Female"),
326
+ ("zh-TW-HsiaoYuNeural", "Chinese (Taiwan)", "Female"),
327
+ ("zh-TW-YunJheNeural", "Chinese (Taiwan)", "Male"),
328
+ ("zu-ZA-ThandoNeural", "Zulu", "Female"),
329
+ ("zu-ZA-ThembaNeural", "Zulu", "Male"),
330
+ ]