Spaces:
Sleeping
Sleeping
| import os | |
| import random | |
| import numpy as np | |
| import torch | |
| from pathlib import Path | |
| # Đảm bảo torch.load luôn map về CPU khi cần | |
| _orig_torch_load = torch.load | |
| def _torch_load_cpu(f, *args, **kwargs): | |
| if "map_location" not in kwargs: | |
| kwargs["map_location"] = torch.device("cpu") | |
| return _orig_torch_load(f, *args, **kwargs) | |
| torch.load = _torch_load_cpu | |
| from huggingface_hub import snapshot_download | |
| from chatterbox.src.chatterbox.tts import ChatterboxTTS | |
| import gradio as gr | |
| # --- CẤU HÌNH MODEL TỪ HUGGINGFACE --- | |
| MODEL_REPO = "LTTEAM/TTS_Pro" | |
| LOCAL_MODEL_DIR = Path(os.getcwd()) / "models" / "tts_pro" | |
| MODELS = {} | |
| # Download model một lần (cache) | |
| if not LOCAL_MODEL_DIR.exists(): | |
| print(f"📥 Đang tải model từ HuggingFace repo {MODEL_REPO} …") | |
| snapshot_download( | |
| repo_id=MODEL_REPO, | |
| repo_type="model", | |
| local_dir=str(LOCAL_MODEL_DIR), | |
| local_dir_use_symlinks=False | |
| ) | |
| print(f"✅ Đã tải xong vào {LOCAL_MODEL_DIR}") | |
| def get_or_load_model(device_str: str): | |
| """ | |
| Lấy hoặc load model ChatterboxTTS trên device 'cpu' hoặc 'cuda'. | |
| device_str = "cpu" hoặc "gpu". | |
| """ | |
| device = "cuda" if (device_str == "gpu" and torch.cuda.is_available()) else "cpu" | |
| if device not in MODELS: | |
| print(f"📂 Loading model lên {device} …") | |
| model = ChatterboxTTS.from_local(str(LOCAL_MODEL_DIR), device) | |
| MODELS[device] = model | |
| print(f"✅ Model đã được load lên {device}") | |
| return MODELS[device] | |
| def set_seed(seed: int): | |
| torch.manual_seed(seed) | |
| if torch.cuda.is_available(): | |
| torch.cuda.manual_seed(seed) | |
| torch.cuda.manual_seed_all(seed) | |
| random.seed(seed) | |
| np.random.seed(seed) | |
| def chunk_text(text: str, chunk_size: int = 300): | |
| """Chia text dài thành các đoạn tối đa chunk_size ký tự, giữ nguyên từ.""" | |
| words = text.split() | |
| chunks, current = [], "" | |
| for w in words: | |
| if len(current) + len(w) + 1 > chunk_size: | |
| chunks.append(current.strip()) | |
| current = w | |
| else: | |
| current = f"{current} {w}".strip() | |
| if current: | |
| chunks.append(current.strip()) | |
| return chunks | |
| def generate_tts_audio( | |
| device_choice: str, | |
| text_input: str, | |
| audio_prompt_path: str, | |
| exaggeration: float, | |
| temperature: float, | |
| seed_num: int, | |
| cfg_weight: float | |
| ): | |
| """ | |
| Sinh audio từ văn bản không giới hạn: chia thành chunk, generate từng chunk, ghép nối. | |
| Trả về (sample_rate, numpy.ndarray). | |
| """ | |
| model = get_or_load_model(device_choice) | |
| if seed_num != 0: | |
| set_seed(int(seed_num)) | |
| chunks = chunk_text(text_input, chunk_size=300) | |
| waves, sr = [], model.sr | |
| for idx, chunk in enumerate(chunks, start=1): | |
| print(f"🔊 Sinh đoạn {idx}/{len(chunks)} trên {model.device}") | |
| wav = model.generate( | |
| chunk, | |
| audio_prompt_path=audio_prompt_path, | |
| exaggeration=exaggeration, | |
| temperature=temperature, | |
| cfg_weight=cfg_weight, | |
| ) | |
| waves.append(wav.squeeze(0).cpu().numpy()) | |
| full_wave = np.concatenate(waves, axis=0) | |
| print("✅ Hoàn thành sinh toàn bộ audio.") | |
| return sr, full_wave | |
| # --- GIAO DIỆN GRADIO TIẾNG VIỆT --- | |
| with gr.Blocks(title="LTTEAM TTS") as demo: | |
| gr.Markdown( | |
| """ | |
| # LTTEAM TTS | |
| **Phát triển bởi: Lý Trần** | |
| Ứng dụng chuyển văn bản thành giọng nói chất lượng cao, hỗ trợ đầu vào không giới hạn. | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| device_choice = gr.Radio( | |
| choices=["cpu", "gpu"], | |
| value="gpu" if torch.cuda.is_available() else "cpu", | |
| label="Chọn thiết bị" | |
| ) | |
| text = gr.Textbox( | |
| label="Văn bản (không giới hạn độ dài)", | |
| lines=8, | |
| placeholder="Dán hoặc nhập văn bản vào đây..." | |
| ) | |
| ref_wav = gr.Audio( | |
| sources=["upload", "microphone"], | |
| type="filepath", | |
| label="Âm thanh mẫu (tùy chọn)", | |
| value="brian.wav" | |
| ) | |
| exaggeration = gr.Slider( | |
| minimum=0.25, maximum=2, step=0.05, | |
| value=0.5, | |
| label="Mức nhấn nhá (Exaggeration)" | |
| ) | |
| cfg_weight = gr.Slider( | |
| minimum=0.2, maximum=1, step=0.05, | |
| value=0.5, | |
| label="Trọng số CFG / Tốc độ" | |
| ) | |
| with gr.Accordion("Tùy chọn thêm", open=False): | |
| seed_num = gr.Number(0, label="Seed (0 = random)") | |
| temperature = gr.Slider( | |
| minimum=0.05, maximum=5, step=0.05, | |
| value=0.8, | |
| label="Nhiệt độ (Temperature)" | |
| ) | |
| run = gr.Button("Chuyển giọng", variant="primary") | |
| with gr.Column(): | |
| out_audio = gr.Audio(label="Kết quả âm thanh") | |
| run.click( | |
| fn=generate_tts_audio, | |
| inputs=[device_choice, text, ref_wav, exaggeration, temperature, seed_num, cfg_weight], | |
| outputs=[out_audio], | |
| ) | |
| if __name__ == "__main__": | |
| # Phát hiện Colab qua biến môi trường | |
| is_colab = "COLAB_GPU" in os.environ | |
| if is_colab: | |
| demo.launch(share=True) | |
| else: | |
| # Dùng host/port để hỗ trợ HuggingFace Spaces | |
| demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860))) | |