Khalil09 commited on
Commit
df1fe67
ยท
verified ยท
1 Parent(s): b40c4fe

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +439 -0
app.py ADDED
@@ -0,0 +1,439 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import edge_tts
3
+ import asyncio
4
+ import tempfile
5
+ import os
6
+ import json
7
+
8
+ # โ”€โ”€ Voice catalogue โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
9
+ VOICES = {
10
+ "๐ŸŒŸ Aria (US โ€“ Female)": "en-US-AriaNeural",
11
+ "๐ŸŽ™๏ธ Guy (US โ€“ Male)": "en-US-GuyNeural",
12
+ "โœจ Jenny (US โ€“ Female)": "en-US-JennyNeural",
13
+ "๐Ÿ”ฅ Davis (US โ€“ Male)": "en-US-DavisNeural",
14
+ "๐ŸŒŠ Jane (US โ€“ Female)": "en-US-JaneNeural",
15
+ "โšก Tony (US โ€“ Male)": "en-US-TonyNeural",
16
+ "๐ŸŒธ Sonia (UK โ€“ Female)": "en-GB-SoniaNeural",
17
+ "๐ŸŽฉ Ryan (UK โ€“ Male)": "en-GB-RyanNeural",
18
+ "๐Ÿ’ซ Libby (UK โ€“ Female)": "en-GB-LibbyNeural",
19
+ "๐ŸŒบ Natasha (AU โ€“ Female)": "en-AU-NatashaNeural",
20
+ "๐Ÿฆ˜ William (AU โ€“ Male)": "en-AU-WilliamNeural",
21
+ "๐Ÿ Clara (CA โ€“ Female)": "en-CA-ClaraNeural",
22
+ "๐ŸŒด Neerja (IN โ€“ Female)": "en-IN-NeerjaNeural",
23
+ "๐ŸŽต Prabhat (IN โ€“ Male)": "en-IN-PrabhatNeural",
24
+ }
25
+
26
+ PRESETS = {
27
+ "๐ŸŽ™๏ธ Podcast Host": {"rate": "+5%", "pitch": "-2Hz", "volume": "+10%"},
28
+ "๐Ÿ“ฐ News Anchor": {"rate": "+0%", "pitch": "+0Hz", "volume": "+5%"},
29
+ "๐Ÿง˜ Meditation": {"rate": "-20%", "pitch": "-5Hz", "volume": "-10%"},
30
+ "๐Ÿ“š Audiobook": {"rate": "-5%", "pitch": "+0Hz", "volume": "+0%"},
31
+ "๐Ÿค– AI Assistant": {"rate": "+10%", "pitch": "+5Hz", "volume": "+15%"},
32
+ "๐ŸŽฎ Game Narrator": {"rate": "+15%", "pitch": "-8Hz", "volume": "+20%"},
33
+ "๐Ÿ‘ถ Kids Story": {"rate": "-10%", "pitch": "+10Hz", "volume": "+5%"},
34
+ "๐Ÿ”ฌ Documentary": {"rate": "-3%", "pitch": "-3Hz", "volume": "+8%"},
35
+ }
36
+
37
+ # โ”€โ”€ TTS core โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
38
+ async def _synthesise(text: str, voice: str, rate: str, pitch: str, volume: str) -> str:
39
+ communicate = edge_tts.Communicate(
40
+ text=text, voice=voice,
41
+ rate=rate, pitch=pitch, volume=volume
42
+ )
43
+ tmp = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False)
44
+ await communicate.save(tmp.name)
45
+ return tmp.name
46
+
47
+ def generate_voice(
48
+ text, voice_label, preset_label,
49
+ rate_slider, pitch_slider, volume_slider
50
+ ):
51
+ if not text or not text.strip():
52
+ raise gr.Error("Please enter some text to convert!")
53
+
54
+ voice_id = VOICES.get(voice_label, "en-US-AriaNeural")
55
+
56
+ # Preset overrides sliders when chosen
57
+ if preset_label and preset_label != "๐ŸŽ›๏ธ Custom":
58
+ p = PRESETS[preset_label]
59
+ rate = p["rate"]
60
+ pitch = p["pitch"]
61
+ volume = p["volume"]
62
+ else:
63
+ sign_r = "+" if rate_slider >= 0 else ""
64
+ sign_p = "+" if pitch_slider >= 0 else ""
65
+ sign_v = "+" if volume_slider >= 0 else ""
66
+ rate = f"{sign_r}{rate_slider}%"
67
+ pitch = f"{sign_p}{pitch_slider}Hz"
68
+ volume = f"{sign_v}{volume_slider}%"
69
+
70
+ audio_path = asyncio.run(_synthesise(text, voice_id, rate, pitch, volume))
71
+ word_count = len(text.split())
72
+ char_count = len(text)
73
+ stats = f"โœ… Generated | {word_count} words | {char_count} chars | Voice: {voice_label}"
74
+ return audio_path, stats
75
+
76
+ def apply_preset(preset_label):
77
+ """Return slider updates when a preset is chosen."""
78
+ if preset_label == "๐ŸŽ›๏ธ Custom":
79
+ return gr.update(), gr.update(), gr.update()
80
+ p = PRESETS[preset_label]
81
+ r = int(p["rate"].replace("%","").replace("+",""))
82
+ pi = int(p["pitch"].replace("Hz","").replace("+",""))
83
+ v = int(p["volume"].replace("%","").replace("+",""))
84
+ return gr.update(value=r), gr.update(value=pi), gr.update(value=v)
85
+
86
+ # โ”€โ”€ Sample texts โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
87
+ SAMPLES = [
88
+ "Welcome to the future of voice synthesis. This AI-powered engine transforms your words into lifelike speech with stunning clarity and natural rhythm.",
89
+ "In the beginning, there was silence. Then came the voice โ€” warm, resonant, and unmistakably human. Today, that voice belongs to you.",
90
+ "Breaking news: Scientists have discovered a new exoplanet orbiting a distant star, potentially harboring conditions suitable for life.",
91
+ "Close your eyes. Take a deep breath. Let every thought drift away like clouds on a gentle breeze. You are safe. You are at peace.",
92
+ ]
93
+
94
+ # โ”€โ”€ Custom CSS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
95
+ CSS = """
96
+ @import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap');
97
+
98
+ :root {
99
+ --bg: #08090d;
100
+ --surface: #0f1117;
101
+ --surface2: #161820;
102
+ --border: #1e2130;
103
+ --accent: #6c63ff;
104
+ --accent2: #ff6584;
105
+ --gold: #f5c842;
106
+ --text: #e8e9f0;
107
+ --muted: #6b7280;
108
+ --glow: rgba(108,99,255,0.35);
109
+ }
110
+
111
+ * { box-sizing: border-box; }
112
+
113
+ body, .gradio-container {
114
+ background: var(--bg) !important;
115
+ font-family: 'DM Sans', sans-serif !important;
116
+ color: var(--text) !important;
117
+ min-height: 100vh;
118
+ }
119
+
120
+ /* โ”€โ”€ Hero Header โ”€โ”€ */
121
+ .hero-wrap {
122
+ text-align: center;
123
+ padding: 52px 24px 36px;
124
+ position: relative;
125
+ overflow: hidden;
126
+ }
127
+ .hero-wrap::before {
128
+ content: "";
129
+ position: absolute;
130
+ inset: 0;
131
+ background: radial-gradient(ellipse 70% 55% at 50% 0%, rgba(108,99,255,.18) 0%, transparent 70%);
132
+ pointer-events: none;
133
+ }
134
+ .hero-badge {
135
+ display: inline-block;
136
+ background: linear-gradient(135deg, var(--accent), var(--accent2));
137
+ color: #fff;
138
+ font-family: 'Syne', sans-serif;
139
+ font-size: 11px;
140
+ font-weight: 700;
141
+ letter-spacing: .12em;
142
+ text-transform: uppercase;
143
+ padding: 5px 16px;
144
+ border-radius: 100px;
145
+ margin-bottom: 20px;
146
+ }
147
+ .hero-title {
148
+ font-family: 'Syne', sans-serif !important;
149
+ font-size: clamp(2.4rem, 5vw, 4rem) !important;
150
+ font-weight: 800 !important;
151
+ line-height: 1.1 !important;
152
+ background: linear-gradient(135deg, #fff 30%, var(--accent) 70%, var(--accent2) 100%);
153
+ -webkit-background-clip: text !important;
154
+ -webkit-text-fill-color: transparent !important;
155
+ background-clip: text !important;
156
+ margin: 0 0 14px !important;
157
+ }
158
+ .hero-sub {
159
+ font-size: 1.05rem;
160
+ color: var(--muted);
161
+ max-width: 520px;
162
+ margin: 0 auto;
163
+ line-height: 1.6;
164
+ }
165
+
166
+ /* โ”€โ”€ Cards / Panels โ”€โ”€ */
167
+ .card {
168
+ background: var(--surface);
169
+ border: 1px solid var(--border);
170
+ border-radius: 18px;
171
+ padding: 28px;
172
+ transition: border-color .25s;
173
+ }
174
+ .card:hover { border-color: rgba(108,99,255,.4); }
175
+
176
+ .section-label {
177
+ font-family: 'Syne', sans-serif;
178
+ font-size: .72rem;
179
+ font-weight: 700;
180
+ letter-spacing: .14em;
181
+ text-transform: uppercase;
182
+ color: var(--accent);
183
+ margin-bottom: 12px;
184
+ }
185
+
186
+ /* โ”€โ”€ Textbox โ”€โ”€ */
187
+ textarea, .gr-textbox textarea {
188
+ background: var(--surface2) !important;
189
+ border: 1.5px solid var(--border) !important;
190
+ border-radius: 12px !important;
191
+ color: var(--text) !important;
192
+ font-family: 'DM Sans', sans-serif !important;
193
+ font-size: 1rem !important;
194
+ padding: 16px !important;
195
+ resize: vertical !important;
196
+ transition: border-color .2s, box-shadow .2s !important;
197
+ }
198
+ textarea:focus, .gr-textbox textarea:focus {
199
+ border-color: var(--accent) !important;
200
+ box-shadow: 0 0 0 3px var(--glow) !important;
201
+ outline: none !important;
202
+ }
203
+
204
+ /* โ”€โ”€ Dropdowns โ”€โ”€ */
205
+ .gr-dropdown select, select {
206
+ background: var(--surface2) !important;
207
+ border: 1.5px solid var(--border) !important;
208
+ border-radius: 10px !important;
209
+ color: var(--text) !important;
210
+ font-family: 'DM Sans', sans-serif !important;
211
+ padding: 10px 14px !important;
212
+ }
213
+
214
+ /* โ”€โ”€ Sliders โ”€โ”€ */
215
+ input[type="range"] {
216
+ accent-color: var(--accent) !important;
217
+ height: 4px;
218
+ }
219
+
220
+ /* โ”€โ”€ Generate Button โ”€โ”€ */
221
+ .gen-btn, .gen-btn button {
222
+ width: 100% !important;
223
+ padding: 18px !important;
224
+ border-radius: 14px !important;
225
+ background: linear-gradient(135deg, var(--accent), #8b5cf6, var(--accent2)) !important;
226
+ background-size: 200% 200% !important;
227
+ animation: gradShift 4s ease infinite !important;
228
+ border: none !important;
229
+ color: #fff !important;
230
+ font-family: 'Syne', sans-serif !important;
231
+ font-size: 1.1rem !important;
232
+ font-weight: 700 !important;
233
+ letter-spacing: .04em !important;
234
+ cursor: pointer !important;
235
+ box-shadow: 0 8px 30px rgba(108,99,255,.4) !important;
236
+ transition: transform .15s, box-shadow .15s !important;
237
+ }
238
+ .gen-btn button:hover {
239
+ transform: translateY(-2px) !important;
240
+ box-shadow: 0 12px 40px rgba(108,99,255,.55) !important;
241
+ }
242
+ .gen-btn button:active { transform: translateY(0) !important; }
243
+
244
+ @keyframes gradShift {
245
+ 0% { background-position: 0% 50%; }
246
+ 50% { background-position: 100% 50%; }
247
+ 100% { background-position: 0% 50%; }
248
+ }
249
+
250
+ /* โ”€โ”€ Sample Buttons โ”€โ”€ */
251
+ .sample-btn button {
252
+ background: var(--surface2) !important;
253
+ border: 1px solid var(--border) !important;
254
+ border-radius: 8px !important;
255
+ color: var(--muted) !important;
256
+ font-size: .82rem !important;
257
+ padding: 8px 14px !important;
258
+ transition: all .2s !important;
259
+ }
260
+ .sample-btn button:hover {
261
+ border-color: var(--accent) !important;
262
+ color: var(--accent) !important;
263
+ background: rgba(108,99,255,.08) !important;
264
+ }
265
+
266
+ /* โ”€โ”€ Audio Player โ”€โ”€ */
267
+ .gr-audio {
268
+ background: var(--surface2) !important;
269
+ border: 1px solid var(--border) !important;
270
+ border-radius: 14px !important;
271
+ padding: 16px !important;
272
+ }
273
+
274
+ /* โ”€โ”€ Stats bar โ”€โ”€ */
275
+ .stats-box textarea, .stats-box input {
276
+ background: rgba(108,99,255,.07) !important;
277
+ border: 1px solid rgba(108,99,255,.25) !important;
278
+ border-radius: 10px !important;
279
+ color: var(--accent) !important;
280
+ font-family: 'Syne', sans-serif !important;
281
+ font-size: .85rem !important;
282
+ text-align: center !important;
283
+ }
284
+
285
+ /* โ”€โ”€ Voice grid pills โ”€โ”€ */
286
+ .voice-pill {
287
+ display: inline-block;
288
+ background: var(--surface2);
289
+ border: 1px solid var(--border);
290
+ border-radius: 100px;
291
+ padding: 4px 14px;
292
+ font-size: .78rem;
293
+ color: var(--muted);
294
+ margin: 3px;
295
+ transition: all .2s;
296
+ }
297
+ .voice-pill:hover {
298
+ background: rgba(108,99,255,.12);
299
+ border-color: var(--accent);
300
+ color: var(--accent);
301
+ }
302
+
303
+ /* โ”€โ”€ Footer โ”€โ”€ */
304
+ .footer-txt {
305
+ text-align: center;
306
+ color: var(--muted);
307
+ font-size: .8rem;
308
+ padding: 28px 0 20px;
309
+ border-top: 1px solid var(--border);
310
+ margin-top: 40px;
311
+ }
312
+
313
+ /* โ”€โ”€ Misc Gradio overrides โ”€โ”€ */
314
+ .gr-form, .gr-box { background: transparent !important; }
315
+ label { color: var(--muted) !important; font-size: .82rem !important; font-weight: 500 !important; margin-bottom: 6px !important; }
316
+ .gr-panel { background: transparent !important; border: none !important; }
317
+ """
318
+
319
+ # โ”€โ”€ Build UI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
320
+ with gr.Blocks(css=CSS, title="VoiceForge AI โ€” Text to Speech") as demo:
321
+
322
+ # Hero
323
+ gr.HTML("""
324
+ <div class="hero-wrap">
325
+ <div class="hero-badge">โšก Powered by Edge TTS Neural Engine</div>
326
+ <h1 class="hero-title">VoiceForge AI</h1>
327
+ <p class="hero-sub">Transform any text into stunning, lifelike speech with 14 neural voices, real-time controls, and studio-quality output.</p>
328
+ </div>
329
+ """)
330
+
331
+ with gr.Row(equal_height=False):
332
+ # โ”€โ”€ LEFT COLUMN โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
333
+ with gr.Column(scale=3):
334
+ gr.HTML('<div class="section-label">โœ๏ธ Your Text</div>')
335
+ text_input = gr.Textbox(
336
+ placeholder="Type or paste anything here โ€” a story, an announcement, a poemโ€ฆ",
337
+ lines=7,
338
+ max_lines=20,
339
+ show_label=False,
340
+ elem_id="main-text",
341
+ )
342
+
343
+ # Sample buttons
344
+ gr.HTML('<div class="section-label" style="margin-top:18px">๐ŸŽฒ Quick Samples</div>')
345
+ with gr.Row():
346
+ for i, s in enumerate(SAMPLES):
347
+ short = s[:38] + "โ€ฆ"
348
+ btn = gr.Button(f'"{short}"', elem_classes=["sample-btn"], size="sm")
349
+ btn.click(fn=lambda t=s: t, outputs=text_input)
350
+
351
+ # โ”€โ”€ Voice & Preset โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
352
+ gr.HTML('<div class="section-label" style="margin-top:24px">๐ŸŽ™๏ธ Voice & Style</div>')
353
+ with gr.Row():
354
+ voice_dd = gr.Dropdown(
355
+ choices=list(VOICES.keys()),
356
+ value="๐ŸŒŸ Aria (US โ€“ Female)",
357
+ label="Neural Voice",
358
+ interactive=True,
359
+ )
360
+ preset_dd = gr.Dropdown(
361
+ choices=["๐ŸŽ›๏ธ Custom"] + list(PRESETS.keys()),
362
+ value="๐ŸŽ›๏ธ Custom",
363
+ label="Style Preset",
364
+ interactive=True,
365
+ )
366
+
367
+ # โ”€โ”€ Fine Controls โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
368
+ gr.HTML('<div class="section-label" style="margin-top:20px">๐ŸŽ›๏ธ Fine Controls</div>')
369
+ with gr.Row():
370
+ rate_sl = gr.Slider(-50, 50, value=0, step=1, label="โšก Speed (%)")
371
+ pitch_sl = gr.Slider(-20, 20, value=0, step=1, label="๐ŸŽต Pitch (Hz)")
372
+ vol_sl = gr.Slider(-50, 50, value=0, step=1, label="๐Ÿ”Š Volume (%)")
373
+
374
+ preset_dd.change(
375
+ fn=apply_preset,
376
+ inputs=[preset_dd],
377
+ outputs=[rate_sl, pitch_sl, vol_sl],
378
+ )
379
+
380
+ # โ”€โ”€ RIGHT COLUMN โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
381
+ with gr.Column(scale=2):
382
+ gr.HTML('<div class="section-label">๐Ÿš€ Generate</div>')
383
+ gen_btn = gr.Button("โœจ Generate Voice", elem_classes=["gen-btn"], variant="primary")
384
+
385
+ gr.HTML('<div class="section-label" style="margin-top:24px">๐ŸŽง Output Audio</div>')
386
+ audio_out = gr.Audio(
387
+ label="",
388
+ type="filepath",
389
+ interactive=False,
390
+ show_download_button=True,
391
+ )
392
+
393
+ stats_out = gr.Textbox(
394
+ label="",
395
+ interactive=False,
396
+ show_label=False,
397
+ elem_classes=["stats-box"],
398
+ placeholder="Stats will appear here after generationโ€ฆ",
399
+ )
400
+
401
+ # Voice reference card
402
+ gr.HTML("""
403
+ <div class="card" style="margin-top:20px">
404
+ <div class="section-label">๐ŸŒ Available Regions</div>
405
+ <div>
406
+ <span class="voice-pill">๐Ÿ‡บ๐Ÿ‡ธ United States</span>
407
+ <span class="voice-pill">๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom</span>
408
+ <span class="voice-pill">๐Ÿ‡ฆ๐Ÿ‡บ Australia</span>
409
+ <span class="voice-pill">๐Ÿ‡จ๐Ÿ‡ฆ Canada</span>
410
+ <span class="voice-pill">๐Ÿ‡ฎ๐Ÿ‡ณ India</span>
411
+ </div>
412
+ <div class="section-label" style="margin-top:14px">๐ŸŽญ Style Presets</div>
413
+ <div>
414
+ <span class="voice-pill">๐ŸŽ™๏ธ Podcast</span>
415
+ <span class="voice-pill">๐Ÿ“ฐ News</span>
416
+ <span class="voice-pill">๐Ÿง˜ Meditation</span>
417
+ <span class="voice-pill">๐Ÿ“š Audiobook</span>
418
+ <span class="voice-pill">๐Ÿค– AI Assistant</span>
419
+ </div>
420
+ </div>
421
+ """)
422
+
423
+ gen_btn.click(
424
+ fn=generate_voice,
425
+ inputs=[text_input, voice_dd, preset_dd, rate_sl, pitch_sl, vol_sl],
426
+ outputs=[audio_out, stats_out],
427
+ )
428
+
429
+ # Footer
430
+ gr.HTML("""
431
+ <div class="footer-txt">
432
+ Built with โค๏ธ using Microsoft Edge TTS Neural Voices &nbsp;ยท&nbsp;
433
+ VoiceForge AI &nbsp;ยท&nbsp;
434
+ <a href="https://huggingface.co" style="color:#6c63ff;text-decoration:none">Hugging Face Spaces</a>
435
+ </div>
436
+ """)
437
+
438
+ if __name__ == "__main__":
439
+ demo.launch()