iphonehelp commited on
Commit
336111c
·
verified ·
1 Parent(s): 6c0a289

Upload 5 files

Browse files
Files changed (5) hide show
  1. README.md +22 -5
  2. app.py +1073 -0
  3. proxies.json +0 -0
  4. requirements.txt +6 -0
  5. voices.json +222 -0
README.md CHANGED
@@ -1,12 +1,29 @@
1
  ---
2
- title: ElevenLabs TTS Free
3
- emoji: 📈
4
- colorFrom: pink
5
- colorTo: blue
6
  sdk: gradio
7
- sdk_version: 5.36.2
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Haidnt Elevenlabs Free
3
+ emoji: 🌖
4
+ colorFrom: yellow
5
+ colorTo: red
6
  sdk: gradio
7
+ sdk_version: 5.35.0
8
  app_file: app.py
9
  pinned: false
10
+ short_description: FREE FOR EVERYONE
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
14
+
15
+ # 🎙️ Công cụ ElevenLabs TTS + Proxy (Miễn phí cho tất cả mọi người)
16
+
17
+ Đây là công cụ **100% miễn phí**, dành cho tất cả mọi người – đặc biệt hỗ trợ cộng đồng cá nhân và phi thương mại.
18
+
19
+ ✅ Tự dùng API Key của bạn (không lưu)
20
+ ✅ Thêm giọng cá nhân hoá
21
+ ✅ Hỗ trợ proxy riêng (không lưu)
22
+ ✅ Có sẵn proxy công khai & bộ giọng mẫu chất lượng
23
+
24
+ > ⚠️ **Lưu ý**: Đây là công cụ **phi thương mại**. Vui lòng **không rao bán, đổi tên hay sử dụng vào mục đích thương mại**.
25
+ > 🛡️ Mọi API Key và Proxy đều chỉ lưu tạm, không ghi log, không gửi đi đâu cả.
26
+
27
+ ---
28
+
29
+ 🔗 Làm với ❤️ để phục vụ cộng đồng mã nguồn mở.
app.py ADDED
@@ -0,0 +1,1073 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Gradio_11Labs_Enhanced_with_Proxy.py
2
+ # ============================================================================
3
+ # 2025-07-09 – *Proxy + Full Voice Manager + UX Tweaks + Privacy Protection*
4
+ # ---------------------------------------------------------------------------
5
+ # ✦ Proxy Tab: bulk add/test, auto/manual assign (≤3 keys/proxy), delete bad
6
+ # ✦ Batch Tab: refresh-all, live token count, total credit, verify key-proxy
7
+ # – "🌀 Tạo giọng nói" sits *above* textbox
8
+ # – auto-pick checkbox at bottom
9
+ # ✦ API-Key Tab: shows proxy **host** only (no creds/port)
10
+ # ✦ Voice Tab: restored original full manager (add/edit/delete/reset/export)
11
+ # ✦ Privacy: Hide sensitive info (API keys, proxy credentials, voice IDs)
12
+ # ✦ Session: Use gr.State for per-browser isolation, auto-clear after session ends
13
+ # ---------------------------------------------------------------------------
14
+
15
+ import gradio as gr
16
+ import os, json, time, urllib.parse, requests, random
17
+ from datetime import datetime
18
+ import pandas as pd
19
+ from dotenv import load_dotenv
20
+ from elevenlabs.client import ElevenLabs
21
+ import tempfile
22
+ import uuid
23
+ from functools import wraps
24
+
25
+ def log_exception(e, context=""):
26
+ import traceback
27
+ tb = traceback.format_exc()
28
+ msg = f"❌ Lỗi ở {context}: {str(e)}\n{tb}"
29
+ print(msg)
30
+ return msg
31
+
32
+ # === .env ===
33
+ load_dotenv()
34
+ DEFAULT_MODEL = os.getenv("ELEVENLABS_MODEL_ID", "eleven_multilingual_v2")
35
+ DEFAULT_FORMAT = os.getenv("ELEVENLABS_OUTPUT_FORMAT", "mp3_44100")
36
+
37
+ # === Default Data ===
38
+ def load_default_voices():
39
+ """Load default voices from voices.json"""
40
+ try:
41
+ with open("voices.json", "r", encoding="utf-8") as f:
42
+ return json.load(f)
43
+ except:
44
+ return {}
45
+
46
+ def load_default_proxies():
47
+ """Load default proxies from proxies.json"""
48
+ try:
49
+ with open("proxies.json", "r", encoding="utf-8") as f:
50
+ return json.load(f)
51
+ except:
52
+ return {}
53
+
54
+ DEFAULT_VOICE_SETTINGS = {
55
+ "speed": 1.0,
56
+ "stability": 0.5,
57
+ "similarity_boost": 0.75,
58
+ "style_exaggeration": 0.0,
59
+ "use_speaker_boost": True,
60
+ }
61
+
62
+ MODELS = ["eleven_monolingual_v1", "eleven_multilingual_v1", "eleven_multilingual_v2"]
63
+
64
+ # === Session Management ===
65
+ def session_wrapper(func):
66
+ """Decorator to ensure session data access"""
67
+ @wraps(func)
68
+ def wrapper(*args, **kwargs):
69
+ return func(*args, **kwargs)
70
+ return wrapper
71
+
72
+ # === Privacy helpers ===
73
+ def mask_api_key(key):
74
+ """Mask API key: show first 4 and last 4 chars"""
75
+ if not key or len(key) < 8:
76
+ return key
77
+ return f"{key[:4]}...{key[-4:]}"
78
+
79
+ def mask_voice_id(voice_id):
80
+ """Mask Voice ID: show first 3 and last 3 chars"""
81
+ if not voice_id or len(voice_id) < 6:
82
+ return voice_id
83
+ return f"{voice_id[:3]}...{voice_id[-3:]}"
84
+
85
+ def mask_proxy_url(url):
86
+ """Mask credentials in proxy URL"""
87
+ try:
88
+ parsed = urllib.parse.urlparse(url)
89
+ if parsed.username and parsed.password:
90
+ masked_netloc = f"***:***@{parsed.hostname}"
91
+ if parsed.port:
92
+ masked_netloc += f":{parsed.port}"
93
+ return urllib.parse.urlunparse((
94
+ parsed.scheme, masked_netloc, parsed.path,
95
+ parsed.params, parsed.query, parsed.fragment
96
+ ))
97
+ return url
98
+ except:
99
+ return url
100
+
101
+ def create_key_display_map(api_keys):
102
+ """Create mapping between real API key and display name"""
103
+ display_map = {}
104
+ reverse_map = {}
105
+ for i, key in enumerate(api_keys.keys(), 1):
106
+ display_name = f"Key-{i:02d} ({mask_api_key(key)})"
107
+ display_map[key] = display_name
108
+ reverse_map[display_name] = key
109
+ return display_map, reverse_map
110
+
111
+ def get_key_choices_for_display(api_keys):
112
+ """Get list of masked API keys for display"""
113
+ display_map, _ = create_key_display_map(api_keys)
114
+ return list(display_map.values())
115
+
116
+ def get_real_key_from_display(display_name, api_keys):
117
+ """Get real API key from display name"""
118
+ _, reverse_map = create_key_display_map(api_keys)
119
+ return reverse_map.get(display_name, display_name)
120
+
121
+ def create_proxy_display_map(proxies):
122
+ """Create mapping between real proxy URL and display name"""
123
+ display_map = {}
124
+ reverse_map = {}
125
+ for i, url in enumerate(proxies.keys(), 1):
126
+ display_name = f"Proxy-{i:02d} ({mask_proxy_url(url)})"
127
+ display_map[url] = display_name
128
+ reverse_map[display_name] = url
129
+ return display_map, reverse_map
130
+
131
+ def get_proxy_choices_for_display(proxies):
132
+ """Get list of masked proxies for display"""
133
+ display_map, _ = create_proxy_display_map(proxies)
134
+ return list(display_map.values())
135
+
136
+ def get_real_proxy_from_display(display_name, proxies):
137
+ """Get real proxy URL from display name"""
138
+ _, reverse_map = create_proxy_display_map(proxies)
139
+ return reverse_map.get(display_name, display_name)
140
+
141
+ # === Generic helpers ===
142
+ def safe_get_default(choices, default_func):
143
+ """Safely get default value that exists in choices"""
144
+ if not choices:
145
+ return None
146
+ default = default_func()
147
+ return default if default in choices else choices[0]
148
+
149
+ # === ElevenLabs ===
150
+ def get_client(api_key):
151
+ return ElevenLabs(api_key=api_key)
152
+
153
+ @session_wrapper
154
+ def get_api_usage(api_key, bypass_proxy=False, proxies=None):
155
+ import urllib3
156
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
157
+ proxy_url = None if bypass_proxy else get_proxy_of_key(api_key, proxies)
158
+ proxies_dict = {"http": proxy_url, "https": proxy_url} if proxy_url else None
159
+ try:
160
+ r = requests.get(
161
+ "https://api.elevenlabs.io/v1/user/subscription",
162
+ headers={"xi-api-key": api_key},
163
+ proxies=proxies_dict,
164
+ timeout=4,
165
+ verify=False if proxy_url else True
166
+ )
167
+ if r.status_code == 200:
168
+ d = r.json()
169
+ return {
170
+ "status": "✅ OK",
171
+ "used": d.get("character_count", 0),
172
+ "limit": d.get("character_limit", 0),
173
+ "tier": d.get("tier", ""),
174
+ "remaining": d.get("character_limit", 0) - d.get("character_count", 0),
175
+ }
176
+ return {"status": f"❌ {r.status_code}"}
177
+ except Exception as e:
178
+ return {"status": f"⚠️ {str(e).split(' ')[0]}"}
179
+
180
+ def total_credit(api_keys):
181
+ return sum(v.get("remaining", 0) for v in api_keys.values())
182
+
183
+ # === Proxy helpers ===
184
+ @session_wrapper
185
+ def proxy_host(url: str):
186
+ try:
187
+ return urllib.parse.urlsplit(url).hostname or ""
188
+ except:
189
+ return ""
190
+
191
+ @session_wrapper
192
+ def format_proxy_table(proxies):
193
+ rows = []
194
+ for url, info in proxies.items():
195
+ masked_url = mask_proxy_url(url)
196
+ masked_keys = [mask_api_key(k) for k in info.get("assigned_keys", [])]
197
+ sample_keys = ", ".join(masked_keys[:3]) + ("…" if len(masked_keys) > 3 else "")
198
+ rows.append([
199
+ masked_url,
200
+ info.get("status", "-"),
201
+ info.get("latency", "-"),
202
+ len(info.get("assigned_keys", [])),
203
+ sample_keys,
204
+ info.get("last_checked", "-"),
205
+ ])
206
+ return pd.DataFrame(rows, columns=["Proxy", "Status", "Latency (ms)", "#Keys", "Sample Keys", "Last check"])
207
+
208
+ @session_wrapper
209
+ def test_proxy_once(url: str, timeout=3): # giảm timeout từ 6 -> 3
210
+ import urllib3
211
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
212
+ proxies = {"http": url, "https": url}
213
+ t0 = time.time()
214
+ try:
215
+ r = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=timeout, verify=False)
216
+ if r.status_code == 200:
217
+ return {"status": "✅ OK", "latency": int((time.time()-t0)*1000)}
218
+ except Exception as e:
219
+ return {"status": f"⚠️ {str(e).split(' ')[0]}", "latency": None}
220
+ return {"status": "❌ Failed", "latency": None}
221
+
222
+ @session_wrapper
223
+ def add_and_test_proxies(text, proxies_state):
224
+ try:
225
+ proxies = proxies_state.copy()
226
+ added = 0
227
+ for line in text.strip().splitlines():
228
+ p = line.strip()
229
+ if not p: continue
230
+ if p not in proxies:
231
+ proxies[p] = {"assigned_keys": []}
232
+ added += 1
233
+ proxies[p].update(test_proxy_once(p))
234
+ proxies[p]["last_checked"] = datetime.utcnow().isoformat(timespec="seconds")
235
+ return format_proxy_table(proxies), f"✅ Đã thêm/kiểm tra {added} proxy.", proxies
236
+ except Exception as e:
237
+ msg = log_exception(e, "add_and_test_proxies")
238
+ return None, msg, proxies_state
239
+
240
+ @session_wrapper
241
+ def refresh_proxy_status(proxies_state):
242
+ proxies = proxies_state.copy()
243
+ for url in proxies:
244
+ proxies[url].update(test_proxy_once(url))
245
+ proxies[url]["last_checked"] = datetime.utcnow().isoformat(timespec="seconds")
246
+ return format_proxy_table(proxies), "🔄 Đã refresh proxy.", proxies
247
+
248
+ @session_wrapper
249
+ def get_proxy_of_key(key, proxies):
250
+ for url, info in proxies.items():
251
+ if key in info.get("assigned_keys", []):
252
+ return url
253
+ return ""
254
+
255
+ @session_wrapper
256
+ def assign_proxy_to_key(proxy_display, api_key_display, proxies_state, api_keys_state):
257
+ proxy_url = get_real_proxy_from_display(proxy_display, proxies_state)
258
+ api_key = get_real_key_from_display(api_key_display, api_keys_state)
259
+ proxies = proxies_state.copy()
260
+ keys = api_keys_state.copy()
261
+ if proxy_url not in proxies:
262
+ return format_proxy_table(proxies), "❌ Proxy không tồn tại!", proxies, keys
263
+ status = proxies[proxy_url].get("status", "")
264
+ if not (status.startswith("✅") or "HTTPSConnectionPool" in status):
265
+ return format_proxy_table(proxies), "❌ Proxy không hoạt động!", proxies, keys
266
+ if api_key not in keys:
267
+ return format_proxy_table(proxies), "❌ API Key không tồn tại!", proxies, keys
268
+ for info in proxies.values():
269
+ if api_key in info.get("assigned_keys", []):
270
+ info["assigned_keys"].remove(api_key)
271
+ proxies[proxy_url].setdefault("assigned_keys", []).append(api_key)
272
+ return format_proxy_table(proxies), "✅ Đã gắn key.", proxies, keys
273
+
274
+ @session_wrapper
275
+ def smart_proxy_assignment(proxies_state, api_keys_state):
276
+ proxies = proxies_state.copy()
277
+ keys = api_keys_state.copy()
278
+ active_proxies = []
279
+ for url, info in proxies.items():
280
+ status = info.get("status", "")
281
+ if status.startswith("✅") or "HTTPSConnectionPool" in status:
282
+ active_proxies.append((url, info))
283
+ if not active_proxies:
284
+ return [], list(keys.keys()), "⚠️ Không gắn proxy – Đang dùng IP thật (Vẫn ổn nếu xài dưới 3 Key/ngày).", proxies
285
+ for url, info in proxies.items():
286
+ info["assigned_keys"] = []
287
+ key_list = list(keys.keys())
288
+ random.shuffle(key_list)
289
+ random.shuffle(active_proxies)
290
+ num_keys = len(key_list)
291
+ num_proxies = len(active_proxies)
292
+ assigned_keys = []
293
+ unassigned_keys = []
294
+ if num_proxies >= num_keys:
295
+ for i, key in enumerate(key_list):
296
+ active_proxies[i][1]["assigned_keys"].append(key)
297
+ assigned_keys.append(key)
298
+ message = f"✅ Gắn 1:1, {len(assigned_keys)} key được gắn với {len(assigned_keys)} proxy, dư {num_proxies - num_keys} proxy."
299
+ elif num_keys < 3 * num_proxies:
300
+ keys_per_proxy_base = num_keys // num_proxies
301
+ extra_keys = num_keys % num_proxies
302
+ key_index = 0
303
+ proxies_with_extra = 0
304
+ proxies_normal = 0
305
+ for i, (url, info) in enumerate(active_proxies):
306
+ keys_for_this_proxy = keys_per_proxy_base + (1 if i < extra_keys else 0)
307
+ for _ in range(keys_for_this_proxy):
308
+ if key_index < len(key_list):
309
+ info["assigned_keys"].append(key_list[key_index])
310
+ assigned_keys.append(key_list[key_index])
311
+ key_index += 1
312
+ if keys_for_this_proxy > keys_per_proxy_base:
313
+ proxies_with_extra += 1
314
+ else:
315
+ proxies_normal += 1
316
+ if extra_keys > 0:
317
+ message = f"✅ Phân bổ hợp lý: {proxies_with_extra} proxy nhận {keys_per_proxy_base + 1} key, {proxies_normal} proxy nhận {keys_per_proxy_base} key."
318
+ else:
319
+ message = f"✅ Phân bổ đều: mỗi proxy nhận {keys_per_proxy_base} key."
320
+ else:
321
+ max_assignable = 3 * num_proxies
322
+ keys_to_assign = key_list[:max_assignable]
323
+ unassigned_keys = key_list[max_assignable:]
324
+ key_index = 0
325
+ for url, info in active_proxies:
326
+ for _ in range(3):
327
+ if key_index < len(keys_to_assign):
328
+ info["assigned_keys"].append(keys_to_assign[key_index])
329
+ assigned_keys.append(keys_to_assign[key_index])
330
+ key_index += 1
331
+ message = f"✅ Mỗi proxy gắn 3 key, {len(assigned_keys)}/{num_keys} key được gắn."
332
+ if unassigned_keys:
333
+ message += f" ⚠️ {len(unassigned_keys)} key chưa gắn: {', '.join([mask_api_key(k) for k in unassigned_keys[:3]])}{'...' if len(unassigned_keys) > 3 else ''}"
334
+ return assigned_keys, unassigned_keys, message, proxies
335
+
336
+ @session_wrapper
337
+ def auto_assign(proxies_state, api_keys_state):
338
+ proxy_table, _, proxies = refresh_proxy_status(proxies_state)
339
+ assigned_keys, unassigned_keys, message, proxies = smart_proxy_assignment(proxies, api_keys_state)
340
+ return proxy_table, message, proxies
341
+
342
+ @session_wrapper
343
+ def delete_bad(proxies_state):
344
+ proxies = proxies_state.copy()
345
+ rem = []
346
+ for u, i in list(proxies.items()):
347
+ bad = i.get("status", "").startswith(("❌", "⚠️"))
348
+ if bad:
349
+ rem.append(u)
350
+ proxies.pop(u)
351
+ return format_proxy_table(proxies), f"🗑️ Đã xoá {len(rem)} proxy lỗi.", proxies
352
+
353
+ @session_wrapper
354
+ def filter_bad_proxies(proxies_state):
355
+ proxies = proxies_state.copy()
356
+ bad_proxies = {}
357
+ for url, info in proxies.items():
358
+ is_bad = info.get("status", "").startswith(("❌", "⚠️"))
359
+ if is_bad:
360
+ bad_proxies[url] = info
361
+ return format_proxy_table(bad_proxies)
362
+
363
+ # === Voice helpers ===
364
+ @session_wrapper
365
+ def get_voice_list(voices_state):
366
+ return list(voices_state.keys())
367
+
368
+ @session_wrapper
369
+ def get_default_voice(voices_state):
370
+ lst = get_voice_list(voices_state)
371
+ return lst[0] if lst else None
372
+
373
+ @session_wrapper
374
+ def save_voice(name, voice_id, current_voice, voices_state):
375
+ try:
376
+ if not name or not voice_id:
377
+ return "❌ Cần nhập tên và ID!", get_voice_list(voices_state), get_voice_list(voices_state), current_voice, voices_state
378
+ voices = voices_state.copy()
379
+ for n, v in voices.items():
380
+ if v.get("voice_id") == voice_id and n != name:
381
+ return f"❌ Voice ID đã tồn tại ở '{n}'.", get_voice_list(voices), get_voice_list(voices), current_voice, voices
382
+ voices[name] = {"voice_id": voice_id, "settings": DEFAULT_VOICE_SETTINGS.copy()}
383
+ vl = get_voice_list(voices)
384
+ return f"✅ Đã lưu '{name}'", vl, vl, name, voices
385
+ except Exception as e:
386
+ msg = log_exception(e, "save_voice")
387
+ return msg, get_voice_list(voices_state), get_voice_list(voices_state), current_voice, voices_state
388
+
389
+ @session_wrapper
390
+ def load_voice_for_edit(name, voices_state):
391
+ voices = voices_state.copy()
392
+ v = voices.get(name, {})
393
+ cfg = v.get("settings", DEFAULT_VOICE_SETTINGS.copy())
394
+ if not name:
395
+ return "", "", *DEFAULT_VOICE_SETTINGS.values()
396
+ voice_id = v.get("voice_id", "")
397
+ masked_voice_id = mask_voice_id(voice_id)
398
+ return name, masked_voice_id, cfg["speed"], cfg["stability"], cfg["similarity_boost"], cfg["style_exaggeration"], cfg["use_speaker_boost"]
399
+
400
+ @session_wrapper
401
+ def delete_voice(name, confirm, cur, voices_state):
402
+ if not name:
403
+ return "❌ Chọn voice!", get_voice_list(voices_state), get_voice_list(voices_state), cur, voices_state
404
+ if not confirm:
405
+ return "⚠️ Hãy tick vào ô xác nhận xoá!", get_voice_list(voices_state), get_voice_list(voices_state), cur, voices_state
406
+ voices = voices_state.copy()
407
+ protected_voice_ids = {
408
+ "TxGEqnHWrfWFTfGW9XjX", "TX3LPaxmHKxFdv7VOQHJ", "ErXwobaYiN019PkySvjV",
409
+ "iP95p4xoKVk53GoZ742B", "VR6AewLTigWG4xSOukaG", "pNInz6obpgDQGcFmaJgB",
410
+ "nPczCjzI2devNBz1zQrb", "CwhRBWXzGAHq8TQ4Fs17", "5Q0t7uMcjvnagumLfvZi",
411
+ "29vD33N1CtxCmqQRPOHJ", "flq6f7yk4E4fJM5XTYuZ", "t0jbNlBVZ17f02VDIeMI",
412
+ "pqHfZKP75CvOlQylNhV4", "LcfcDJNUP1GQjkzn1xUU", "z9fAnlkpzviPz146aGWa",
413
+ "pMsXgVXv3BLzUgSXRplE", "XrExE9yKIg1WjnnlVkGX", "SAz9YHcvj6GT2YYXdXww",
414
+ "N2lVS1w4EtoT3dr4eOWO", "2EiwWnXFnvU5JabPnv8n", "p28fY1cl6tovhD2M4WEH",
415
+ "eC5XQ2bYx6LQHFG29bNv"
416
+ }
417
+ voice_info = voices.get(name)
418
+ if voice_info and voice_info.get("voice_id") in protected_voice_ids:
419
+ return "❌ Không được xoá voice mặc định!", get_voice_list(voices), get_voice_list(voices), cur, voices
420
+ if name in voices:
421
+ voices.pop(name)
422
+ lst = get_voice_list(voices)
423
+ new = lst[0] if lst else None
424
+ return f"🗑️ Đã xoá '{name}'", lst, lst, new, voices
425
+ return "❌ Không tìm thấy voice!", get_voice_list(voices), get_voice_list(voices), cur, voices
426
+
427
+ @session_wrapper
428
+ def reset_voice(name, confirm, cur, voices_state):
429
+ if not name:
430
+ return "❌ Chọn voice!", get_voice_list(voices_state), get_voice_list(voices_state), cur, voices_state
431
+ if not confirm:
432
+ return "⚠️ Hãy tick vào ô xác nhận reset!", get_voice_list(voices_state), get_voice_list(voices_state), cur, voices_state
433
+ voices = voices_state.copy()
434
+ if name in voices:
435
+ voices[name]["settings"] = DEFAULT_VOICE_SETTINGS.copy()
436
+ return f"✅ Đã reset '{name}'", get_voice_list(voices), get_voice_list(voices), name, voices
437
+ return "❌ Không tìm thấy voice!", get_voice_list(voices), get_voice_list(voices), cur, voices
438
+
439
+ @session_wrapper
440
+ def update_voice_cfg(name, speed, stab, sim, exag, boost, cur, voices_state):
441
+ if not name:
442
+ return "❌ Chọn voice!", get_voice_list(voices_state), get_voice_list(voices_state), cur, voices_state
443
+ voices = voices_state.copy()
444
+ voices.setdefault(name, {"voice_id": "", "settings": DEFAULT_VOICE_SETTINGS.copy()})
445
+ voices[name]["settings"] = {
446
+ "speed": speed,
447
+ "stability": stab,
448
+ "similarity_boost": sim,
449
+ "style_exaggeration": exag,
450
+ "use_speaker_boost": boost
451
+ }
452
+ return f"✅ Đã cập nhật '{name}'", get_voice_list(voices), get_voice_list(voices), name, voices
453
+
454
+ @session_wrapper
455
+ def voice_table(voices_state):
456
+ voices = voices_state.copy()
457
+ data = [[n, mask_voice_id(v.get("voice_id", "")), "✅" if v.get("settings") else "❌"] for n, v in voices.items()]
458
+ return pd.DataFrame(data, columns=["Tên Voice", "Voice ID", "Đã cấu hình"])
459
+
460
+ # === API-Key helpers ===
461
+ @session_wrapper
462
+ def dataframe_with_keys(api_keys_state, proxies_state):
463
+ keys = api_keys_state.copy()
464
+ proxies = proxies_state.copy()
465
+ rows = []
466
+ for k, v in keys.items():
467
+ masked_key = mask_api_key(k)
468
+ rows.append([masked_key, v.get("status", ""), v.get("used", 0), v.get("limit", 0), v.get("tier", ""), v.get("remaining", 0), proxy_host(get_proxy_of_key(k, proxies))])
469
+ df = pd.DataFrame(rows, columns=["API Key", "Status", "Used", "Limit", "Tier", "Remaining", "Proxy Host"])
470
+ df = df.sort_values("Remaining", ascending=True)
471
+ return df
472
+
473
+ @session_wrapper
474
+ def get_sorted_keys_by_credit(api_keys_state):
475
+ keys = api_keys_state.copy()
476
+ if not keys:
477
+ return []
478
+ sorted_keys = sorted(keys.items(), key=lambda x: x[1].get("remaining", 0))
479
+ return [k for k, v in sorted_keys]
480
+
481
+ @session_wrapper
482
+ def lowest_key(api_keys_state):
483
+ keys = api_keys_state.copy()
484
+ if not keys:
485
+ return None
486
+ return sorted(keys.items(), key=lambda x: x[1].get("remaining", float("inf")))[0][0]
487
+
488
+ @session_wrapper
489
+ def save_and_show_keys(text, api_keys_state, proxies_state):
490
+ # --- Copy state hiện tại ---
491
+ keys = api_keys_state.copy()
492
+ proxies = proxies_state.copy()
493
+
494
+ # --- Đọc input, thu list new_keys ---
495
+ new_keys = []
496
+ for line in text.strip().splitlines():
497
+ k = line.strip()
498
+ if k and k not in keys:
499
+ new_keys.append(k)
500
+
501
+ # --- Nhánh 1: nếu không có key mới ---
502
+ if not new_keys:
503
+ df = dataframe_with_keys(keys, proxies)
504
+ message = "ℹ️ Không có key mới nào."
505
+ choices = get_key_choices_for_display(keys)
506
+ lowest = choices[0] if choices else None
507
+
508
+ return (
509
+ # Tab 2 outputs: key_df, key_dd, key_del_dd, key_sel, status_out, api_keys_state, total_credit_txt
510
+ df,
511
+ gr.update(choices=choices, value=lowest), # dropdown “Chọn API Key”
512
+ gr.update(choices=choices, value=None), # dropdown “Chọn API Key để xoá”
513
+ gr.update(choices=choices, value=None), # dropdown “API Key”
514
+ message,
515
+ keys,
516
+ f"Tổng credit: {total_credit(keys):,}"
517
+ )
518
+
519
+ # --- Nhánh 2: có new_keys -> thêm vào state ---
520
+ added = len(new_keys)
521
+ for k in new_keys:
522
+ keys[k] = {"status": "⏳ Chưa kiểm tra", "remaining": 0}
523
+
524
+ # Gán proxy và kiểm tra usage cho các key mới
525
+ assigned_keys, unassigned_keys, assign_message, proxies = smart_proxy_assignment(proxies, keys)
526
+ checked = 0
527
+ for k in new_keys:
528
+ if k in assigned_keys:
529
+ keys[k] = get_api_usage(k, proxies=proxies)
530
+ checked += 1
531
+ else:
532
+ if not proxies:
533
+ keys[k] = get_api_usage(k, bypass_proxy=True, proxies=proxies)
534
+ checked += 1
535
+ else:
536
+ keys[k]["status"] = "⚠️ Chưa gắn proxy"
537
+
538
+ active_proxies = [
539
+ p for p in proxies.values()
540
+ if p.get("status", "").startswith("✅") or "HTTPSConnectionPool" in p.get("status", "")
541
+ ]
542
+ if not active_proxies:
543
+ assign_message += " ⚠️ Không có proxy – Đang dùng IP thật (Vẫn ổn nếu xài dưới 3 key/ngày)"
544
+
545
+ # Build kết quả
546
+ df = dataframe_with_keys(keys, proxies)
547
+ if not active_proxies:
548
+ assign_message = "⚠️ Không gắn proxy – Đang dùng IP thật (Vẫn ổn nếu xài dưới 3 Key/ngày)"
549
+ message = f"✅ Thêm {added} key mới, kiểm tra {checked} key. {assign_message}"
550
+ choices = get_key_choices_for_display(keys)
551
+ lowest = choices[0] if choices else None
552
+
553
+ return (
554
+ # Tab 2 outputs: key_df, key_dd, key_del_dd, key_sel, status_out, api_keys_state, total_credit_txt
555
+ df,
556
+ gr.update(choices=choices, value=lowest), # dropdown “Chọn API Key”
557
+ gr.update(choices=choices, value=None), # dropdown “Chọn API Key để xoá”
558
+ gr.update(choices=choices, value=None), # dropdown “API Key”
559
+ message,
560
+ keys,
561
+ f"Tổng credit: {total_credit(keys):,}"
562
+ )
563
+
564
+ @session_wrapper
565
+ def refresh_keys(api_keys_state, proxies_state):
566
+ keys = api_keys_state.copy()
567
+ for k in keys:
568
+ keys[k] = get_api_usage(k, proxies=proxies_state)
569
+ df = dataframe_with_keys(keys, proxies_state)
570
+ choices = get_key_choices_for_display(keys)
571
+ lowest = choices[0] if choices else None
572
+ return (
573
+ df,
574
+ gr.update(choices=choices, value=lowest), # an toàn ngay cả khi choices = []
575
+ f"Tổng credit: {total_credit(keys):,}",
576
+ gr.update(choices=choices, value=None),
577
+ keys
578
+ )
579
+ @session_wrapper
580
+ def filter_api_keys_by_credit(threshold, api_keys_state, proxies_state):
581
+ keys = api_keys_state.copy()
582
+ filtered = {k: v for k, v in keys.items() if v.get("remaining", 0) < threshold}
583
+ rows = []
584
+ for k, v in filtered.items():
585
+ masked_key = mask_api_key(k)
586
+ rows.append([masked_key, v.get("status", ""), v.get("used", 0), v.get("limit", 0), v.get("tier", ""), v.get("remaining", 0), proxy_host(get_proxy_of_key(k, proxies_state))])
587
+ df = pd.DataFrame(rows, columns=["API Key", "Status", "Used", "Limit", "Tier", "Remaining", "Proxy Host"])
588
+ return df
589
+
590
+ def remove_insufficient_keys(threshold, api_keys_state, proxies_state):
591
+ keys = api_keys_state.copy()
592
+ filtered = {k: v for k, v in keys.items() if v.get("remaining", 0) >= threshold}
593
+ choices = get_key_choices_for_display(filtered)
594
+ lowest = choices[0] if choices else None
595
+ return (
596
+ dataframe_with_keys(filtered, proxies_state),
597
+ gr.update(choices=choices, value=lowest),
598
+ gr.update(choices=choices, value=None),
599
+ gr.update(choices=choices, value=None),
600
+ filtered
601
+ )
602
+ @session_wrapper
603
+ def key_has_proxy(k, proxies_state):
604
+ return bool(get_proxy_of_key(k, proxies_state))
605
+
606
+ @session_wrapper
607
+ def tts_from_text(text, voice, model, fmt, key_display, auto, bypass_proxy, voices_state, api_keys_state, proxies_state):
608
+ if not text.strip():
609
+ return None, "Nội dung trống!", "", api_keys_state
610
+ keys = api_keys_state.copy()
611
+ proxies = proxies_state.copy()
612
+ voices = voices_state.copy()
613
+ tokens = len(text)
614
+ import urllib3
615
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
616
+ if auto:
617
+ if bypass_proxy:
618
+ c = [(k, v["remaining"]) for k, v in keys.items() if v.get("remaining", 0) >= tokens]
619
+ else:
620
+ c = [(k, v["remaining"]) for k, v in keys.items() if v.get("remaining", 0) >= tokens and key_has_proxy(k, proxies)]
621
+ if not c:
622
+ c = [(k, v["remaining"]) for k, v in keys.items() if v.get("remaining", 0) >= tokens]
623
+ bypass_proxy = True # chuyển sang dùng IP thật
624
+ if not c:
625
+ return None, "❌ Không có key đủ credit", "", keys
626
+ api_key = sorted(c, key=lambda x: x[1])[0][0]
627
+ else:
628
+ if not key_display:
629
+ return None, "❌ Chưa chọn key", "", keys
630
+ api_key = get_real_key_from_display(key_display, keys)
631
+ if not bypass_proxy and not key_has_proxy(api_key, proxies):
632
+ return None, "❌ Key chưa gắn proxy", "", keys
633
+ proxy_url = None if bypass_proxy else get_proxy_of_key(api_key, proxies)
634
+ try:
635
+ info = voices.get(voice, {})
636
+ if not info:
637
+ return None, "❌ Voice không tồn tại", "", keys
638
+ payload = {
639
+ "text": text,
640
+ "voice_settings": info.get("settings", DEFAULT_VOICE_SETTINGS),
641
+ "model_id": model
642
+ }
643
+ headers = {
644
+ "Accept": "audio/mpeg",
645
+ "Content-Type": "application/json",
646
+ "xi-api-key": api_key
647
+ }
648
+ url = f"https://api.elevenlabs.io/v1/text-to-speech/{info.get('voice_id')}"
649
+ proxies_dict = None
650
+ if not bypass_proxy and proxy_url:
651
+ proxies_dict = {"http": proxy_url, "https": proxy_url}
652
+ response = requests.post(
653
+ url,
654
+ json=payload,
655
+ headers=headers,
656
+ proxies=proxies_dict,
657
+ timeout=30,
658
+ verify=False if proxies_dict else True
659
+ )
660
+ if response.status_code != 200:
661
+ error_detail = response.text
662
+ if response.status_code == 401 and "detected_unusual_activity" in error_detail:
663
+ return None, f"❌ Key {mask_api_key(api_key)} bị chặn 'unusual activity'.", "", keys
664
+ else:
665
+ return None, f"❌ API Error {response.status_code}: {error_detail[:100]}", "", keys
666
+ ext = fmt.split('_')[0] if '_' in fmt else fmt
667
+ timestamp = int(time.time())
668
+ unique_id = str(uuid.uuid4())[:8]
669
+ filename = f"tts_{timestamp}_{unique_id}.{ext}"
670
+ temp_dir = tempfile.gettempdir()
671
+ file_path = os.path.join(temp_dir, filename)
672
+ with open(file_path, 'wb') as f:
673
+ f.write(response.content)
674
+ if not os.path.exists(file_path):
675
+ return None, "❌ Không thể tạo file audio", "", keys
676
+ keys[api_key] = get_api_usage(api_key, bypass_proxy, proxies)
677
+ proxy_status = "🔓 Direct" if bypass_proxy or not proxy_url else f"🛡️ Proxy"
678
+ success_msg = f"✅ Tạo {tokens} ký tự bằng key {mask_api_key(api_key)} ({proxy_status})"
679
+ credit_msg = f"Tổng credit: {total_credit(keys):,}"
680
+ return file_path, success_msg, credit_msg, keys
681
+ except Exception as e:
682
+ error_msg = str(e)
683
+ if "ProxyError" in error_msg or "ConnectError" in error_msg:
684
+ return None, f"❌ Lỗi kết nối proxy: {mask_proxy_url(proxy_url)}", "", keys
685
+ else:
686
+ return None, f"❌ Lỗi: {error_msg[:100]}", "", keys
687
+
688
+ @session_wrapper
689
+ def verify_key_proxy(key_display, api_keys_state, proxies_state):
690
+ api_key = get_real_key_from_display(key_display, api_keys_state)
691
+ proxy = get_proxy_of_key(api_key, proxies_state)
692
+ if not proxy:
693
+ return "🔴 Key chưa gắn proxy!"
694
+ proxies = {"http": proxy, "https": proxy}
695
+ try:
696
+ r = requests.get("https://api.elevenlabs.io/v1/user/subscription", headers={"xi-api-key": api_key}, proxies=proxies, timeout=8)
697
+ ip = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=6).json().get("ip", "-")
698
+ return f"{'✅' if r.status_code == 200 else '❌'} ElevenLabs {r.status_code} | IP via proxy: {ip}"
699
+ except Exception as e:
700
+ return f"❌ Lỗi: {str(e).split(' ')[0]}"
701
+
702
+ # === Refresh-all ===
703
+ @session_wrapper
704
+ def refresh_all(voices_state, api_keys_state, proxies_state):
705
+ voices = voices_state.copy()
706
+ keys = api_keys_state.copy()
707
+ proxies = proxies_state.copy()
708
+ voice_list = get_voice_list(voices)
709
+ key_df = dataframe_with_keys(keys, proxies)
710
+ proxy_df = format_proxy_table(proxies)
711
+ sorted_keys = get_key_choices_for_display(keys)
712
+ proxy_list = get_proxy_choices_for_display(proxies)
713
+ total_credit_msg = f"Tổng credit: {total_credit(keys):,}"
714
+ lowest = sorted_keys[0] if sorted_keys else None
715
+ return (
716
+ voice_list,
717
+ voice_list,
718
+ gr.update(choices=sorted_keys, value=lowest),
719
+ gr.update(choices=sorted_keys, value=None),
720
+ gr.update(choices=sorted_keys, value=None),
721
+ proxy_list,
722
+ total_credit_msg,
723
+ key_df,
724
+ proxy_df,
725
+ voices,
726
+ keys,
727
+ proxies,
728
+ )
729
+ @session_wrapper
730
+ def refresh_keys_complete(api_keys_state, proxies_state):
731
+ keys = api_keys_state.copy()
732
+ for k in keys:
733
+ keys[k] = get_api_usage(k, proxies=proxies_state)
734
+ key_df = dataframe_with_keys(keys, proxies_state)
735
+ sorted_keys = get_key_choices_for_display(keys)
736
+ total_credit_msg = f"Tổng credit: {total_credit(keys):,}"
737
+ lowest = sorted_keys[0] if sorted_keys else None
738
+ return (
739
+ key_df,
740
+ gr.update(choices=sorted_keys, value=lowest), # “Chọn API Key”
741
+ gr.update(choices=sorted_keys, value=None), # “Chọn API Key để xoá”
742
+ gr.update(choices=sorted_keys, value=None), # “API Key”
743
+ total_credit_msg,
744
+ lowest,
745
+ keys
746
+ )
747
+ @session_wrapper
748
+ def refresh_proxies_complete(proxies_state, api_keys_state):
749
+ proxies = proxies_state.copy()
750
+ for url in proxies:
751
+ proxies[url].update(test_proxy_once(url))
752
+ proxies[url]["last_checked"] = datetime.utcnow().isoformat(timespec="seconds")
753
+ keys = api_keys_state.copy()
754
+ key_df = dataframe_with_keys(keys, proxies)
755
+ proxy_df = format_proxy_table(proxies)
756
+ proxy_list = get_proxy_choices_for_display(proxies)
757
+ return proxy_df, "🔄 Đã refresh proxy.", proxy_list, key_df, proxies
758
+
759
+ @session_wrapper
760
+ def refresh_voices_complete(voices_state):
761
+ voices = voices_state.copy()
762
+ voice_list = get_voice_list(voices)
763
+ voice_df = voice_table(voices)
764
+ return voice_list, voice_list, voice_df, voices
765
+
766
+ # === UI ===
767
+ with gr.Blocks() as demo:
768
+ voices_state = gr.State(load_default_voices())
769
+ api_keys_state = gr.State({})
770
+ proxies_state = gr.State(load_default_proxies())
771
+
772
+ gr.Markdown("""
773
+ > 🟢 **Công cụ này hoàn toàn MIỄN PHÍ cho tất cả mọi người.**
774
+ > ❌ Vui lòng KHÔNG rao bán, đổi tên hay thương mại hoá công cụ.
775
+ > 🛡️ Mọi dữ liệu (API key, proxy) chỉ lưu tạm trong phiên, tự xóa khi đóng trình duyệt.
776
+ """)
777
+ gr.Markdown("# 🎙️ ElevenLabs TTS + Proxy Manager")
778
+ with gr.Tabs():
779
+ with gr.Tab("1. Xử lý Batch"):
780
+ with gr.Row():
781
+ voice_dd = gr.Dropdown(choices=get_voice_list(voices_state.value), value=get_default_voice(voices_state.value), label="Chọn Voice", allow_custom_value=True)
782
+ model_dd = gr.Dropdown(choices=MODELS, value=DEFAULT_MODEL, label="Model")
783
+ fmt_dd = gr.Dropdown(choices=["mp3_44100", "wav"], value=DEFAULT_FORMAT, label="Output")
784
+ with gr.Row():
785
+ key_dd = gr.Dropdown(choices=get_key_choices_for_display(api_keys_state.value), value=None, label="Chọn API Key", allow_custom_value=True)
786
+ key_credit = gr.Text(label="Credit hiện còn", interactive=False)
787
+ total_credit_txt = gr.Text(label="Tổng Credit", interactive=False)
788
+ def update_key_credit(display_key, api_keys_state):
789
+ if not display_key:
790
+ return "-"
791
+ real_key = get_real_key_from_display(display_key, api_keys_state)
792
+ return f"{api_keys_state.get(real_key, {}).get('remaining', '-'):,}"
793
+ key_dd.change(update_key_credit, [key_dd, api_keys_state], key_credit)
794
+ refresh_all_btn = gr.Button("🔄 Refresh All")
795
+ verify_btn = gr.Button("⚡ Kiểm tra Proxy của API Key")
796
+ status_out = gr.Text(label="Trạng thái")
797
+ input_txt = gr.Textbox(lines=6, label="Nội dung")
798
+ token_info = gr.Text(label="Tổng ký tự")
799
+ input_txt.change(lambda t: f"{len(t)} ký tự", input_txt, token_info)
800
+ auto_cb = gr.Checkbox(value=True, label="Tự động chọn API Key có gắn proxy")
801
+ bypass_proxy_cb = gr.Checkbox(value=False, label="🔓 Tạm thời không dùng proxy (bypass)")
802
+ generate_btn = gr.Button("🌀 Tạo giọng nói")
803
+ audio_out = gr.Audio(label="Kết quả", type="filepath")
804
+ with gr.Tab("2. Quản lý API Key"):
805
+ api_in = gr.Textbox(lines=4, label="Nhập API Key (mỗi dòng)")
806
+ save_key_btn = gr.Button("📅 Lưu & Kiểm tra")
807
+ refresh_key_btn = gr.Button("🔄 Refresh danh sách")
808
+ key_df = gr.Dataframe(label="Danh sách API Key", interactive=False)
809
+ gr.Markdown("### Xoá API Key thủ công")
810
+ key_del_dd = gr.Dropdown(choices=get_key_choices_for_display(api_keys_state.value), value=None, label="Chọn API Key để xoá", allow_custom_value=True)
811
+ key_del_btn = gr.Button("🗑️ Xoá API Key đã chọn")
812
+ gr.Markdown("### Lọc API Key theo Credit")
813
+ filter_input = gr.Number(label="Lọc các API Key có số credit dưới", value=100)
814
+ filter_btn = gr.Button("🔍 Lọc API Key")
815
+ remove_low_btn = gr.Button("❌ Xoá các key không đủ credit")
816
+ with gr.Tab("3. Quản lý Voice ID"):
817
+ with gr.Row():
818
+ v_name = gr.Textbox(label="Tên Voice")
819
+ v_id = gr.Textbox(label="Voice ID")
820
+ v_select = gr.Dropdown(choices=get_voice_list(voices_state.value), value=get_default_voice(voices_state.value), label="Chọn Voice để sửa", allow_custom_value=True)
821
+ voice_status = gr.Textbox(label="Trạng thái", interactive=False)
822
+ with gr.Row():
823
+ save_voice_btn = gr.Button("💾 Lưu Voice mới")
824
+ with gr.Row():
825
+ del_voice_btn = gr.Button("🗑️ Xoá Voice đang chọn")
826
+ with gr.Row():
827
+ speed_sl = gr.Slider(0.70, 1.20, DEFAULT_VOICE_SETTINGS["speed"], step=0.01, label="Speed")
828
+ stab_sl = gr.Slider(0, 1, DEFAULT_VOICE_SETTINGS["stability"], step=0.05, label="Stability")
829
+ sim_sl = gr.Slider(0, 1, DEFAULT_VOICE_SETTINGS["similarity_boost"], step=0.05, label="Similarity")
830
+ ex_sl = gr.Slider(0, 1, DEFAULT_VOICE_SETTINGS["style_exaggeration"], step=0.05, label="Exaggeration")
831
+ boost_cb = gr.Checkbox(value=True, label="Speaker Boost")
832
+ upd_cfg_btn = gr.Button("💾 Lưu cấu hình Voice")
833
+ reset_cfg_btn = gr.Button("↻ Reset cấu hình Voice")
834
+ refresh_v_btn = gr.Button("🔄 Làm mới danh sách Voice")
835
+ with gr.Row():
836
+ reset_confirm_cb = gr.Checkbox(value=False, label="Đồng ý reset về mặc định")
837
+ gr.HTML("")
838
+ delete_confirm_cb = gr.Checkbox(value=False, label="Đồng ý xoá voice")
839
+ voice_df = gr.Dataframe(label="Danh sách Voice", interactive=False)
840
+ with gr.Tab("4. Quản lý Proxy"):
841
+ proxy_in = gr.Textbox(lines=4, label="Nhập proxy (mỗi dòng | http://user:pass@host:port)")
842
+ add_p_btn = gr.Button("💾 Lưu & Kiểm tra")
843
+ refresh_p_btn = gr.Button("🔄 Refresh trạng thái")
844
+ proxy_df = gr.Dataframe(label="Danh sách Proxy", interactive=False)
845
+ p_status = gr.Text(label="Trạng thái")
846
+ gr.Markdown("### Xoá Proxy thủ công")
847
+ proxy_del_dd = gr.Dropdown(choices=get_proxy_choices_for_display(proxies_state.value), value=None, label="Chọn Proxy để xoá", allow_custom_value=True)
848
+ proxy_del_btn = gr.Button("🗑️ Xoá Proxy đã chọn")
849
+ gr.Markdown("### Gắn Proxy với API Key")
850
+ with gr.Row():
851
+ proxy_sel = gr.Dropdown(choices=get_proxy_choices_for_display(proxies_state.value), value=None, label="Proxy", allow_custom_value=True)
852
+ key_sel = gr.Dropdown(choices=get_key_choices_for_display(api_keys_state.value), value=None, label="API Key", allow_custom_value=True)
853
+ assign_btn = gr.Button("↔️ Gắn thủ công")
854
+ auto_btn = gr.Button("🤖 Gắn tự động thông minh")
855
+ filter_bad_btn = gr.Button("🔍 Lọc Proxy lỗi")
856
+ del_bad_btn = gr.Button("🗑️ Xoá Proxy lỗi")
857
+
858
+ # Events setup
859
+ @session_wrapper
860
+ def delete_api_key_manual(display_key, api_keys_state, proxies_state):
861
+ if not display_key:
862
+ return dataframe_with_keys(api_keys_state, proxies_state), get_key_choices_for_display(api_keys_state), get_key_choices_for_display(api_keys_state), get_key_choices_for_display(api_keys_state), "❌ Chọn API Key để xoá!", api_keys_state
863
+ real_key = get_real_key_from_display(display_key, api_keys_state)
864
+ keys = api_keys_state.copy()
865
+ if real_key in keys:
866
+ del keys[real_key]
867
+ key_list = get_key_choices_for_display(keys)
868
+ return (
869
+ dataframe_with_keys(keys, proxies_state),
870
+ gr.update(choices=key_list, value=key_list[0]), # key_dd
871
+ gr.update(choices=key_list, value=None), # key_del_dd
872
+ gr.update(choices=key_list, value=None), # key_sel
873
+ f"🗑️ Đã xoá key: {display_key}",
874
+ keys
875
+ )
876
+
877
+ @session_wrapper
878
+ def delete_proxy_manual(display_proxy, proxies_state):
879
+ if not display_proxy:
880
+ return format_proxy_table(proxies_state), get_proxy_choices_for_display(proxies_state), "❌ Chọn Proxy để xoá!", proxies_state
881
+ real_proxy = get_real_proxy_from_display(display_proxy, proxies_state)
882
+ proxies = proxies_state.copy()
883
+ if real_proxy in proxies:
884
+ del proxies[real_proxy]
885
+ proxy_list = get_proxy_choices_for_display(proxies)
886
+ return format_proxy_table(proxies), proxy_list, f"🗑️ Đã xoá proxy: {display_proxy}", proxies
887
+
888
+ @session_wrapper
889
+ def update_proxy_and_sync(text, proxies_state):
890
+ proxy_table, message, proxies = add_and_test_proxies(text, proxies_state)
891
+ proxy_choices = get_proxy_choices_for_display(proxies)
892
+ return proxy_table, message, proxy_choices, proxies
893
+
894
+ @session_wrapper
895
+ def auto_assign_and_sync(proxies_state, api_keys_state):
896
+ proxy_df_result, message, proxies = auto_assign(proxies_state, api_keys_state)
897
+ proxies_choices = get_proxy_choices_for_display(proxies)
898
+ sorted_keys = get_key_choices_for_display(api_keys_state)
899
+ api_key_df = dataframe_with_keys(api_keys_state, proxies)
900
+ return proxy_df_result, message, proxies_choices, sorted_keys, api_key_df, proxies
901
+
902
+ @session_wrapper
903
+ def assign_manual_and_sync(proxy_display, key_display, proxies_state, api_keys_state):
904
+ proxy_df_result, message, proxies, keys = assign_proxy_to_key(proxy_display, key_display, proxies_state, api_keys_state)
905
+ api_key_df = dataframe_with_keys(keys, proxies)
906
+ return proxy_df_result, message, api_key_df, proxies, keys
907
+
908
+ @session_wrapper
909
+ def delete_bad_and_sync(proxies_state):
910
+ proxy_table, message, proxies = delete_bad(proxies_state)
911
+ proxies_choices = get_proxy_choices_for_display(proxies)
912
+ return proxy_table, message, proxies_choices, proxies
913
+
914
+ # Gắn sự kiện
915
+ key_del_btn.click(
916
+ delete_api_key_manual,
917
+ [key_del_dd, api_keys_state, proxies_state],
918
+ [key_df, key_dd, key_del_dd, key_sel, status_out, api_keys_state]
919
+ )
920
+ proxy_del_btn.click(
921
+ delete_proxy_manual,
922
+ [proxy_del_dd, proxies_state],
923
+ [proxy_df, proxy_del_dd, p_status, proxies_state]
924
+ )
925
+ save_key_btn.click(
926
+ fn=save_and_show_keys,
927
+ inputs=[api_in, api_keys_state, proxies_state],
928
+ outputs=[key_df, key_dd, key_del_dd, key_sel, status_out, api_keys_state, total_credit_txt]
929
+ )
930
+ refresh_key_btn.click(
931
+ refresh_keys,
932
+ [api_keys_state, proxies_state],
933
+ [key_df, key_dd, total_credit_txt, key_sel, api_keys_state]
934
+ )
935
+ filter_btn.click(
936
+ filter_api_keys_by_credit,
937
+ [filter_input, api_keys_state, proxies_state],
938
+ key_df
939
+ )
940
+ remove_low_btn.click(
941
+ remove_insufficient_keys,
942
+ [filter_input, api_keys_state, proxies_state],
943
+ [key_df, key_dd, key_del_dd, key_sel, api_keys_state]
944
+ )
945
+ refresh_all_btn.click(
946
+ refresh_all,
947
+ [voices_state, api_keys_state, proxies_state],
948
+ [voice_dd, v_select, key_dd, key_del_dd, key_sel, proxy_sel, total_credit_txt, key_df, proxy_df, voices_state, api_keys_state, proxies_state]
949
+ )
950
+ verify_btn.click(
951
+ verify_key_proxy,
952
+ [key_dd, api_keys_state, proxies_state],
953
+ status_out
954
+ )
955
+ generate_btn.click(
956
+ tts_from_text,
957
+ [input_txt, voice_dd, model_dd, fmt_dd, key_dd, auto_cb, bypass_proxy_cb, voices_state, api_keys_state, proxies_state],
958
+ [audio_out, status_out, total_credit_txt, api_keys_state]
959
+ )
960
+ def save_voice_and_refresh(name, voice_id, current_voice, voices_state):
961
+ status, v_select_choices, voice_dd_choices, selected_voice, new_voices_state = save_voice(
962
+ name, voice_id, current_voice, voices_state
963
+ )
964
+ voice_df_data = voice_table(new_voices_state)
965
+ voice_dd_update = gr.update(choices=get_voice_list(new_voices_state), value=get_default_voice(new_voices_state))
966
+ return status, v_select_choices, voice_dd_update, selected_voice, new_voices_state, voice_df_data
967
+ save_voice_btn.click(
968
+ save_voice_and_refresh,
969
+ [v_name, v_id, v_select, voices_state],
970
+ [voice_status, v_select, voice_dd, v_select, voices_state, voice_df]
971
+ )
972
+ v_select.change(
973
+ load_voice_for_edit,
974
+ [v_select, voices_state],
975
+ [v_name, v_id, speed_sl, stab_sl, sim_sl, ex_sl, boost_cb]
976
+ )
977
+ def update_voice_cfg_and_refresh(name, speed, stab, sim, exag, boost, cur, voices_state):
978
+ status, v_select_choices, voice_dd_choices, selected_voice, new_voices_state = update_voice_cfg(
979
+ name, speed, stab, sim, exag, boost, cur, voices_state
980
+ )
981
+ voice_df_data = voice_table(new_voices_state)
982
+ return status, v_select_choices, voice_dd_choices, selected_voice, new_voices_state, voice_df_data
983
+ upd_cfg_btn.click(
984
+ update_voice_cfg_and_refresh,
985
+ [v_select, speed_sl, stab_sl, sim_sl, ex_sl, boost_cb, v_select, voices_state],
986
+ [voice_status, v_select, voice_dd, v_select, voices_state, voice_df]
987
+ )
988
+ def reset_voice_and_refresh(name, confirm, cur, voices_state):
989
+ status, v_select_choices, voice_dd_choices, selected_voice, new_voices_state = reset_voice(
990
+ name, confirm, cur, voices_state
991
+ )
992
+ voice_df_data = voice_table(new_voices_state)
993
+ voice_dd_update = gr.update(choices=get_voice_list(new_voices_state), value=get_default_voice(new_voices_state))
994
+ return status, v_select_choices, voice_dd_update, selected_voice, new_voices_state, voice_df_data
995
+ reset_cfg_btn.click(
996
+ reset_voice_and_refresh,
997
+ [v_select, reset_confirm_cb, v_select, voices_state],
998
+ [voice_status, v_select, voice_dd, v_select, voices_state, voice_df]
999
+ )
1000
+ refresh_v_btn.click(
1001
+ refresh_voices_complete,
1002
+ voices_state,
1003
+ [v_select, voice_dd, voice_df, voices_state]
1004
+ )
1005
+ def del_voice_and_refresh(name, confirm, cur, voices_state):
1006
+ status, v_select_choices, voice_dd_choices, selected_voice, new_voices_state = delete_voice(
1007
+ name, confirm, cur, voices_state
1008
+ )
1009
+ voice_df_data = voice_table(new_voices_state)
1010
+ voice_dd_update = gr.update(choices=get_voice_list(new_voices_state), value=get_default_voice(new_voices_state))
1011
+ return status, v_select_choices, voice_dd_update, selected_voice, new_voices_state, voice_df_data
1012
+ del_voice_btn.click(
1013
+ del_voice_and_refresh,
1014
+ [v_select, delete_confirm_cb, v_select, voices_state],
1015
+ [voice_status, v_select, voice_dd, v_select, voices_state, voice_df]
1016
+ )
1017
+ def add_proxy_and_refresh(text, proxies_state):
1018
+ proxy_table, message, new_proxies_state = add_and_test_proxies(text, proxies_state)
1019
+ proxy_sel_update = gr.update(choices=get_proxy_choices_for_display(new_proxies_state), value=None)
1020
+ proxy_del_dd_update = gr.update(choices=get_proxy_choices_for_display(new_proxies_state), value=None)
1021
+ return proxy_table, message, proxy_sel_update, new_proxies_state, proxy_del_dd_update
1022
+ add_p_btn.click(
1023
+ add_proxy_and_refresh,
1024
+ [proxy_in, proxies_state],
1025
+ [proxy_df, p_status, proxy_sel, proxies_state, proxy_del_dd]
1026
+ )
1027
+ refresh_p_btn.click(
1028
+ refresh_proxies_complete,
1029
+ [proxies_state, api_keys_state],
1030
+ [proxy_df, p_status, proxy_sel, key_df, proxies_state]
1031
+ )
1032
+ assign_btn.click(
1033
+ assign_manual_and_sync,
1034
+ [proxy_sel, key_sel, proxies_state, api_keys_state],
1035
+ [proxy_df, p_status, key_df, proxies_state, api_keys_state]
1036
+ )
1037
+ auto_btn.click(
1038
+ auto_assign_and_sync,
1039
+ [proxies_state, api_keys_state],
1040
+ [proxy_df, p_status, proxy_sel, key_sel, key_df, proxies_state]
1041
+ )
1042
+ filter_bad_btn.click(
1043
+ filter_bad_proxies,
1044
+ proxies_state,
1045
+ proxy_df
1046
+ )
1047
+ del_bad_btn.click(
1048
+ delete_bad_and_sync,
1049
+ proxies_state,
1050
+ [proxy_df, p_status, proxy_sel, proxies_state]
1051
+ )
1052
+ demo.load(
1053
+ voice_table,
1054
+ voices_state,
1055
+ voice_df
1056
+ )
1057
+ demo.load(
1058
+ dataframe_with_keys,
1059
+ [api_keys_state, proxies_state],
1060
+ key_df
1061
+ )
1062
+ demo.load(
1063
+ format_proxy_table,
1064
+ proxies_state,
1065
+ proxy_df
1066
+ )
1067
+ demo.load(
1068
+ lambda api_keys: f"Tổng credit: {total_credit(api_keys):,}",
1069
+ api_keys_state,
1070
+ total_credit_txt
1071
+ )
1072
+
1073
+ demo.launch(ssr_mode=False)
proxies.json ADDED
File without changes
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ gradio
2
+ pandas
3
+ requests
4
+ python-dotenv
5
+ elevenlabs
6
+ aiohttp
voices.json ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "👨‍💼Josh": {
3
+ "voice_id": "TxGEqnHWrfWFTfGW9XjX",
4
+ "settings": {
5
+ "speed": 0.83,
6
+ "stability": 0.93,
7
+ "similarity_boost": 0.95,
8
+ "style_exaggeration": 0,
9
+ "use_speaker_boost": true
10
+ }
11
+ },
12
+ "👨‍💼Liam": {
13
+ "voice_id": "TX3LPaxmHKxFdv7VOQHJ",
14
+ "settings": {
15
+ "speed": 0.81,
16
+ "stability": 0.95,
17
+ "similarity_boost": 0.95,
18
+ "style_exaggeration": 0,
19
+ "use_speaker_boost": true
20
+ }
21
+ },
22
+ "👨‍💼Antoni": {
23
+ "voice_id": "ErXwobaYiN019PkySvjV",
24
+ "settings": {
25
+ "speed": 0.84,
26
+ "stability": 0.95,
27
+ "similarity_boost": 0.95,
28
+ "style_exaggeration": 0,
29
+ "use_speaker_boost": true
30
+ }
31
+ },
32
+ "👨🏻‍🦰Chris": {
33
+ "voice_id": "iP95p4xoKVk53GoZ742B",
34
+ "settings": {
35
+ "speed": 0.81,
36
+ "stability": 0.4,
37
+ "similarity_boost": 0.95,
38
+ "style_exaggeration": 0,
39
+ "use_speaker_boost": true
40
+ }
41
+ },
42
+ "👨🏻‍🦰Arnold": {
43
+ "voice_id": "VR6AewLTigWG4xSOukaG",
44
+ "settings": {
45
+ "speed": 0.86,
46
+ "stability": 0.4,
47
+ "similarity_boost": 0.95,
48
+ "style_exaggeration": 0,
49
+ "use_speaker_boost": true
50
+ }
51
+ },
52
+ "👨🏻‍🦰Adam": {
53
+ "voice_id": "pNInz6obpgDQGcFmaJgB",
54
+ "settings": {
55
+ "speed": 0.86,
56
+ "stability": 0.4,
57
+ "similarity_boost": 0.95,
58
+ "style_exaggeration": 0,
59
+ "use_speaker_boost": true
60
+ }
61
+ },
62
+ "👨🏻‍🦰Brian": {
63
+ "voice_id": "nPczCjzI2devNBz1zQrb",
64
+ "settings": {
65
+ "speed": 0.83,
66
+ "stability": 0.96,
67
+ "similarity_boost": 0.95,
68
+ "style_exaggeration": 0,
69
+ "use_speaker_boost": true
70
+ }
71
+ },
72
+ "👨🏻‍🦰Roger": {
73
+ "voice_id": "CwhRBWXzGAHq8TQ4Fs17",
74
+ "settings": {
75
+ "speed": 0.86,
76
+ "stability": 0.4,
77
+ "similarity_boost": 0.95,
78
+ "style_exaggeration": 0,
79
+ "use_speaker_boost": true
80
+ }
81
+ },
82
+ "👨🏻‍🦰Paul": {
83
+ "voice_id": "5Q0t7uMcjvnagumLfvZi",
84
+ "settings": {
85
+ "speed": 0.86,
86
+ "stability": 0.9,
87
+ "similarity_boost": 0.95,
88
+ "style_exaggeration": 0,
89
+ "use_speaker_boost": true
90
+ }
91
+ },
92
+ "👨🏻‍🦰Drew": {
93
+ "voice_id": "29vD33N1CtxCmqQRPOHJ",
94
+ "settings": {
95
+ "speed": 0.86,
96
+ "stability": 0.9,
97
+ "similarity_boost": 0.95,
98
+ "style_exaggeration": 0,
99
+ "use_speaker_boost": true
100
+ }
101
+ },
102
+ "👴🏻Michael": {
103
+ "voice_id": "flq6f7yk4E4fJM5XTYuZ",
104
+ "settings": {
105
+ "speed": 0.83,
106
+ "stability": 0.96,
107
+ "similarity_boost": 0.95,
108
+ "style_exaggeration": 0,
109
+ "use_speaker_boost": true
110
+ }
111
+ },
112
+ "👴🏻Jessie": {
113
+ "voice_id": "t0jbNlBVZ17f02VDIeMI",
114
+ "settings": {
115
+ "speed": 0.86,
116
+ "stability": 0.9,
117
+ "similarity_boost": 0.95,
118
+ "style_exaggeration": 0,
119
+ "use_speaker_boost": true
120
+ }
121
+ },
122
+ "👴🏻Bill": {
123
+ "voice_id": "pqHfZKP75CvOlQylNhV4",
124
+ "settings": {
125
+ "speed": 0.86,
126
+ "stability": 0.9,
127
+ "similarity_boost": 0.95,
128
+ "style_exaggeration": 0,
129
+ "use_speaker_boost": true
130
+ }
131
+ },
132
+ "👩‍🦳Emily": {
133
+ "voice_id": "LcfcDJNUP1GQjkzn1xUU",
134
+ "settings": {
135
+ "stability": 0.43,
136
+ "similarity_boost": 0.28,
137
+ "style_exaggeration": 0,
138
+ "use_speaker_boost": true,
139
+ "speed": 1.05
140
+ }
141
+ },
142
+ "👩‍🦳Glinda": {
143
+ "voice_id": "z9fAnlkpzviPz146aGWa",
144
+ "settings": {
145
+ "stability": 0.43,
146
+ "similarity_boost": 0.28,
147
+ "style_exaggeration": 0,
148
+ "use_speaker_boost": true,
149
+ "speed": 1
150
+ }
151
+ },
152
+ "👩‍🦳Serena": {
153
+ "voice_id": "pMsXgVXv3BLzUgSXRplE",
154
+ "settings": {
155
+ "stability": 0.43,
156
+ "similarity_boost": 0.28,
157
+ "style_exaggeration": 0,
158
+ "use_speaker_boost": true,
159
+ "speed": 1
160
+ }
161
+ },
162
+ "👩‍🦳Matilda": {
163
+ "voice_id": "XrExE9yKIg1WjnnlVkGX",
164
+ "settings": {
165
+ "stability": 0.43,
166
+ "similarity_boost": 0.28,
167
+ "style_exaggeration": 0,
168
+ "use_speaker_boost": true,
169
+ "speed": 1
170
+ }
171
+ },
172
+ "👩‍🦳River": {
173
+ "voice_id": "SAz9YHcvj6GT2YYXdXww",
174
+ "settings": {
175
+ "stability": 0.43,
176
+ "similarity_boost": 0.28,
177
+ "style_exaggeration": 0,
178
+ "use_speaker_boost": true,
179
+ "speed": 1
180
+ }
181
+ },
182
+ "👴🏻Callum": {
183
+ "voice_id": "N2lVS1w4EtoT3dr4eOWO",
184
+ "settings": {
185
+ "stability": 0.43,
186
+ "similarity_boost": 0.28,
187
+ "style_exaggeration": 0,
188
+ "use_speaker_boost": true,
189
+ "speed": 1
190
+ }
191
+ },
192
+ "👴🏻Clyde": {
193
+ "voice_id": "2EiwWnXFnvU5JabPnv8n",
194
+ "settings": {
195
+ "speed": 0.86,
196
+ "stability": 0.9,
197
+ "similarity_boost": 0.95,
198
+ "style_exaggeration": 0,
199
+ "use_speaker_boost": true
200
+ }
201
+ },
202
+ "👨B. Patrone - Peaceful & Meditative": {
203
+ "voice_id": "p28fY1cl6tovhD2M4WEH",
204
+ "settings": {
205
+ "speed": 1.05,
206
+ "stability": 0.95,
207
+ "similarity_boost": 0.95,
208
+ "style_exaggeration": 0,
209
+ "use_speaker_boost": true
210
+ }
211
+ },
212
+ "👴🏻Drstephe": {
213
+ "voice_id": "eC5XQ2bYx6LQHFG29bNv",
214
+ "settings": {
215
+ "speed": 1.0,
216
+ "stability": 0.5,
217
+ "similarity_boost": 0.75,
218
+ "style_exaggeration": 0.0,
219
+ "use_speaker_boost": true
220
+ }
221
+ }
222
+ }