Spaces:
Sleeping
Sleeping
| # app-elevenlab-huggingface.py | |
| # -*- coding: utf-8 -*- | |
| import os, re, time, random, json, base64 | |
| import requests | |
| import uuid | |
| import platform | |
| import warnings | |
| from typing import List, Dict, Optional, Tuple | |
| import concurrent.futures | |
| import urllib3 | |
| import gradio as gr | |
| from datetime import datetime | |
| import threading | |
| import subprocess | |
| import sys | |
| warnings.filterwarnings("ignore") | |
| urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) | |
| # ==================== FIXED FUNCTIONS FROM COLAB ==================== | |
| try: | |
| from pydub import AudioSegment | |
| import natsort | |
| PYDUB_AVAILABLE = True | |
| except ImportError: | |
| print("⚠️ pydub không khả dụng, cài đặt bằng: pip install pydub") | |
| PYDUB_AVAILABLE = False | |
| def merge_audio_files(input_folder, format, output_filename, silence_ms=300): | |
| """Hàm merge audio từ Colab - đã fix""" | |
| if not PYDUB_AVAILABLE: | |
| raise ImportError("pydub chưa được cài đặt") | |
| audio_files = [f for f in os.listdir(input_folder) if f.endswith(f".{format.lower()}")] | |
| if not audio_files: | |
| print("❌ Không có file âm thanh nào trong thư mục") | |
| return None | |
| audio_files = natsort.natsorted(audio_files) | |
| # Mở file âm thanh đầu tiên để tạo đối tượng AudioSegment | |
| try: | |
| combined = AudioSegment.from_file(os.path.join(input_folder, audio_files[0]), format=format.lower()) | |
| except Exception as e: | |
| print(f"❌ Lỗi khi đọc file đầu tiên: {e}") | |
| return None | |
| # Lặp qua các file còn lại và nối chúng lại | |
| for audio_file in audio_files[1:]: | |
| try: | |
| audio = AudioSegment.from_file(os.path.join(input_folder, audio_file), format=format.lower()) | |
| combined += AudioSegment.silent(duration=silence_ms) # Thêm khoảng nghỉ | |
| combined += audio # Nối âm thanh vào cuối | |
| except Exception as e: | |
| print(f"❌ Lỗi khi xử lý file {audio_file}: {e}") | |
| continue | |
| # Lưu file âm thanh đã gộp lại | |
| output_path = f"{output_filename}.{format.lower()}" | |
| try: | |
| combined.export(output_path, format=format.lower()) | |
| # Kiểm tra file lỗi 1KB | |
| if os.path.exists(output_path) and os.path.getsize(output_path) <= 1024: | |
| print(f"⚠️ File {output_path} chỉ có {os.path.getsize(output_path)} bytes, tạo lại...") | |
| os.remove(output_path) | |
| combined.export(output_path, format=format.lower()) | |
| print(f"✅ Đã tạo lại file {output_path}, kích thước mới: {os.path.getsize(output_path)} bytes") | |
| return output_path | |
| except Exception as e: | |
| print(f"❌ Lỗi khi export file: {e}") | |
| return None | |
| def parse_text_blocks(raw_text, max_length=200): | |
| """Phân chia văn bản thành các đoạn nhỏ - từ Colab""" | |
| blocks = [] | |
| sentences = re.split(r'(?<=[.!?])\s+', raw_text) | |
| current_block = "" | |
| for sentence in sentences: | |
| if len(current_block) + len(sentence) + 1 <= max_length: | |
| if current_block: | |
| current_block += " " + sentence | |
| else: | |
| current_block = sentence | |
| else: | |
| if current_block: | |
| blocks.append(current_block.strip()) | |
| current_block = sentence | |
| if current_block.strip(): | |
| blocks.append(current_block.strip()) | |
| return blocks | |
| def estimate_credit(text): | |
| """Ước tính credit cần thiết - từ Colab""" | |
| return len(text) + 50 | |
| def ms_to_srt_time(ms): | |
| """Chuyển milliseconds sang định dạng thời gian SRT""" | |
| h = ms // 3600000 | |
| m = (ms % 3600000) // 60000 | |
| s = (ms % 60000) // 1000 | |
| ms = ms % 1000 | |
| return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}" | |
| def create_srt(voice_dir, texts, silence_ms=300): | |
| """Tạo file phụ đề SRT - từ Colab""" | |
| if not PYDUB_AVAILABLE: | |
| return None | |
| files_audio = natsort.natsorted([f for f in os.listdir(voice_dir) if f.startswith("voice_")]) | |
| if not files_audio: | |
| return None | |
| current_time = 0 | |
| srt_lines = [] | |
| for idx, (fname, text) in enumerate(zip(files_audio, texts), start=1): | |
| try: | |
| audio = AudioSegment.from_file(os.path.join(voice_dir, fname)) | |
| start = current_time | |
| end = start + len(audio) | |
| srt_lines.append(str(idx)) | |
| srt_lines.append(f"{ms_to_srt_time(start)} --> {ms_to_srt_time(end)}") | |
| srt_lines.append(text.strip()) | |
| srt_lines.append("") | |
| current_time = end + silence_ms | |
| except Exception as e: | |
| print(f"❌ Lỗi khi xử lý file {fname}: {e}") | |
| continue | |
| if srt_lines: | |
| srt_path = os.path.join(voice_dir, "output_full.srt") | |
| try: | |
| with open(srt_path, "w", encoding="utf-8") as f: | |
| f.write("\n".join(srt_lines)) | |
| return srt_path | |
| except Exception as e: | |
| print(f"❌ Lỗi khi tạo file SRT: {e}") | |
| return None | |
| # ==================== ENHANCED SECURITY AND PROXY FUNCTIONS ==================== | |
| class PrivacyProtector: | |
| """Lớp bảo vệ quyền riêng tư nâng cao""" | |
| def get_random_user_agent(): | |
| user_agents = [ | |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", | |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0", | |
| "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", | |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0", | |
| "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" | |
| ] | |
| return random.choice(user_agents) | |
| def get_obfuscated_headers(api_key): | |
| return { | |
| "xi-api-key": api_key, | |
| "Content-Type": "application/json", | |
| "User-Agent": PrivacyProtector.get_random_user_agent(), | |
| "Accept": "application/json", | |
| "Accept-Language": "en-US,en;q=0.9", | |
| "Accept-Encoding": "gzip, deflate, br", | |
| "Connection": "keep-alive", | |
| "Cache-Control": "no-cache", | |
| "Pragma": "no-cache" | |
| } | |
| def create_secure_session(proxy_manager=None, use_proxy=False): | |
| session = requests.Session() | |
| session.timeout = 30 | |
| session.headers.update({ | |
| "User-Agent": PrivacyProtector.get_random_user_agent(), | |
| "Accept-Encoding": "gzip, deflate", | |
| "Connection": "keep-alive" | |
| }) | |
| return session | |
| class EnhancedProxyManager: | |
| """Quản lý proxy nâng cao với khả năng kiểm tra với ElevenLabs""" | |
| def __init__(self): | |
| self.proxy_list = [] | |
| self.alive_proxies = [] | |
| self.current_proxy = None | |
| self.timeout = 10 | |
| self.api_key_for_check = None | |
| self.test_url = "https://api.elevenlabs.io/v1/user" | |
| def add_proxy(self, proxy): | |
| if proxy and proxy not in self.proxy_list: | |
| if '://' not in proxy: | |
| proxy = f"http://{proxy}" | |
| self.proxy_list.append(proxy) | |
| def load_from_file(self, file_path): | |
| try: | |
| with open(file_path, 'r', encoding='utf-8') as f: | |
| proxies = [line.strip() for line in f if line.strip()] | |
| self.proxy_list.extend(proxies) | |
| return True | |
| except Exception as e: | |
| print(f"❌ Lỗi đọc file proxy: {e}") | |
| return False | |
| def check_with_elevenlabs(self, proxy, api_key): | |
| """Kiểm tra proxy với ElevenLabs (nhanh 1-5 giây)""" | |
| try: | |
| proxies = { | |
| 'http': proxy, | |
| 'https': proxy | |
| } | |
| headers = { | |
| 'xi-api-key': api_key, | |
| 'User-Agent': PrivacyProtector.get_random_user_agent() | |
| } | |
| start_time = time.time() | |
| timeout = random.uniform(1, 3) # Timeout ngắn để kiểm tra nhanh | |
| try: | |
| response = requests.get( | |
| self.test_url, | |
| headers=headers, | |
| proxies=proxies, | |
| timeout=timeout, | |
| verify=False | |
| ) | |
| response_time = time.time() - start_time | |
| if response.status_code == 200: | |
| return True, f"✅ ElevenLabs OK ({response_time:.2f}s)" | |
| else: | |
| return False, f"❌ API Error: {response.status_code}" | |
| except requests.exceptions.Timeout: | |
| return False, f"⏱️ Timeout ({timeout:.1f}s)" | |
| except requests.exceptions.ConnectionError: | |
| return False, f"🔌 Connection failed" | |
| except Exception as e: | |
| return False, f"❌ Error: {str(e)[:30]}" | |
| except Exception as e: | |
| return False, f"❌ Check Error: {str(e)[:30]}" | |
| def check_all_with_elevenlabs(self, api_key, max_workers=10): | |
| """Kiểm tra tất cả proxy với ElevenLabs""" | |
| if not self.proxy_list: | |
| return [] | |
| self.api_key_for_check = api_key | |
| self.alive_proxies = [] | |
| print(f"🔍 Kiểm tra {len(self.proxy_list)} proxy với ElevenLabs...") | |
| def check_single(proxy): | |
| return self.check_with_elevenlabs(proxy, api_key) | |
| with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: | |
| future_to_proxy = {executor.submit(check_single, proxy): proxy for proxy in self.proxy_list} | |
| for future in concurrent.futures.as_completed(future_to_proxy): | |
| proxy = future_to_proxy[future] | |
| try: | |
| is_alive, message = future.result() | |
| if is_alive: | |
| self.alive_proxies.append(proxy) | |
| print(f"✅ {proxy} - {message}") | |
| else: | |
| print(f"❌ {proxy} - {message}") | |
| except Exception as e: | |
| print(f"❌ {proxy} - Exception: {e}") | |
| print(f"✅ Tìm thấy {len(self.alive_proxies)} proxy sống với ElevenLabs") | |
| return self.alive_proxies | |
| def get_random_proxy(self): | |
| if self.alive_proxies: | |
| proxy = random.choice(self.alive_proxies) | |
| self.current_proxy = proxy | |
| return proxy | |
| return None | |
| def get_stats(self): | |
| total = len(self.proxy_list) | |
| alive = len(self.alive_proxies) | |
| return { | |
| "total": total, | |
| "alive": alive, | |
| "dead": total - alive, | |
| "percentage": (alive / total * 100) if total > 0 else 0 | |
| } | |
| # ==================== CORE TTS FUNCTIONS ==================== | |
| def check_api_key(api_key, proxy_manager=None, use_proxy=False): | |
| """Kiểm tra API key - FIXED VERSION""" | |
| try: | |
| session = PrivacyProtector.create_secure_session() | |
| headers = PrivacyProtector.get_obfuscated_headers(api_key) | |
| # Thêm proxy nếu cần | |
| proxies = None | |
| if use_proxy and proxy_manager and proxy_manager.current_proxy: | |
| proxies = { | |
| 'http': proxy_manager.current_proxy, | |
| 'https': proxy_manager.current_proxy | |
| } | |
| time.sleep(random.uniform(1, 2)) | |
| response = session.get( | |
| "https://api.elevenlabs.io/v1/user", | |
| headers=headers, | |
| proxies=proxies, | |
| timeout=15, | |
| verify=False | |
| ) | |
| if response.status_code == 200: | |
| data = response.json() | |
| sub = data.get("subscription", {}) | |
| remaining = sub.get("character_limit", 0) - sub.get("character_count", 0) | |
| return { | |
| "valid": True, | |
| "remaining": remaining, | |
| "total": sub.get("character_limit", 0), | |
| "character_count": sub.get("character_count", 0) | |
| } | |
| else: | |
| return {"valid": False, "error": f"HTTP {response.status_code}"} | |
| except Exception as e: | |
| return {"valid": False, "error": str(e)} | |
| def generate_voice(text, api_key, voice_id, model_id, stability=0.95, similarity=0.8, | |
| style=0.4, speed=0.8, speaker_boost=True, proxy_manager=None, use_proxy=False): | |
| """Tạo giọng nói - FIXED VERSION""" | |
| # Độ trễ ngẫu nhiên | |
| time.sleep(random.uniform(2, 4)) | |
| url = f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}" | |
| headers = PrivacyProtector.get_obfuscated_headers(api_key) | |
| payload = { | |
| "text": text, | |
| "model_id": model_id, | |
| "voice_settings": { | |
| "stability": stability, | |
| "similarity_boost": similarity, | |
| "style": style, | |
| "speed": speed, | |
| "use_speaker_boost": speaker_boost | |
| } | |
| } | |
| try: | |
| session = PrivacyProtector.create_secure_session() | |
| # Thêm proxy nếu cần | |
| proxies = None | |
| if use_proxy and proxy_manager and proxy_manager.current_proxy: | |
| proxies = { | |
| 'http': proxy_manager.current_proxy, | |
| 'https': proxy_manager.current_proxy | |
| } | |
| # Xoay proxy mỗi lần request | |
| proxy_manager.current_proxy = proxy_manager.get_random_proxy() | |
| response = session.post( | |
| url, | |
| headers=headers, | |
| json=payload, | |
| proxies=proxies, | |
| timeout=60, | |
| verify=False | |
| ) | |
| if response.status_code == 200: | |
| return response.content | |
| elif response.status_code == 429: | |
| # Rate limit, đợi lâu hơn | |
| time.sleep(random.uniform(30, 60)) | |
| return None | |
| else: | |
| print(f"❌ API Error: {response.status_code}") | |
| return None | |
| except Exception as e: | |
| print(f"❌ Request Error: {e}") | |
| return None | |
| # ==================== GRADIO INTERFACE ==================== | |
| class ElevenLabsTTSApp: | |
| def __init__(self): | |
| self.proxy_manager = EnhancedProxyManager() | |
| self.use_proxy = False | |
| self.output_dir = "voices" | |
| os.makedirs(self.output_dir, exist_ok=True) | |
| self.current_process = None | |
| self.logs = ["===== ElevenLabs TTS Pro - Khởi động ứng dụng ====="] | |
| def log_message(self, message, msg_type="info"): | |
| timestamp = datetime.now().strftime("%H:%M:%S") | |
| log_entry = f"[{timestamp}] {message}" | |
| self.logs.append(log_entry) | |
| print(log_entry) | |
| return "\n".join(self.logs[-50:]) # Giữ 50 dòng gần nhất | |
| def check_api_keys(self, api_keys_text): | |
| """Kiểm tra API keys""" | |
| api_keys = [k.strip() for k in api_keys_text.splitlines() if k.strip()] | |
| if not api_keys: | |
| return self.log_message("❌ Vui lòng nhập API key!", "error") | |
| results = [] | |
| self.log_message("🔍 Đang kiểm tra API keys...") | |
| for i, key in enumerate(api_keys, 1): | |
| info = check_api_key(key, self.proxy_manager, self.use_proxy) | |
| if info.get("valid"): | |
| remaining = info.get("remaining", 0) | |
| results.append(f"✅ Key {i}: Hợp lệ, còn {remaining} ký tự") | |
| self.log_message(f"✅ Key {i} hợp lệ: {remaining} ký tự còn lại") | |
| else: | |
| results.append(f"❌ Key {i}: Không hợp lệ ({info.get('error', 'Lỗi')})") | |
| self.log_message(f"❌ Key {i} không hợp lệ: {info.get('error', 'Lỗi')}") | |
| return "\n".join(results) | |
| def check_proxies(self, api_key, proxies_text): | |
| """Kiểm tra proxy với ElevenLabs""" | |
| if not api_key.strip(): | |
| return self.log_message("❌ Vui lòng nhập API key để kiểm tra proxy!", "error") | |
| proxies = [p.strip() for p in proxies_text.splitlines() if p.strip()] | |
| if not proxies: | |
| return self.log_message("❌ Không có proxy để kiểm tra!", "error") | |
| # Cập nhật proxy manager | |
| self.proxy_manager.proxy_list = proxies | |
| self.log_message(f"🔍 Đang kiểm tra {len(proxies)} proxy với ElevenLabs...") | |
| alive_proxies = self.proxy_manager.check_all_with_elevenlabs(api_key.strip()) | |
| result = f"Kết quả kiểm tra:\n" | |
| result += f"✅ Proxy sống: {len(alive_proxies)}\n" | |
| result += f"❌ Proxy chết: {len(proxies) - len(alive_proxies)}\n" | |
| result += f"📊 Tỉ lệ sống: {(len(alive_proxies)/len(proxies)*100):.1f}%\n\n" | |
| if alive_proxies: | |
| result += "Proxy sống:\n" | |
| for i, proxy in enumerate(alive_proxies[:10], 1): | |
| result += f"{i}. {proxy}\n" | |
| if len(alive_proxies) > 10: | |
| result += f"... và {len(alive_proxies) - 10} proxy khác" | |
| return result | |
| def generate_voices(self, api_keys_text, voice_id, text, model, fmt, | |
| stability, similarity, style, speed, speaker_boost, progress=gr.Progress()): | |
| """Tạo giọng nói""" | |
| try: | |
| # Parse inputs | |
| api_keys = [k.strip() for k in api_keys_text.splitlines() if k.strip()] | |
| voice_id = voice_id.strip() | |
| text = text.strip() | |
| if not api_keys: | |
| return None, self.log_message("❌ Vui lòng nhập API key!", "error") | |
| if not voice_id: | |
| return None, self.log_message("❌ Vui lòng nhập Voice ID!", "error") | |
| if not text: | |
| return None, self.log_message("❌ Vui lòng nhập văn bản!", "error") | |
| self.log_message("=" * 60) | |
| self.log_message("🚀 BẮT ĐẦU QUÁ TRÌNH TẠO GIỌNG NÓI") | |
| self.log_message(f"🔑 Số API keys: {len(api_keys)}") | |
| self.log_message(f"🎤 Voice ID: {voice_id}") | |
| self.log_message(f"🤖 Model: {model}") | |
| self.log_message(f"📁 Format: {fmt}") | |
| self.log_message("=" * 60) | |
| # Parse text blocks | |
| texts = parse_text_blocks(text) | |
| if not texts: | |
| return None, self.log_message("❌ Không thể phân tích văn bản!", "error") | |
| self.log_message(f"📝 Số đoạn văn bản: {len(texts)}") | |
| # Create output directory | |
| output_dir = os.path.join(self.output_dir, f"output_{int(time.time())}") | |
| os.makedirs(output_dir, exist_ok=True) | |
| # Check API keys | |
| valid_keys = [] | |
| for i, key in enumerate(api_keys, 1): | |
| progress((i-1)/len(api_keys), f"Kiểm tra API key {i}/{len(api_keys)}") | |
| info = check_api_key(key, self.proxy_manager, self.use_proxy) | |
| if info.get("valid"): | |
| remaining = info.get("remaining", 0) | |
| valid_keys.append([key, remaining]) | |
| self.log_message(f"✅ Key {i} hợp lệ: {remaining} ký tự còn lại") | |
| else: | |
| self.log_message(f"❌ Key {i} không hợp lệ: {info.get('error', 'Lỗi')}") | |
| if not valid_keys: | |
| return None, self.log_message("❌ Không có API key hợp lệ!", "error") | |
| # Generate voices | |
| current_key_index = 0 | |
| generated_files = [] | |
| for i, text_block in enumerate(texts, 1): | |
| progress((i-1)/len(texts), f"Đang tạo file {i}/{len(texts)}") | |
| self.log_message(f"🎤 Đang tạo file {i}/{len(texts)}...") | |
| success = False | |
| for attempt in range(3): # Thử 3 lần | |
| key, remaining = valid_keys[current_key_index] | |
| need = estimate_credit(text_block) | |
| if remaining < need: | |
| self.log_message(f"⚠ Key không đủ ngạch: {remaining}/{need}") | |
| current_key_index = (current_key_index + 1) % len(valid_keys) | |
| continue | |
| audio_data = generate_voice( | |
| text_block, key, voice_id, model, | |
| stability=stability, | |
| similarity=similarity, | |
| style=style, | |
| speed=speed, | |
| speaker_boost=speaker_boost, | |
| proxy_manager=self.proxy_manager if self.use_proxy else None, | |
| use_proxy=self.use_proxy | |
| ) | |
| if audio_data: | |
| filename = f"voice_{i:03d}.{fmt.lower()}" | |
| filepath = os.path.join(output_dir, filename) | |
| with open(filepath, "wb") as f: | |
| f.write(audio_data) | |
| if os.path.getsize(filepath) > 1024: | |
| generated_files.append(filepath) | |
| remaining -= need | |
| valid_keys[current_key_index][1] = remaining | |
| current_key_index = (current_key_index + 1) % len(valid_keys) | |
| success = True | |
| self.log_message(f"✅ Tạo thành công: {filename}") | |
| break | |
| else: | |
| self.log_message(f"⚠ File {filename} lỗi 1KB, thử lại...") | |
| else: | |
| self.log_message(f"⚠ Không thể tạo file {i}, thử lại...") | |
| time.sleep(random.uniform(2, 3)) | |
| if not success: | |
| self.log_message(f"❌ Không thể tạo file {i} sau 3 lần thử") | |
| # Merge files | |
| if generated_files: | |
| self.log_message("🔗 Đang merge file audio...") | |
| progress(0.9, "Đang merge file audio...") | |
| merged_file = merge_audio_files(output_dir, fmt, os.path.join(output_dir, "output_full")) | |
| if merged_file and os.path.exists(merged_file): | |
| self.log_message("📝 Đang tạo file phụ đề...") | |
| progress(0.95, "Đang tạo file phụ đề...") | |
| srt_file = create_srt(output_dir, texts) | |
| if srt_file: | |
| self.log_message(f"✅ Đã tạo file phụ đề: {os.path.basename(srt_file)}") | |
| self.log_message(f"🎉 Hoàn thành! File: {os.path.basename(merged_file)}") | |
| progress(1.0, "Hoàn thành!") | |
| return merged_file, self.log_message("✅ Quá trình hoàn tất!") | |
| else: | |
| return None, self.log_message("❌ Không thể merge file audio", "error") | |
| else: | |
| return None, self.log_message("❌ Không có file nào được tạo thành công", "error") | |
| except Exception as e: | |
| return None, self.log_message(f"❌ Lỗi: {str(e)}", "error") | |
| def toggle_proxy(self, use_proxy): | |
| """Bật/tắt proxy""" | |
| self.use_proxy = use_proxy | |
| if use_proxy: | |
| return self.log_message("✅ Đã bật chế độ sử dụng proxy") | |
| else: | |
| return self.log_message("🔌 Đã tắt chế độ sử dụng proxy") | |
| def clear_logs(self): | |
| """Xóa logs""" | |
| self.logs = ["===== Logs đã được xóa ====="] | |
| return "Logs đã được xóa." | |
| def get_logs(self): | |
| """Lấy logs hiện tại""" | |
| return "\n".join(self.logs[-20:]) | |
| # ==================== GRADIO UI ==================== | |
| def create_gradio_interface(): | |
| app = ElevenLabsTTSApp() | |
| with gr.Blocks(title="🎤 ElevenLabs TTS Pro - Hugging Face Edition", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("# 🎤 ElevenLabs TTS Pro - Hugging Face Edition") | |
| gr.Markdown("Công cụ chuyển văn bản thành giọng nói sử dụng ElevenLabs API") | |
| with gr.Tabs(): | |
| with gr.TabItem("⚙️ Cấu hình chính"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 🔑 API Keys") | |
| api_keys = gr.Textbox( | |
| label="API Keys (mỗi key một dòng)", | |
| placeholder="sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nsk_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy", | |
| lines=4 | |
| ) | |
| check_api_btn = gr.Button("🔍 Kiểm tra API Keys") | |
| api_check_result = gr.Textbox(label="Kết quả kiểm tra", interactive=False) | |
| gr.Markdown("### 🎤 Voice Settings") | |
| voice_id = gr.Textbox( | |
| label="Voice ID", | |
| placeholder="21m00Tcm4TlvDq8ikWAM", | |
| value="21m00Tcm4TlvDq8ikWAM" | |
| ) | |
| with gr.Row(): | |
| model = gr.Dropdown( | |
| label="Model", | |
| choices=[ | |
| "eleven_turbo_v2_5", | |
| "eleven_flash_v2_5", | |
| "eleven_multilingual_v2" | |
| ], | |
| value="eleven_multilingual_v2" | |
| ) | |
| fmt = gr.Dropdown( | |
| label="Format", | |
| choices=["MP3", "WAV"], | |
| value="MP3" | |
| ) | |
| stability = gr.Slider(label="Stability", minimum=0, maximum=1, value=0.95, step=0.01) | |
| similarity = gr.Slider(label="Similarity Boost", minimum=0, maximum=1, value=0.8, step=0.01) | |
| style = gr.Slider(label="Style", minimum=0, maximum=1, value=0.4, step=0.01) | |
| speed = gr.Slider(label="Speed", minimum=0.5, maximum=1.5, value=0.8, step=0.01) | |
| speaker_boost = gr.Checkbox(label="Speaker Boost", value=True) | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 📝 Văn bản") | |
| text_input = gr.Textbox( | |
| label="Văn bản cần chuyển đổi", | |
| placeholder="Nhập văn bản cần chuyển đổi tại đây...", | |
| lines=15 | |
| ) | |
| generate_btn = gr.Button("🚀 Bắt đầu tạo giọng nói", variant="primary") | |
| gr.Markdown("### 📤 Kết quả") | |
| audio_output = gr.Audio(label="File âm thanh đã tạo", type="filepath") | |
| gr.Markdown("### 📊 Logs") | |
| log_output = gr.Textbox(label="Logs", lines=10, interactive=False) | |
| with gr.Row(): | |
| clear_log_btn = gr.Button("🗑️ Xóa logs") | |
| refresh_log_btn = gr.Button("🔄 Làm mới logs") | |
| with gr.TabItem("🌐 Proxy Management"): | |
| gr.Markdown("### Proxy Settings") | |
| use_proxy = gr.Checkbox(label="Sử dụng proxy", value=False) | |
| proxy_status = gr.Textbox(label="Trạng thái proxy", value="TẮT", interactive=False) | |
| gr.Markdown("### Danh sách Proxy") | |
| proxies_input = gr.Textbox( | |
| label="Proxy (mỗi proxy một dòng)", | |
| placeholder="http://ip:port\nhttp://user:pass@ip:port\nsocks5://ip:port", | |
| lines=6 | |
| ) | |
| gr.Markdown("### Kiểm tra Proxy") | |
| with gr.Row(): | |
| proxy_api_key = gr.Textbox( | |
| label="API Key để kiểm tra", | |
| placeholder="Nhập API key để kiểm tra proxy", | |
| type="password" | |
| ) | |
| check_proxy_btn = gr.Button("🔍 Kiểm tra với ElevenLabs") | |
| proxy_check_result = gr.Textbox(label="Kết quả kiểm tra proxy", lines=8, interactive=False) | |
| with gr.TabItem("📖 Hướng dẫn"): | |
| gr.Markdown(""" | |
| ## 📖 Hướng dẫn sử dụng | |
| ### 1. Cấu hình API Keys | |
| - Lấy API key từ [ElevenLabs](https://elevenlabs.io/) | |
| - Nhập mỗi key trên một dòng | |
| - Có thể sử dụng nhiều key để tăng hạn ngạch | |
| ### 2. Voice Settings | |
| - **Voice ID**: ID của giọng nói từ ElevenLabs | |
| - **Model**: Chọn model phù hợp | |
| - `eleven_multilingual_v2`: Hỗ trợ đa ngôn ngữ | |
| - `eleven_turbo_v2_5`: Tốc độ cao | |
| - `eleven_flash_v2_5`: Tốc độ cực nhanh | |
| - **Format**: MP3 hoặc WAV | |
| ### 3. Voice Parameters | |
| - **Stability**: Độ ổn định giọng nói (0-1) | |
| - **Similarity Boost**: Độ tương tự với giọng gốc (0-1) | |
| - **Style**: Phong cách giọng nói (0-1) | |
| - **Speed**: Tốc độ nói (0.5-1.5) | |
| - **Speaker Boost**: Tăng cường chất lượng giọng nói | |
| ### 4. Proxy (Tùy chọn) | |
| - Thêm proxy để tránh bị rate limit | |
| - Kiểm tra proxy với ElevenLabs trước khi sử dụng | |
| - Mỗi proxy trên một dòng | |
| ### 5. Sử dụng | |
| 1. Nhập API keys | |
| 2. Nhập Voice ID | |
| 3. Nhập văn bản cần chuyển đổi | |
| 4. Điều chỉnh thông số nếu cần | |
| 5. Nhấn "🚀 Bắt đầu tạo giọng nói" | |
| ### ⚠️ Lưu ý | |
| - Giữ logs để debug nếu có lỗi | |
| - Kiểm tra API keys và proxy trước khi sử dụng | |
| - File âm thanh sẽ tự động download khi hoàn thành | |
| - Cần có API key hợp lệ từ ElevenLabs để sử dụng | |
| """) | |
| # Event handlers | |
| check_api_btn.click( | |
| fn=app.check_api_keys, | |
| inputs=[api_keys], | |
| outputs=[api_check_result] | |
| ) | |
| check_proxy_btn.click( | |
| fn=app.check_proxies, | |
| inputs=[proxy_api_key, proxies_input], | |
| outputs=[proxy_check_result] | |
| ) | |
| use_proxy.change( | |
| fn=app.toggle_proxy, | |
| inputs=[use_proxy], | |
| outputs=[log_output] | |
| ).then( | |
| fn=lambda x: "BẬT" if x else "TẮT", | |
| inputs=[use_proxy], | |
| outputs=[proxy_status] | |
| ) | |
| generate_btn.click( | |
| fn=app.generate_voices, | |
| inputs=[ | |
| api_keys, voice_id, text_input, model, fmt, | |
| stability, similarity, style, speed, speaker_boost | |
| ], | |
| outputs=[audio_output, log_output] | |
| ) | |
| clear_log_btn.click( | |
| fn=app.clear_logs, | |
| inputs=[], | |
| outputs=[log_output] | |
| ) | |
| refresh_log_btn.click( | |
| fn=app.get_logs, | |
| inputs=[], | |
| outputs=[log_output] | |
| ) | |
| # Initial load of logs | |
| demo.load( | |
| fn=app.get_logs, | |
| inputs=[], | |
| outputs=[log_output] | |
| ) | |
| return demo | |
| # ==================== MAIN ==================== | |
| if __name__ == "__main__": | |
| print("=" * 60) | |
| print("🔄 Đang khởi động ElevenLabs TTS Pro - Hugging Face Edition") | |
| print("=" * 60) | |
| # Tạo thư mục voices nếu chưa tồn tại | |
| os.makedirs("voices", exist_ok=True) | |
| # Tạo và chạy Gradio app | |
| demo = create_gradio_interface() | |
| # Lấy port từ environment variable (cần cho Hugging Face Spaces) | |
| port = int(os.environ.get("PORT", 7860)) | |
| print(f"🌐 Ứng dụng đang chạy tại: http://0.0.0.0:{port}") | |
| print("✅ Sẵn sàng sử dụng!") | |
| # Chạy ứng dụng | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=port, | |
| share=False, | |
| debug=True | |
| ) |