File size: 5,485 Bytes
5678a90
 
 
 
 
 
 
 
 
 
 
 
 
8044590
5678a90
 
 
 
 
8044590
5678a90
 
 
8044590
5678a90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8044590
 
5678a90
 
f01effa
 
 
 
 
 
5678a90
 
8044590
 
 
 
 
 
f01effa
8044590
 
5678a90
 
 
8044590
5678a90
 
 
8044590
5678a90
 
 
 
8044590
 
5678a90
 
8044590
 
 
 
 
 
 
 
 
 
 
5678a90
8044590
f01effa
 
8044590
f01effa
 
8044590
5678a90
 
8044590
f01effa
8044590
 
 
5678a90
8044590
 
 
 
 
5678a90
 
8044590
 
f01effa
8044590
 
5678a90
 
 
8044590
 
 
 
 
 
 
5678a90
 
 
 
 
8044590
5678a90
 
 
8044590
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import os
import tempfile
import time
from typing import Optional, Tuple

import gradio as gr
import numpy as np
import soundfile as sf
import spaces 

from kani_vie.tts_core import Config, KaniModel, NemoAudioPlayer
from utils.normalize_text import VietnameseTTSNormalizer

# --- Speaker options ---
SPEAKER_CHOICES = [
    ("Khoa – Nam miền Bắc", "nam-mien-bac"),
    ("Hùng – Nam miền Nam", "nam-mien-nam"),
    ("Trinh – Nữ miền Nam", "nu-mien-nam"),
    ("David – English (British)", "david"),
    ("Katie – English (Irish)", "katie"),
    ("Không chỉ định", None),
]

# --- Initialize model once ---
def _init_models():
    config = Config()
    player = NemoAudioPlayer(config)
    kani = KaniModel(config, player)
    return config, player, kani

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
CONFIG, PLAYER, KANI_MODEL = _init_models()
NORMALIZER = VietnameseTTSNormalizer()
SAMPLE_RATE = 22050

def _save_audio(audio: np.ndarray) -> str:
    fd, path = tempfile.mkstemp(suffix=".wav"); os.close(fd)
    sf.write(path, audio.astype(np.float32), SAMPLE_RATE)
    return path

@spaces.GPU
def _run_standard(text: str, speaker_id: Optional[str]) -> Tuple[np.ndarray, float]:
    start = time.perf_counter()
    audio, _ = KANI_MODEL.run_model(text, speaker_id=speaker_id)
    elapsed = time.perf_counter() - start
    return audio, elapsed

# --- Main synthesis ---
def synthesize(text: str, speaker_label: str, normalize: bool = True):
    text = (text or "").strip()
    if not text:
        yield None, "⚠️ Vui lòng nhập nội dung.", None
        return

    if len(text) > 250:
        yield None, f"⚠️ Văn bản quá dài ({len(text)} ký tự). Giới hạn là 250 ký tự.", None
        return

    speaker_id = dict(SPEAKER_CHOICES).get(speaker_label, None)
    processed_text = NORMALIZER.normalize(text) if normalize else text

    # --- mô phỏng tiến trình ---
    yield None, "⏳ Đang xử lý văn bản...", None
    time.sleep(0.8)

    yield None, "🎧 Đang tạo giọng nói...", None
    time.sleep(0.8)

    try:
        audio, elapsed = _run_standard(processed_text, speaker_id)
    except Exception as exc:
        yield None, f"❌ Lỗi khi suy luận: {exc}", None
        return

    if audio is None or len(audio) == 0:
        yield None, "⚠️ Không tạo được audio đầu ra.", None
        return

    wav_path = _save_audio(audio)
    duration = len(audio) / SAMPLE_RATE
    status = f"✅ Hoàn tất sau {elapsed:.2f}s | Độ dài audio: {duration:.1f}s"
    yield wav_path, status, wav_path


# --- Build simple Gradio UI ---
def build_interface():
    examples = [
        ["Khoa – Nam miền Bắc", "Cũng trong thập niên 1960, Jones quyết định đương đầu với một thử thách mới, viết nhạc phim."],
        ["Hùng – Nam miền Nam", "Ông biết hiện giờ nhiều người không còn thích đọc sách nữa, thế nên dù ai đó chỉ vô tình ghé hiệu sách, ông cũng đều trân trọng cả."],
        ["Trinh – Nữ miền Nam", "Đi vào chi tiết Làm việc nhóm và tính cứng nhắc cá nhân là hai điều không thể nào tương thích với nhau."],
        ["David – English (British)", "Ngược lại, những người không thể đào tạo sẽ gặp khó khăn với sự thay đổi và kết quả là họ không thể thích nghi."],
        ["Katie – English (Irish)", "Những người này sẽ vò đầu bứt tai, chịu đựng nỗi đau thể chất khi nghĩ đến chuyện làm những điều khác biệt."],
    ]

    with gr.Blocks(theme=gr.themes.Soft(primary_hue="teal")) as demo:
        gr.Markdown(
            """
            # 😻 Kani TTS Vie – Simple Demo  
            Mô hình tổng hợp giọng nói nhanh và biểu cảm, hỗ trợ tiếng Việt  
            
            💡 *Gradio chưa hỗ trợ streaming trực tiếp. Chế độ này chỉ mô phỏng, nếu muốn streaming thật, tham khảo phiên bản mã nguồn tại https://github.com/pnnbao97/Kani-TTS-Vie.*

            """
        )

        text_input = gr.Textbox(
            label="📝 Nội dung (tối đa 250 ký tự)",
            placeholder="Nhập văn bản cần chuyển thành giọng nói...",
            lines=4,
            value="Khi bạn kề vai sát cánh cùng đồng đội của mình, bạn có thể làm nên những điều phi thường.",
        )

        speaker_dropdown = gr.Dropdown(
            label="🎤 Chọn giọng đọc",
            choices=[label for label, _ in SPEAKER_CHOICES],
            value="Hùng – Nam miền Nam",
        )

        run_button = gr.Button("🎵 Tạo giọng nói", variant="primary")
        status_output = gr.Markdown(label="Trạng thái")
        audio_output = gr.Audio(label="🔊 Kết quả", autoplay=False)
        download_output = gr.File(label="💾 Tải WAV")

        run_button.click(
            fn=synthesize,
            inputs=[text_input, speaker_dropdown],
            outputs=[audio_output, status_output, download_output],
        )

        gr.Examples(
            examples=examples,
            inputs=[speaker_dropdown, text_input],
            label="📚 Ví dụ nhanh"
        )

    demo.queue()
    return demo


demo = build_interface()

if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))