File size: 4,640 Bytes
9a9e06c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import gradio as gr
from kokoro_onnx import Kokoro
import soundfile as sf
import numpy as np
from pathlib import Path
import requests
import tempfile
import os

# Model setup
MODEL_DIR = Path("/tmp/kokoro_models")
MODEL_DIR.mkdir(exist_ok=True)
MODEL_PATH = MODEL_DIR / "kokoro-v1.0.onnx"
VOICES_PATH = MODEL_DIR / "voices-v1.0.bin"
MODEL_URL = "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx"
VOICES_URL = "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/voices-v1.0.bin"

kokoro = None

def download_file(url, dest):
    if dest.exists():
        return
    print(f"Downloading {dest.name}...")
    with requests.get(url, stream=True, timeout=120, allow_redirects=True) as r:
        r.raise_for_status()
        with open(dest, "wb") as f:
            for chunk in r.iter_content(chunk_size=8192):
                f.write(chunk)
    print(f"✓ Downloaded {dest.name}")

def get_kokoro():
    global kokoro
    if kokoro is not None:
        return kokoro
    download_file(MODEL_URL, MODEL_PATH)
    download_file(VOICES_URL, VOICES_PATH)
    kokoro = Kokoro(str(MODEL_PATH), str(VOICES_PATH))
    print(f"✓ Kokoro loaded. Voices: {len(kokoro.get_voices())}")
    return kokoro

# Warm up
try:
    get_kokoro()
except Exception as e:
    print(f"Warmup failed: {e}")

def tts(text, voice, speed):
    if not text.strip():
        return None, "请输入文本"
    
    k = get_kokoro()
    voices = k.get_voices()
    if voice not in voices:
        return None, f"语音 '{voice}' 不可用"
    
    try:
        audio, sr = k.create(text, voice=voice, speed=speed, lang="en-us")
        # Save to temp file for Gradio
        with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
            sf.write(f.name, audio, sr, format="WAV", subtype="PCM_16")
            return f.name, "生成成功"
    except Exception as e:
        return None, f"生成失败: {e}"

# Get voice list
try:
    voices_list = get_kokoro().get_voices()
except:
    voices_list = ["af_heart", "af_bella", "am_michael", "am_adam", "bf_emma", "bm_george"]

# Categorize voices
us_female = [v for v in voices_list if v.startswith("af_")]
us_male = [v for v in voices_list if v.startswith("am_")]
uk_female = [v for v in voices_list if v.startswith("bf_")]
uk_male = [v for v in voices_list if v.startswith("bm_")]
other = [v for v in voices_list if not v.startswith(("af_", "am_", "bf_", "bm_"))]

voice_choices = us_female + us_male + uk_female + uk_male + other

with gr.Blocks(title="Kokoro 82M TTS") as demo:
    gr.Markdown("# 🗣️ Kokoro 82M TTS - High Quality CPU TTS")
    gr.Markdown("**Model**: Kokoro 82M (Apache 2.0) | **Quality**: UTMOS MOS ~4.45 | **Speed**: RTF ~0.64 on CPU")
    
    with gr.Row():
        with gr.Column(scale=2):
            text = gr.Textbox(
                label="Text to synthesize",
                placeholder="Enter text here...",
                lines=3,
                value="Hello, this is Kokoro 82M text to speech!"
            )
            voice = gr.Dropdown(
                label="Voice",
                choices=voice_choices,
                value="af_heart" if "af_heart" in voice_choices else voice_choices[0],
                info=f"Total {len(voice_choices)} voices available"
            )
            speed = gr.Slider(0.5, 2.0, value=1.0, step=0.1, label="Speed")
            btn = gr.Button("Generate", variant="primary")
            status = gr.Textbox(label="Status", interactive=False)
        
        with gr.Column(scale=1):
            audio_out = gr.Audio(label="Output", type="filepath")
    
    btn.click(tts, inputs=[text, voice, speed], outputs=[audio_out, status])
    
    with gr.Accordion("Voice Categories", open=False):
        gr.Markdown(f"""
        **US Female** ({len(us_female)}): {', '.join(us_female[:10])}{'...' if len(us_female) > 10 else ''}
        **US Male** ({len(us_male)}): {', '.join(us_male[:10])}{'...' if len(us_male) > 10 else ''}
        **UK Female** ({len(uk_female)}): {', '.join(uk_female[:10])}{'...' if len(uk_female) > 10 else ''}
        **UK Male** ({len(uk_male)}): {', '.join(uk_male[:10])}{'...' if len(uk_male) > 10 else ''}
        **Other** ({len(other)}): {', '.join(other[:10])}{'...' if len(other) > 10 else ''}
        """)
    
    gr.Markdown("""
    ---
    **API**: Also available via REST:
    - `POST /tts` - `{"text": "...", "voice": "af_heart", "speed": 1.0}` → WAV
    - `GET /health` - Health check
    - `GET /` - Voice catalog
    """)

if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860)