0___0 / app (11).py
Arxords's picture
Upload 2 files
b8a499e verified
# coding=utf-8
# Qwen3-TTS Gradio Demo - Phiên bản tương thích CPU
# Hỗ trợ: Thiết kế giọng nói, Nhân bản giọng nói (Base), TTS (CustomVoice)
import os
import gradio as gr
import numpy as np
import torch
from huggingface_hub import snapshot_download, login
# Xử lý HuggingFace Token
HF_TOKEN = os.environ.get('HF_TOKEN')
if HF_TOKEN:
login(token=HF_TOKEN, add_to_git_credential=False)
else:
print("Cảnh báo: Không tìm thấy HF_TOKEN trong môi trường. Chỉ sử dụng các mô hình công khai.")
# Tùy chọn kích thước mô hình
MODEL_SIZES = ["0.6B", "1.7B"]
# Lựa chọn giọng đọc và ngôn ngữ cho mô hình CustomVoice
SPEAKERS = [
"Aiden", "Dylan", "Eric", "Ono_anna", "Ryan", "Serena", "Sohee", "Uncle_fu", "Vivian"
]
LANGUAGES = ["Tự động", "Tiếng Trung", "Tiếng Anh", "Tiếng Nhật", "Tiếng Hàn", "Tiếng Pháp", "Tiếng Đức", "Tiếng Tây Ban Nha", "Tiếng Bồ Đào Nha", "Tiếng Nga"]
# Ánh xạ ngôn ngữ tiếng Việt -> tiếng Anh (để truyền vào model)
LANGUAGE_MAP = {
"Tự động": "Auto",
"Tiếng Trung": "Chinese",
"Tiếng Anh": "English",
"Tiếng Nhật": "Japanese",
"Tiếng Hàn": "Korean",
"Tiếng Pháp": "French",
"Tiếng Đức": "German",
"Tiếng Tây Ban Nha": "Spanish",
"Tiếng Bồ Đào Nha": "Portuguese",
"Tiếng Nga": "Russian",
}
def get_model_path(model_type: str, model_size: str) -> str:
"""Lấy đường dẫn mô hình dựa trên loại và kích thước."""
return snapshot_download(
f"Qwen/Qwen3-TTS-12Hz-{model_size}-{model_type}",
token=HF_TOKEN if HF_TOKEN else None
)
# ============================================================================
# TẢI MÔ HÌNH THEO YÊU CẦU - Chỉ tải khi cần (tối ưu cho CPU)
# ============================================================================
_loaded_models = {}
def get_model(model_key: str, model_type: str, model_size: str):
"""Tải mô hình theo yêu cầu trên CPU với các tối ưu hóa."""
global _loaded_models
if model_key not in _loaded_models:
print(f"Đang tải mô hình {model_type} {model_size} lên CPU...")
try:
from qwen_tts import Qwen3TTSModel
model_path = get_model_path(model_type, model_size)
# Cấu hình CPU - không dùng flash attention, dùng float32
_loaded_models[model_key] = Qwen3TTSModel.from_pretrained(
model_path,
device_map="cpu", # Bắt buộc dùng CPU
dtype=torch.float32, # Dùng float32 cho CPU
token=HF_TOKEN if HF_TOKEN else None,
attn_implementation=None,
)
print(f"Đã tải thành công mô hình {model_type} {model_size} trên CPU!")
except Exception as e:
print(f"Lỗi khi tải mô hình: {e}")
raise
return _loaded_models[model_key]
# ============================================================================
def _normalize_audio(wav, eps=1e-12, clip=True):
"""Chuẩn hóa âm thanh về float32 trong khoảng [-1, 1]."""
x = np.asarray(wav)
if np.issubdtype(x.dtype, np.integer):
info = np.iinfo(x.dtype)
if info.min < 0:
y = x.astype(np.float32) / max(abs(info.min), info.max)
else:
mid = (info.max + 1) / 2.0
y = (x.astype(np.float32) - mid) / mid
elif np.issubdtype(x.dtype, np.floating):
y = x.astype(np.float32)
m = np.max(np.abs(y)) if y.size else 0.0
if m > 1.0 + 1e-6:
y = y / (m + eps)
else:
raise TypeError(f"Kiểu dữ liệu không được hỗ trợ: {x.dtype}")
if clip:
y = np.clip(y, -1.0, 1.0)
if y.ndim > 1:
y = np.mean(y, axis=-1).astype(np.float32)
return y
def _audio_to_tuple(audio):
"""Chuyển đổi đầu vào âm thanh từ Gradio thành tuple (wav, sr)."""
if audio is None:
return None
if isinstance(audio, tuple) and len(audio) == 2 and isinstance(audio[0], int):
sr, wav = audio
wav = _normalize_audio(wav)
return wav, int(sr)
if isinstance(audio, dict) and "sampling_rate" in audio and "data" in audio:
sr = int(audio["sampling_rate"])
wav = _normalize_audio(audio["data"])
return wav, sr
return None
def generate_voice_design(text, language, voice_description, progress=gr.Progress(track_tqdm=True)):
"""Tạo giọng nói bằng mô hình Voice Design (chỉ dùng 1.7B)."""
if not text or not text.strip():
return None, "Lỗi: Văn bản là bắt buộc."
if not voice_description or not voice_description.strip():
return None, "Lỗi: Mô tả giọng nói là bắt buộc."
lang_en = LANGUAGE_MAP.get(language, "Auto")
try:
model = get_model("voice_design_1.7B", "VoiceDesign", "1.7B")
wavs, sr = model.generate_voice_design(
text=text.strip(),
language=lang_en,
instruct=voice_description.strip(),
non_streaming_mode=True,
max_new_tokens=2048,
)
return (sr, wavs[0]), "Tạo giọng nói theo thiết kế thành công!"
except Exception as e:
return None, f"Lỗi: {type(e).__name__}: {e}"
def generate_voice_clone(ref_audio, ref_text, target_text, language, use_xvector_only, model_size, progress=gr.Progress(track_tqdm=True)):
"""Tạo giọng nói bằng mô hình Base (Nhân bản giọng nói)."""
if not target_text or not target_text.strip():
return None, "Lỗi: Văn bản cần đọc là bắt buộc."
audio_tuple = _audio_to_tuple(ref_audio)
if audio_tuple is None:
return None, "Lỗi: Âm thanh tham chiếu là bắt buộc."
if not use_xvector_only and (not ref_text or not ref_text.strip()):
return None, "Lỗi: Văn bản tham chiếu là bắt buộc khi không bật 'Chỉ dùng x-vector'."
lang_en = LANGUAGE_MAP.get(language, "Auto")
try:
model = get_model(f"base_{model_size}", "Base", model_size)
wavs, sr = model.generate_voice_clone(
text=target_text.strip(),
language=lang_en,
ref_audio=audio_tuple,
ref_text=ref_text.strip() if ref_text else None,
x_vector_only_mode=use_xvector_only,
max_new_tokens=2048,
)
return (sr, wavs[0]), "Nhân bản giọng nói thành công!"
except Exception as e:
return None, f"Lỗi: {type(e).__name__}: {e}"
def generate_custom_voice(text, language, speaker, instruct, model_size, progress=gr.Progress(track_tqdm=True)):
"""Tạo giọng nói bằng mô hình CustomVoice."""
if not text or not text.strip():
return None, "Lỗi: Văn bản là bắt buộc."
if not speaker:
return None, "Lỗi: Giọng đọc là bắt buộc."
lang_en = LANGUAGE_MAP.get(language, "Auto")
try:
model = get_model(f"custom_voice_{model_size}", "CustomVoice", model_size)
wavs, sr = model.generate_custom_voice(
text=text.strip(),
language=lang_en,
speaker=speaker.lower().replace(" ", "_"),
instruct=instruct.strip() if instruct else None,
non_streaming_mode=True,
max_new_tokens=2048,
)
return (sr, wavs[0]), "Tạo giọng nói thành công!"
except Exception as e:
return None, f"Lỗi: {type(e).__name__}: {e}"
# Xây dựng giao diện Gradio
def build_ui():
theme = gr.themes.Soft(
font=[gr.themes.GoogleFont("Source Sans Pro"), "Arial", "sans-serif"],
)
css = """
.gradio-container {max-width: none !important;}
.tab-content {padding: 20px;}
"""
with gr.Blocks(theme=theme, css=css, title="Qwen3-TTS Demo (CPU)") as demo:
gr.Markdown(
"""
# 🎙️ Demo Chuyển Văn Bản Thành Giọng Nói (CPU)
Ba chế độ tổng hợp giọng nói:
- **Thiết kế giọng nói**: Tạo giọng nói tùy chỉnh bằng mô tả ngôn ngữ tự nhiên
- **Nhân bản giọng nói**: Sao chép giọng nói từ file âm thanh tham chiếu
- **TTS (CustomVoice)**: Tổng hợp giọng nói với các giọng đọc có sẵn và tùy chỉnh phong cách
⚠️ **Chế độ CPU**: Phiên bản này chạy trên CPU. Quá trình tạo có thể chậm hơn phiên bản GPU. Vui lòng kiên nhẫn chờ.
"""
)
with gr.Tabs():
# Tab 1: Thiết kế giọng nói (chỉ dùng 1.7B)
with gr.Tab("Thiết kế giọng nói"):
gr.Markdown("### Tạo giọng nói tùy chỉnh bằng ngôn ngữ tự nhiên")
with gr.Row():
with gr.Column(scale=2):
design_text = gr.Textbox(
label="Văn bản cần đọc",
lines=4,
placeholder="Nhập văn bản bạn muốn chuyển thành giọng nói...",
value="Trong ngăn kéo trên... khoan đã, trống rỗng? Không thể nào, tôi chắc chắn đã để ở đó mà!"
)
design_language = gr.Dropdown(
label="Ngôn ngữ",
choices=LANGUAGES,
value="Tự động",
interactive=True,
)
design_instruct = gr.Textbox(
label="Mô tả giọng nói",
lines=3,
placeholder="Mô tả đặc điểm giọng nói bạn muốn...",
value="Giọng ngạc nhiên, không tin tưởng, bắt đầu có chút hoảng loạn."
)
design_btn = gr.Button("Tạo giọng nói", variant="primary")
with gr.Column(scale=2):
design_audio_out = gr.Audio(label="Âm thanh đầu ra", type="numpy")
design_status = gr.Textbox(label="Trạng thái", lines=2, interactive=False)
design_btn.click(
generate_voice_design,
inputs=[design_text, design_language, design_instruct],
outputs=[design_audio_out, design_status],
)
# Tab 2: Nhân bản giọng nói (Base)
with gr.Tab("Nhân bản giọng nói"):
gr.Markdown("### Sao chép giọng nói từ file âm thanh tham chiếu")
with gr.Row():
with gr.Column(scale=2):
clone_ref_audio = gr.Audio(
label="Âm thanh tham chiếu (Tải lên mẫu giọng nói cần sao chép)",
type="numpy",
)
clone_ref_text = gr.Textbox(
label="Văn bản tham chiếu (Nội dung được đọc trong file âm thanh tham chiếu)",
lines=2,
placeholder="Nhập chính xác văn bản được đọc trong file âm thanh tham chiếu...",
)
clone_xvector = gr.Checkbox(
label="Chỉ dùng x-vector (Không cần văn bản tham chiếu, nhưng chất lượng thấp hơn)",
value=False,
)
with gr.Column(scale=2):
clone_target_text = gr.Textbox(
label="Văn bản cần đọc (Văn bản sẽ được đọc bằng giọng đã nhân bản)",
lines=4,
placeholder="Nhập văn bản bạn muốn giọng nói đã nhân bản đọc...",
)
with gr.Row():
clone_language = gr.Dropdown(
label="Ngôn ngữ",
choices=LANGUAGES,
value="Tự động",
interactive=True,
)
clone_model_size = gr.Dropdown(
label="Kích thước mô hình",
choices=MODEL_SIZES,
value="0.6B", # Mặc định dùng mô hình nhỏ hơn cho CPU
interactive=True,
)
clone_btn = gr.Button("Nhân bản & Tạo giọng nói", variant="primary")
with gr.Row():
clone_audio_out = gr.Audio(label="Âm thanh đầu ra", type="numpy")
clone_status = gr.Textbox(label="Trạng thái", lines=2, interactive=False)
clone_btn.click(
generate_voice_clone,
inputs=[clone_ref_audio, clone_ref_text, clone_target_text, clone_language, clone_xvector, clone_model_size],
outputs=[clone_audio_out, clone_status],
)
# Tab 3: TTS (CustomVoice)
with gr.Tab("TTS (CustomVoice)"):
gr.Markdown("### Chuyển văn bản thành giọng nói với các giọng đọc có sẵn")
with gr.Row():
with gr.Column(scale=2):
tts_text = gr.Textbox(
label="Văn bản cần đọc",
lines=4,
placeholder="Nhập văn bản bạn muốn chuyển thành giọng nói...",
value="Xin chào! Chào mừng bạn đến với hệ thống chuyển văn bản thành giọng nói. Đây là bản demo các tính năng TTS của chúng tôi."
)
with gr.Row():
tts_language = gr.Dropdown(
label="Ngôn ngữ",
choices=LANGUAGES,
value="Tiếng Anh",
interactive=True,
)
tts_speaker = gr.Dropdown(
label="Giọng đọc",
choices=SPEAKERS,
value="Ryan",
interactive=True,
)
with gr.Row():
tts_instruct = gr.Textbox(
label="Hướng dẫn phong cách (Tùy chọn)",
lines=2,
placeholder="Ví dụ: Đọc với giọng vui vẻ và năng động",
)
tts_model_size = gr.Dropdown(
label="Kích thước mô hình",
choices=MODEL_SIZES,
value="0.6B", # Mặc định dùng mô hình nhỏ hơn cho CPU
interactive=True,
)
tts_btn = gr.Button("Tạo giọng nói", variant="primary")
with gr.Column(scale=2):
tts_audio_out = gr.Audio(label="Âm thanh đầu ra", type="numpy")
tts_status = gr.Textbox(label="Trạng thái", lines=2, interactive=False)
tts_btn.click(
generate_custom_voice,
inputs=[tts_text, tts_language, tts_speaker, tts_instruct, tts_model_size],
outputs=[tts_audio_out, tts_status],
)
gr.Markdown(
"""
---
**Lưu ý**: Phiên bản này chạy trên CPU, việc tải mô hình và suy luận sẽ chậm hơn phiên bản GPU.
Với văn bản dài, hãy chia thành các đoạn nhỏ hơn. Nên dùng mô hình 0.6B để tạo âm thanh nhanh hơn.
"""
)
return demo
if __name__ == "__main__":
print("Đang khởi động Demo TTS (Phiên bản CPU)...")
print("Các mô hình sẽ được tải theo yêu cầu để tiết kiệm bộ nhớ.")
demo = build_ui()
demo.launch(server_name="0.0.0.0", server_port=7860)