mr-don88 commited on
Commit
78e9885
·
verified ·
1 Parent(s): 9b83ca7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +199 -0
app.py CHANGED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import os, re, time, random
4
+ import requests
5
+ import gradio as gr
6
+ from pydub import AudioSegment
7
+ import natsort
8
+
9
+ # ================== PATH SAFE ==================
10
+ BASE_DIR = os.getcwd()
11
+ VOICE_DIR = os.path.join(BASE_DIR, "voices")
12
+ os.makedirs(VOICE_DIR, exist_ok=True)
13
+
14
+ # ================== CORE ==================
15
+ def check_api_key(api_key):
16
+ try:
17
+ r = requests.get(
18
+ "https://api.elevenlabs.io/v1/user",
19
+ headers={"xi-api-key": api_key},
20
+ timeout=10
21
+ )
22
+ if r.status_code == 200:
23
+ sub = r.json().get("subscription", {})
24
+ return {
25
+ "valid": True,
26
+ "remaining": sub.get("character_limit", 0)
27
+ - sub.get("character_count", 0)
28
+ }
29
+ return {"valid": False}
30
+ except:
31
+ return {"valid": False}
32
+
33
+
34
+ def parse_text_blocks(text, max_len=200):
35
+ blocks, cur = [], ""
36
+ for s in re.split(r'(?<=[.!?])\s+', text):
37
+ if len(cur) + len(s) <= max_len:
38
+ cur += " " + s
39
+ else:
40
+ blocks.append(cur.strip())
41
+ cur = s
42
+ if cur:
43
+ blocks.append(cur.strip())
44
+ return blocks
45
+
46
+
47
+ def estimate_credit(text):
48
+ return len(text) + 50
49
+
50
+
51
+ def generate_voice(text, api_key, voice_id, model_id,
52
+ stability, similarity, style, speed, boost):
53
+
54
+ time.sleep(random.uniform(1, 2))
55
+
56
+ url = f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}"
57
+ headers = {
58
+ "xi-api-key": api_key,
59
+ "Content-Type": "application/json"
60
+ }
61
+
62
+ payload = {
63
+ "text": text,
64
+ "model_id": model_id,
65
+ "voice_settings": {
66
+ "stability": stability,
67
+ "similarity_boost": similarity,
68
+ "style": style,
69
+ "speed": speed,
70
+ "use_speaker_boost": boost
71
+ }
72
+ }
73
+
74
+ r = requests.post(url, headers=headers, json=payload, timeout=30)
75
+ if r.status_code == 200:
76
+ return r.content
77
+ return None
78
+
79
+
80
+ def merge_audio(fmt):
81
+ files = natsort.natsorted([
82
+ f for f in os.listdir(VOICE_DIR)
83
+ if f.endswith("." + fmt.lower())
84
+ ])
85
+
86
+ if not files:
87
+ return None
88
+
89
+ combined = AudioSegment.from_file(os.path.join(VOICE_DIR, files[0]))
90
+ for f in files[1:]:
91
+ combined += AudioSegment.silent(500)
92
+ combined += AudioSegment.from_file(os.path.join(VOICE_DIR, f))
93
+
94
+ out_path = os.path.join(BASE_DIR, f"output_full.{fmt.lower()}")
95
+ combined.export(out_path, format=fmt.lower())
96
+ return out_path
97
+
98
+
99
+ # ================== MAIN ==================
100
+ def run_tts(api_keys_text, voice_id, text, model_id, fmt,
101
+ stability, similarity, style, speed, boost):
102
+
103
+ for f in os.listdir(VOICE_DIR):
104
+ os.remove(os.path.join(VOICE_DIR, f))
105
+
106
+ api_keys = [k.strip() for k in api_keys_text.splitlines() if k.strip()]
107
+ valid_keys = []
108
+
109
+ for k in api_keys:
110
+ info = check_api_key(k)
111
+ if info.get("valid") and info["remaining"] > 500:
112
+ valid_keys.append([k, info["remaining"]])
113
+
114
+ if not valid_keys:
115
+ return "❌ Không có API key hợp lệ", None
116
+
117
+ blocks = parse_text_blocks(text)
118
+ key_idx = 0
119
+
120
+ for i, block in enumerate(blocks):
121
+ success = False
122
+
123
+ while valid_keys:
124
+ key, remain = valid_keys[key_idx]
125
+ need = estimate_credit(block)
126
+
127
+ if remain < need:
128
+ valid_keys.pop(key_idx)
129
+ continue
130
+
131
+ audio = generate_voice(
132
+ block, key, voice_id, model_id,
133
+ stability, similarity, style, speed, boost
134
+ )
135
+
136
+ if audio:
137
+ path = os.path.join(
138
+ VOICE_DIR, f"voice_{i+1:03d}.{fmt.lower()}"
139
+ )
140
+ with open(path, "wb") as f:
141
+ f.write(audio)
142
+ valid_keys[key_idx][1] -= need
143
+ success = True
144
+ break
145
+ else:
146
+ valid_keys.pop(key_idx)
147
+
148
+ if not success:
149
+ return "❌ Hết API key khi đang chạy", None
150
+
151
+ merged = merge_audio(fmt)
152
+ return "✅ Hoàn tất", merged
153
+
154
+
155
+ # ================== UI ==================
156
+ with gr.Blocks() as demo:
157
+ gr.Markdown("## 🔊 ElevenLabs TTS – Hugging Face Stable")
158
+
159
+ api_keys = gr.Textbox(label="API Keys (mỗi dòng 1 key)", lines=4)
160
+ voice_id = gr.Textbox(label="Voice ID")
161
+ text = gr.Textbox(label="Text", lines=6)
162
+
163
+ model = gr.Dropdown(
164
+ choices=[
165
+ ("Turbo v2.5", "eleven_turbo_v2_5"),
166
+ ("Flash v2.5", "eleven_flash_v2_5"),
167
+ ("Multilingual v2", "eleven_multilingual_v2"),
168
+ ],
169
+ value="eleven_multilingual_v2",
170
+ label="Model"
171
+ )
172
+
173
+ fmt = gr.Dropdown(["MP3", "WAV"], value="MP3", label="Format")
174
+
175
+ stability = gr.Slider(0, 1, 0.9, label="Stability")
176
+ similarity = gr.Slider(0, 1, 0.5, label="Similarity")
177
+ style = gr.Slider(0, 1, 0.4, label="Style")
178
+ speed = gr.Slider(0.7, 1.2, 0.81, label="Speed")
179
+ boost = gr.Checkbox(True, label="Speaker Boost")
180
+
181
+ run_btn = gr.Button("🎧 Tạo giọng nói")
182
+ status = gr.Textbox(label="Status")
183
+ output_audio = gr.Audio(type="filepath", label="Output")
184
+
185
+ run_btn.click(
186
+ run_tts,
187
+ inputs=[
188
+ api_keys, voice_id, text, model, fmt,
189
+ stability, similarity, style, speed, boost
190
+ ],
191
+ outputs=[status, output_audio]
192
+ )
193
+
194
+ # ⚠️ DÒNG QUAN TRỌNG NHẤT CHO HUGGING FACE
195
+ demo.launch(
196
+ server_name="0.0.0.0",
197
+ server_port=7860,
198
+ show_error=True
199
+ )