Plana-Archive commited on
Commit
5ad521e
·
verified ·
1 Parent(s): c2836fc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +262 -325
app.py CHANGED
@@ -1,7 +1,8 @@
1
  import json
2
  import os
3
- import re
4
- import random # Impor random di sini
 
5
  import librosa
6
  import numpy as np
7
  import torch
@@ -14,344 +15,280 @@ from text import text_to_sequence, _clean_text
14
  from mel_processing import spectrogram_torch
15
  from huggingface_hub import hf_hub_download
16
 
17
- # Mengambil token dari Secret Variables (Settings Space)
18
- HF_TOKEN = os.getenv("HF_TOKEN")
19
  REPO_ID = "Plana-Archive/Plana-TTS"
20
  SUBFOLDER = "Prosekai-TTS/saved_model"
21
-
22
- # Setting CPU Mode untuk Space Gratis
23
  device = torch.device("cpu")
24
- limitation = os.getenv("SYSTEM") == "spaces"
25
 
 
26
  def get_text(text, hps, is_phoneme):
 
27
  text_norm = text_to_sequence(text, hps.symbols, [] if is_phoneme else hps.data.text_cleaners)
28
  if hps.data.add_blank:
29
  text_norm = commons.intersperse(text_norm, 0)
30
- text_norm = LongTensor(text_norm)
31
- return text_norm
32
-
33
- def create_tts_fn(model, hps, speaker_ids):
34
- def tts_fn(text, speaker, speed, is_phoneme):
35
- if limitation:
36
- text_len = len(text)
37
- max_len = 500
38
- if is_phoneme:
39
- max_len *= 3
40
- if text_len > max_len:
41
- return "Error: Text is too long", None
42
-
43
- speaker_id = speaker_ids[speaker]
44
- stn_tst = get_text(text, hps, is_phoneme)
45
- with no_grad():
46
- x_tst = stn_tst.unsqueeze(0).to(device)
47
- x_tst_lengths = LongTensor([stn_tst.size(0)]).to(device)
48
- sid = LongTensor([speaker_id]).to(device)
49
- audio = model.infer(x_tst, x_tst_lengths, sid=sid, noise_scale=.667,
50
- noise_scale_w=0.8, length_scale=1.0 / speed)[0][0, 0].data.cpu().float().numpy()
51
- del stn_tst, x_tst, x_tst_lengths, sid
52
- return "Success", (hps.data.sampling_rate, audio)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  return tts_fn
54
 
55
  def create_to_phoneme_fn(hps):
 
56
  def to_phoneme_fn(text):
57
- return _clean_text(text, hps.data.text_cleaners) if text != "" else ""
58
  return to_phoneme_fn
59
 
60
- # ================== CSS LAYOUT YANG DIPERBARUI ==================
61
- css_layout = """
62
- /* Warna tema utama: Hijau lembut dan tebal */
63
- :root {
64
- --primary-green: #2ecc71;
65
- --soft-green: #d5f5e3;
66
- --glow-green: rgba(46, 204, 113, 0.3);
67
- }
68
-
69
- /* Latar belakang utama putih cerah */
70
- .gradio-container {
71
- background-color: white !important;
72
- }
73
-
74
- /* Judul utama di tengah dan berwarna hijau */
75
- h1 {
76
- text-align: center !important;
77
- color: #27ae60 !important;
78
- text-shadow: 0 0 10px var(--glow-green);
79
- }
80
-
81
- /* Hilangkan footer Gradio default */
82
- .gradio-footer {
83
- display: none !important;
84
- }
85
-
86
- /* ===== BAGIAN SELECT CHARACTER (RADIO BUTTON DI-STYLE SEPERTI KOTAK) ===== */
87
- .character-radio .gr-radio {
88
- display: flex;
89
- flex-direction: column;
90
- gap: 8px;
91
- max-height: 200px;
92
- overflow-y: auto;
93
- padding: 10px;
94
- border: 2px solid var(--primary-green);
95
- border-radius: 16px;
96
- background: white;
97
- box-shadow: 0 4px 15px var(--glow-green);
98
- }
99
-
100
- /* Sembunyikan radio button asli */
101
- .character-radio .gr-radio input[type="radio"] {
102
- display: none;
103
- }
104
-
105
- /* Style untuk setiap item label */
106
- .character-radio .gr-radio label {
107
- display: block;
108
- background: linear-gradient(145deg, #ffffff, var(--soft-green));
109
- border: 1px solid var(--primary-green);
110
- border-radius: 30px;
111
- padding: 8px 16px;
112
- margin: 0;
113
- cursor: pointer;
114
- color: #2c3e50;
115
- font-weight: bold;
116
- text-align: center;
117
- transition: all 0.3s ease;
118
- box-shadow: 0 2px 8px rgba(0,0,0,0.05);
119
- }
120
-
121
- .character-radio .gr-radio label:hover {
122
- background: var(--primary-green);
123
- color: white;
124
- transform: scale(1.02);
125
- box-shadow: 0 6px 12px var(--glow-green);
126
- border-color: white;
127
- }
128
-
129
- /* Saat terpilih */
130
- .character-radio .gr-radio input[type="radio"]:checked + label {
131
- background: var(--primary-green);
132
- color: white;
133
- border-color: white;
134
- box-shadow: 0 0 0 2px white, 0 0 0 4px var(--primary-green);
135
- }
136
-
137
- /* Scrollbar kustom */
138
- .character-radio .gr-radio::-webkit-scrollbar {
139
- width: 8px;
140
- }
141
- .character-radio .gr-radio::-webkit-scrollbar-track {
142
- background: var(--soft-green);
143
- border-radius: 10px;
144
- }
145
- .character-radio .gr-radio::-webkit-scrollbar-thumb {
146
- background: var(--primary-green);
147
- border-radius: 10px;
148
- }
149
-
150
- /* ===== BACKGROUND HIJAU UNTUK LABEL TERTENTU (VERSI LEBIH AKURAT) ===== */
151
- /* Target label untuk TextArea dengan id text-label */
152
- .gr-box-wrapper:has(> #text-label) > label,
153
- #text-label + label {
154
- background-color: var(--primary-green) !important;
155
- color: white !important;
156
- padding: 4px 12px !important;
157
- border-radius: 20px !important;
158
- font-weight: bold !important;
159
- box-shadow: none !important;
160
- display: inline-block !important;
161
- margin-bottom: 4px !important;
162
- }
163
-
164
- /* Target label untuk Slider dengan id speed-label */
165
- .gr-box-wrapper:has(> #speed-label) > label,
166
- #speed-label + label {
167
- background-color: var(--primary-green) !important;
168
- color: white !important;
169
- padding: 4px 12px !important;
170
- border-radius: 20px !important;
171
- font-weight: bold !important;
172
- box-shadow: none !important;
173
- display: inline-block !important;
174
- margin-bottom: 4px !important;
175
- }
176
-
177
- /* Target label untuk Textbox output message */
178
- .gr-box-wrapper:has(> #output-msg-label) > label,
179
- #output-msg-label + label {
180
- background-color: var(--primary-green) !important;
181
- color: white !important;
182
- padding: 4px 12px !important;
183
- border-radius: 20px !important;
184
- font-weight: bold !important;
185
- box-shadow: none !important;
186
- display: inline-block !important;
187
- margin-bottom: 4px !important;
188
- }
189
-
190
- /* Target label untuk Audio output */
191
- .gr-box-wrapper:has(> #output-audio-label) > label,
192
- #output-audio-label + label {
193
- background-color: var(--primary-green) !important;
194
- color: white !important;
195
- padding: 4px 12px !important;
196
- border-radius: 20px !important;
197
- font-weight: bold !important;
198
- box-shadow: none !important;
199
- display: inline-block !important;
200
- margin-bottom: 4px !important;
201
- }
202
-
203
- /* ===== CREDIT SECTION (hanya CREATED BY MUTSUMI) ===== */
204
- .credit-section {
205
- margin-top: 40px;
206
- padding: 20px;
207
- background: linear-gradient(135deg, #ffffff, #f0fff4);
208
- border-top: 3px solid var(--primary-green);
209
- border-radius: 30px 30px 0 0;
210
- text-align: center;
211
- box-shadow: 0 -5px 20px var(--glow-green);
212
- }
213
-
214
- .credit-text {
215
- font-size: 1.8rem;
216
- font-weight: bold;
217
- color: var(--primary-green);
218
- text-shadow: 0 0 10px var(--glow-green);
219
- letter-spacing: 2px;
220
- }
221
-
222
- /* Gaya untuk loading Gradio (progress bar) menjadi hijau */
223
- .gr-progress {
224
- background: var(--primary-green) !important;
225
- }
226
- .gr-progress-track {
227
- background: var(--soft-green) !important;
228
- }
229
-
230
- /* Memastikan elemen lain tetap seperti aslinya */
231
- #advanced-btn {
232
- color: white;
233
- border-color: black;
234
- background: black;
235
- font-size: .7rem !important;
236
- border-radius: 14px !important;
237
- }
238
- #advanced-options {
239
- display: none;
240
- margin-bottom: 20px;
241
- }
242
  """
243
- # ================== AKHIR CSS ==================
244
-
245
- if __name__ == '__main__':
246
- print("[*] Downloading model assets from Hub...")
247
- # Menggunakan token dari environment variable
248
- config_path = hf_hub_download(repo_id=REPO_ID, filename="config.json", subfolder=SUBFOLDER, token=HF_TOKEN)
249
- model_path = hf_hub_download(repo_id=REPO_ID, filename="model.pth", subfolder=SUBFOLDER, token=HF_TOKEN)
250
- cover_path = hf_hub_download(repo_id=REPO_ID, filename="cover.png", subfolder=SUBFOLDER, token=HF_TOKEN)
251
-
252
- hps = utils.get_hparams_from_file(config_path)
253
- model = SynthesizerTrn(
254
- len(hps.symbols),
255
- hps.data.filter_length // 2 + 1,
256
- hps.train.segment_size // hps.data.hop_length,
257
- n_speakers=hps.data.n_speakers,
258
- **hps.model).to(device)
259
-
260
- utils.load_checkpoint(model_path, model, None)
261
- model.eval()
262
-
263
- speaker_ids = [sid for sid, name in enumerate(hps.speakers) if name != "None"]
264
- speakers = [name for sid, name in enumerate(hps.speakers) if name != "None"]
265
-
266
- # Daftar contoh teks random (bisa ditambah)
267
  random_texts = [
268
- "こんにちは。",
269
- "おはようございます。",
270
- "こんばんは。",
271
- "今日はいい天気ですね。",
272
- "プロジェクトセカイへようこそ!",
273
- "Hatsune Miku",
274
- "初音ミク",
275
- "音街ウナ",
276
- "鏡音リン",
277
- "鏡音レン"
278
  ]
279
 
280
- models_tts = [('プロセカ TTS', cover_path, speakers, '日本語 (Japanese)', 'こんにちは。',
281
- hps.symbols, create_tts_fn(model, hps, speaker_ids),
282
- create_to_phoneme_fn(hps))]
283
-
284
- with gr.Blocks(css=css_layout, theme=gr.themes.Soft(primary_hue="green")) as app:
285
- # Judul utama di tengah
286
- gr.Markdown("# Project Sekai VITS\n\n")
287
-
288
- with gr.TabItem("Proseka"):
289
- for i, (name, cover, spks, lang, ex, syms, tts_fn, to_phoneme_fn) in enumerate(models_tts):
290
- with gr.Column():
291
- # Menampilkan cover.png
292
- gr.Image(value=cover, label="Cover", show_label=False, width="100%")
293
-
294
- # ===== INPUT TEXT AREA dengan tombol Random =====
295
- with gr.Row():
296
- tts_input1 = gr.TextArea(
297
- label="Text", value=ex, elem_id="text-label" # Hanya satu elem_id
298
- )
299
- random_btn = gr.Button("🎲 Random", scale=0, min_width=80)
300
-
301
- # ===== BAGIAN SELECT CHARACTER (RADIO BUTTON YANG DI-STYLE) =====
302
- gr.Markdown("### **Select Character**")
303
- character_radio = gr.Radio(
304
- choices=spks,
305
- value=spks[0],
306
- label="",
307
- elem_classes="character-radio"
308
- )
309
-
310
- # ===== SPEED SLIDER =====
311
- tts_input3 = gr.Slider(
312
- label="Speed", value=1, minimum=0.1, maximum=2, step=0.1,
313
- elem_id="speed-label"
314
- )
315
-
316
- # ===== ADVANCED OPTIONS =====
317
- with gr.Accordion(label="Advanced Options", open=False):
318
- phoneme_input = gr.Checkbox(value=False, label="Phoneme input")
319
- to_phoneme_btn = gr.Button("Convert text to phoneme")
320
- phoneme_list = gr.Dataset(
321
- label="Phoneme list",
322
- components=[tts_input1],
323
- samples=[[x] for x in syms]
324
- )
325
- phoneme_list_json = gr.Json(value=syms, visible=False)
326
-
327
- # ===== TOMBOL GENERATE =====
328
- tts_submit = gr.Button("Generate", variant="primary")
329
- tts_output1 = gr.Textbox(label="Output Message", elem_id="output-msg-label")
330
- tts_output2 = gr.Audio(label="Output Audio", elem_id="output-audio-label")
331
-
332
- # ===== FUNGSI RANDOM TEXT =====
333
- def set_random_text():
334
- return random.choice(random_texts)
335
-
336
- random_btn.click(
337
- fn=set_random_text,
338
- inputs=[],
339
- outputs=[tts_input1]
340
- )
341
-
342
- # ===== FUNGSI TTS =====
343
- tts_submit.click(
344
- tts_fn,
345
- [tts_input1, character_radio, tts_input3, phoneme_input],
346
- [tts_output1, tts_output2]
347
- )
348
-
349
- to_phoneme_btn.click(to_phoneme_fn, [tts_input1], [tts_input1])
350
-
351
- # ===== CREDIT SECTION =====
352
- with gr.Column(elem_classes="credit-section"):
353
- gr.Markdown("""
354
- <div class="credit-text">✨ CREATED BY MUTSUMI ✨</div>
355
- """)
356
-
357
- app.queue().launch()
 
1
  import json
2
  import os
3
+ import sys
4
+ import traceback
5
+ import random
6
  import librosa
7
  import numpy as np
8
  import torch
 
15
  from mel_processing import spectrogram_torch
16
  from huggingface_hub import hf_hub_download
17
 
18
+ # ================= KONFIGURASI =================
19
+ HF_TOKEN = os.getenv("HF_TOKEN") # Token dari Secret (jika diperlukan)
20
  REPO_ID = "Plana-Archive/Plana-TTS"
21
  SUBFOLDER = "Prosekai-TTS/saved_model"
 
 
22
  device = torch.device("cpu")
23
+ limitation = os.getenv("SYSTEM") == "spaces" # batasan teks di Spaces gratis
24
 
25
+ # ================= FUNGSI BANTU =================
26
  def get_text(text, hps, is_phoneme):
27
+ """Mengubah teks menjadi tensor ID untuk model."""
28
  text_norm = text_to_sequence(text, hps.symbols, [] if is_phoneme else hps.data.text_cleaners)
29
  if hps.data.add_blank:
30
  text_norm = commons.intersperse(text_norm, 0)
31
+ return LongTensor(text_norm)
32
+
33
+ def load_model_and_speakers():
34
+ """Memuat model dan daftar speaker dengan penanganan error."""
35
+ try:
36
+ print("[INFO] Downloading model assets...")
37
+ config_path = hf_hub_download(repo_id=REPO_ID, filename="config.json", subfolder=SUBFOLDER, token=HF_TOKEN)
38
+ model_path = hf_hub_download(repo_id=REPO_ID, filename="model.pth", subfolder=SUBFOLDER, token=HF_TOKEN)
39
+ cover_path = hf_hub_download(repo_id=REPO_ID, filename="cover.png", subfolder=SUBFOLDER, token=HF_TOKEN)
40
+
41
+ # Baca konfigurasi
42
+ hps = utils.get_hparams_from_file(config_path)
43
+
44
+ # Inisialisasi model
45
+ model = SynthesizerTrn(
46
+ len(hps.symbols),
47
+ hps.data.filter_length // 2 + 1,
48
+ hps.train.segment_size // hps.data.hop_length,
49
+ n_speakers=hps.data.n_speakers,
50
+ **hps.model
51
+ ).to(device)
52
+
53
+ # Muat bobot model
54
+ utils.load_checkpoint(model_path, model, None)
55
+ model.eval()
56
+
57
+ # Ekstrak daftar speaker
58
+ # Coba beberapa kemungkinan struktur data
59
+ if hasattr(hps, 'speakers') and isinstance(hps.speakers, list):
60
+ speakers = [name for name in hps.speakers if name != "None"]
61
+ elif hasattr(hps, 'speaker_names') and isinstance(hps.speaker_names, list):
62
+ speakers = [name for name in hps.speaker_names if name != "None"]
63
+ else:
64
+ # Fallback: buat speaker default berdasarkan jumlah
65
+ speakers = [f"Speaker {i+1}" for i in range(hps.data.n_speakers)]
66
+
67
+ # Pastikan tidak ada duplikat
68
+ speakers = list(dict.fromkeys(speakers))
69
+
70
+ speaker_ids = list(range(len(speakers)))
71
+
72
+ print(f"[INFO] Loaded {len(speakers)} speakers: {speakers}")
73
+ return model, hps, speakers, speaker_ids, cover_path
74
+
75
+ except Exception as e:
76
+ print("[ERROR] Gagal memuat model:")
77
+ traceback.print_exc()
78
+ raise gr.Error(f"Gagal memuat model: {str(e)}")
79
+
80
+ # ================= FUNGSI TTS =================
81
+ def create_tts_fn(model, hps, speaker_ids, speakers):
82
+ """Membuat fungsi TTS dengan penanganan error."""
83
+ def tts_fn(text, speaker_name, speed, is_phoneme):
84
+ try:
85
+ # Validasi input
86
+ if not text.strip():
87
+ return "Error: Teks kosong", None
88
+
89
+ if limitation and len(text) > (1500 if is_phoneme else 500):
90
+ return "Error: Teks terlalu panjang", None
91
+
92
+ # Cari ID speaker berdasarkan nama
93
+ try:
94
+ speaker_idx = speakers.index(speaker_name)
95
+ speaker_id = speaker_ids[speaker_idx]
96
+ except ValueError:
97
+ return f"Error: Speaker '{speaker_name}' tidak ditemukan", None
98
+
99
+ # Proses teks
100
+ stn_tst = get_text(text, hps, is_phoneme)
101
+ with no_grad():
102
+ x_tst = stn_tst.unsqueeze(0).to(device)
103
+ x_tst_lengths = LongTensor([stn_tst.size(0)]).to(device)
104
+ sid = LongTensor([speaker_id]).to(device)
105
+
106
+ # Inferensi
107
+ audio = model.infer(
108
+ x_tst, x_tst_lengths,
109
+ sid=sid,
110
+ noise_scale=0.667,
111
+ noise_scale_w=0.8,
112
+ length_scale=1.0 / speed
113
+ )[0][0, 0].data.cpu().float().numpy()
114
+
115
+ return "Sukses", (hps.data.sampling_rate, audio)
116
+
117
+ except Exception as e:
118
+ print("[ERROR] Saat generate:")
119
+ traceback.print_exc()
120
+ return f"Error: {str(e)}", None
121
+
122
  return tts_fn
123
 
124
  def create_to_phoneme_fn(hps):
125
+ """Fungsi konversi teks ke fonem (jika diperlukan)."""
126
  def to_phoneme_fn(text):
127
+ return _clean_text(text, hps.data.text_cleaners) if text else ""
128
  return to_phoneme_fn
129
 
130
+ # ================= CSS SEDERHANA =================
131
+ css = """
132
+ /* Latar putih bersih */
133
+ .gradio-container { background: white !important; }
134
+
135
+ /* Judul hijau di tengah */
136
+ h1 { text-align: center; color: #2ecc71; }
137
+
138
+ /* Sembunyikan footer */
139
+ .gradio-footer { display: none !important; }
140
+
141
+ /* Label hijau tanpa shadow */
142
+ label {
143
+ background: #2ecc71 !important;
144
+ color: white !important;
145
+ padding: 2px 10px !important;
146
+ border-radius: 20px !important;
147
+ font-weight: bold !important;
148
+ display: inline-block !important;
149
+ margin-bottom: 4px !important;
150
+ box-shadow: none !important;
151
+ }
152
+
153
+ /* Kotak karakter dengan scroll */
154
+ .character-box {
155
+ border: 2px solid #2ecc71;
156
+ border-radius: 12px;
157
+ padding: 10px;
158
+ max-height: 250px;
159
+ overflow-y: auto;
160
+ background: #f9f9f9;
161
+ margin-bottom: 15px;
162
+ }
163
+
164
+ /* Radio button disembunyikan, label menjadi kotak */
165
+ .character-box input[type="radio"] { display: none; }
166
+ .character-box label {
167
+ display: block;
168
+ background: white;
169
+ border: 1px solid #2ecc71;
170
+ border-radius: 25px;
171
+ padding: 8px 12px;
172
+ margin: 5px 0;
173
+ cursor: pointer;
174
+ color: #2c3e50;
175
+ font-weight: normal;
176
+ transition: all 0.2s;
177
+ box-shadow: none !important;
178
+ }
179
+ .character-box label:hover {
180
+ background: #2ecc71;
181
+ color: white;
182
+ }
183
+ .character-box input[type="radio"]:checked + label {
184
+ background: #2ecc71;
185
+ color: white;
186
+ border-color: white;
187
+ font-weight: bold;
188
+ }
189
+
190
+ /* Credit section */
191
+ .credit {
192
+ margin-top: 40px;
193
+ text-align: center;
194
+ font-size: 1.5rem;
195
+ color: #2ecc71;
196
+ border-top: 2px solid #2ecc71;
197
+ padding-top: 20px;
198
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  """
200
+
201
+ # ================= MAIN =================
202
+ if __name__ == "__main__":
203
+ # Muat model dan speaker
204
+ model, hps, speakers, speaker_ids, cover_path = load_model_and_speakers()
205
+
206
+ # Contoh teks acak (tanpa Jepang)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  random_texts = [
208
+ "Hello, how are you today?",
209
+ "This is a test sentence.",
210
+ "Welcome to Project Sekai TTS.",
211
+ "I love music and singing.",
212
+ "Can you generate speech for me?",
213
+ "Hatsune Miku is a virtual singer.",
214
+ "Let's create something amazing."
 
 
 
215
  ]
216
 
217
+ # Buat fungsi TTS
218
+ tts_fn = create_tts_fn(model, hps, speaker_ids, speakers)
219
+ to_phoneme_fn = create_to_phoneme_fn(hps)
220
+
221
+ # ===== GRADIO INTERFACE =====
222
+ with gr.Blocks(css=css, theme=gr.themes.Soft(primary_hue="green")) as demo:
223
+ gr.Markdown("# Project SEKAI VITS")
224
+
225
+ with gr.Tab("Proseka"):
226
+ # Cover image
227
+ gr.Image(value=cover_path, show_label=False, width="100%")
228
+
229
+ # Input teks dan tombol random
230
+ with gr.Row():
231
+ text_input = gr.TextArea(
232
+ label="Text",
233
+ placeholder="Masukkan teks di sini...",
234
+ value=random_texts[0]
235
+ )
236
+ random_btn = gr.Button("🎲 Random", scale=0, min_width=80)
237
+
238
+ # Pilihan karakter (custom radio dengan scroll)
239
+ gr.Markdown("### Select Character")
240
+ with gr.Column(elem_classes="character-box"):
241
+ # Buat radio secara manual agar bisa di-styling
242
+ character_radio = gr.Radio(
243
+ choices=speakers,
244
+ value=speakers[0],
245
+ label="",
246
+ show_label=False
247
+ )
248
+
249
+ # Speed slider
250
+ speed_slider = gr.Slider(
251
+ label="Speed",
252
+ minimum=0.5,
253
+ maximum=2.0,
254
+ value=1.0,
255
+ step=0.1
256
+ )
257
+
258
+ # Advanced options (opsional)
259
+ with gr.Accordion("Advanced Options", open=False):
260
+ phoneme_check = gr.Checkbox(label="Phoneme input", value=False)
261
+ to_phoneme_btn = gr.Button("Convert to phoneme")
262
+
263
+ # Tombol generate
264
+ generate_btn = gr.Button("Generate", variant="primary")
265
+
266
+ # Output
267
+ output_msg = gr.Textbox(label="Output Message")
268
+ output_audio = gr.Audio(label="Output Audio")
269
+
270
+ # ===== INTERAKSI =====
271
+ # Random text
272
+ random_btn.click(
273
+ fn=lambda: random.choice(random_texts),
274
+ outputs=text_input
275
+ )
276
+
277
+ # Generate
278
+ generate_btn.click(
279
+ fn=tts_fn,
280
+ inputs=[text_input, character_radio, speed_slider, phoneme_check],
281
+ outputs=[output_msg, output_audio]
282
+ )
283
+
284
+ # Convert to phoneme
285
+ to_phoneme_btn.click(
286
+ fn=to_phoneme_fn,
287
+ inputs=[text_input],
288
+ outputs=[text_input]
289
+ )
290
+
291
+ # Credit
292
+ gr.Markdown('<div class="credit">✨ CREATED BY MUTSUMI ✨</div>')
293
+
294
+ demo.queue().launch()