PlotweaverModel commited on
Commit
41ac87f
Β·
verified Β·
1 Parent(s): 83e368d

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +31 -7
  2. app.py +252 -0
  3. requirements.txt +5 -0
README.md CHANGED
@@ -1,13 +1,37 @@
1
  ---
2
- title: Qwen3-TTS AudioBook Demo
3
- emoji: 🏒
4
- colorFrom: yellow
5
- colorTo: blue
6
  sdk: gradio
7
- sdk_version: 6.17.3
8
- python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Qwen3-TTS Demo
3
+ emoji: "\U0001F399"
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 5.25.0
 
8
  app_file: app.py
9
  pinned: false
10
+ license: apache-2.0
11
  ---
12
 
13
+ # Qwen3-TTS Demo
14
+
15
+ Open-source text-to-speech with three modes:
16
+
17
+ 1. **Custom Voice** - Pick a preset speaker with optional emotion instructions
18
+ 2. **Voice Design** - Describe any voice in natural language and the AI creates it
19
+ 3. **Voice Clone** - Clone a voice from a 3-second audio sample
20
+
21
+ ## Setup
22
+
23
+ No API keys needed. The models load automatically from HuggingFace.
24
+
25
+ Hardware: Requires GPU (runs on ZeroGPU for free on HF Spaces).
26
+
27
+ ## Supported Languages
28
+
29
+ English, Chinese, Japanese, Korean, German, French, Russian, Portuguese, Spanish, Italian
30
+
31
+ ## Models Used
32
+
33
+ - Qwen3-TTS-12Hz-1.7B-CustomVoice (preset voices)
34
+ - Qwen3-TTS-12Hz-1.7B-VoiceDesign (natural language voice design)
35
+ - Qwen3-TTS-12Hz-1.7B-Base (voice cloning)
36
+
37
+ All models are Apache 2.0 licensed.
app.py ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Qwen3-TTS Demo β€” Self-hosted Text-to-Speech
3
+ Three modes: Custom Voice, Voice Design, Voice Clone
4
+ Runs on HF Spaces with ZeroGPU (free)
5
+ """
6
+
7
+ import os
8
+ import tempfile
9
+ import torch
10
+ import spaces
11
+ import gradio as gr
12
+ import soundfile as sf
13
+
14
+ from qwen_tts import Qwen3TTSModel
15
+
16
+ # ==========================================
17
+ # MODEL LOADING
18
+ # ==========================================
19
+ # Models are loaded on-demand per mode to save VRAM
20
+ _models = {}
21
+
22
+ def get_model(model_type):
23
+ """Load model lazily. Models share the tokenizer so memory is manageable."""
24
+ if model_type not in _models:
25
+ model_map = {
26
+ "custom": "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice",
27
+ "design": "Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign",
28
+ "clone": "Qwen/Qwen3-TTS-12Hz-1.7B-Base",
29
+ }
30
+ print(f"[TTS] Loading {model_map[model_type]}...")
31
+ _models[model_type] = Qwen3TTSModel.from_pretrained(
32
+ model_map[model_type],
33
+ device_map="cuda:0",
34
+ dtype=torch.bfloat16,
35
+ )
36
+ print(f"[TTS] {model_type} model loaded.")
37
+ return _models[model_type]
38
+
39
+
40
+ # ==========================================
41
+ # CONFIG
42
+ # ==========================================
43
+ LANGUAGES = ["Auto", "English", "Chinese", "Japanese", "Korean", "German",
44
+ "French", "Russian", "Portuguese", "Spanish", "Italian"]
45
+
46
+ SPEAKERS = {
47
+ "Vivian": "Bright, edgy young female (Chinese)",
48
+ "Serena": "Warm, gentle young female (Chinese)",
49
+ "Uncle_Fu": "Seasoned male, low mellow timbre (Chinese)",
50
+ "Dylan": "Youthful Beijing male, clear natural (Chinese)",
51
+ "Eric": "Lively Chengdu male, slightly husky (Chinese/Sichuan)",
52
+ "Ryan": "Dynamic male, strong rhythmic drive (English)",
53
+ "Aiden": "Sunny American male, clear midrange (English)",
54
+ "Ono_Anna": "Playful Japanese female, light nimble (Japanese)",
55
+ "Sohee": "Warm Korean female, rich emotion (Korean)",
56
+ }
57
+
58
+ SPEAKER_CHOICES = [f"{name} -- {desc}" for name, desc in SPEAKERS.items()]
59
+
60
+ EMOTION_EXAMPLES = [
61
+ "Very happy and excited",
62
+ "Speak sadly, with a heavy heart",
63
+ "Whisper softly and mysteriously",
64
+ "Angry and frustrated tone",
65
+ "Calm, warm bedtime story narrator",
66
+ "Professional news anchor delivery",
67
+ "Dramatic storytelling with suspense",
68
+ ]
69
+
70
+
71
+ # ==========================================
72
+ # TTS FUNCTIONS
73
+ # ==========================================
74
+ @spaces.GPU
75
+ def generate_custom_voice(text, language, speaker_label, instruction):
76
+ """Mode 1: Custom Voice β€” pick a preset speaker with optional instruction."""
77
+ if not text.strip():
78
+ raise gr.Error("Please enter some text.")
79
+
80
+ model = get_model("custom")
81
+ speaker = speaker_label.split("--")[0].strip()
82
+ lang = language if language != "Auto" else "Auto"
83
+
84
+ kwargs = {
85
+ "text": text,
86
+ "language": lang,
87
+ "speaker": speaker,
88
+ }
89
+ if instruction and instruction.strip():
90
+ kwargs["instruct"] = instruction.strip()
91
+
92
+ print(f"[TTS] Custom voice: speaker={speaker}, lang={lang}, instruct={instruction[:50] if instruction else 'none'}")
93
+ wavs, sr = model.generate_custom_voice(**kwargs)
94
+
95
+ output_path = os.path.join(tempfile.mkdtemp(), "custom_voice.wav")
96
+ sf.write(output_path, wavs[0], sr)
97
+ print(f"[TTS] Generated: {output_path}, {len(wavs[0])/sr:.1f}s")
98
+ return output_path
99
+
100
+
101
+ @spaces.GPU
102
+ def generate_voice_design(text, language, voice_description):
103
+ """Mode 2: Voice Design β€” describe the voice you want in natural language."""
104
+ if not text.strip():
105
+ raise gr.Error("Please enter some text.")
106
+ if not voice_description.strip():
107
+ raise gr.Error("Please describe the voice you want.")
108
+
109
+ model = get_model("design")
110
+ lang = language if language != "Auto" else "Auto"
111
+
112
+ print(f"[TTS] Voice design: lang={lang}, desc={voice_description[:80]}")
113
+ wavs, sr = model.generate_voice_design(
114
+ text=text,
115
+ language=lang,
116
+ instruct=voice_description,
117
+ )
118
+
119
+ output_path = os.path.join(tempfile.mkdtemp(), "voice_design.wav")
120
+ sf.write(output_path, wavs[0], sr)
121
+ print(f"[TTS] Generated: {output_path}, {len(wavs[0])/sr:.1f}s")
122
+ return output_path
123
+
124
+
125
+ @spaces.GPU
126
+ def generate_voice_clone(text, language, ref_audio, ref_text):
127
+ """Mode 3: Voice Clone β€” clone a voice from a 3+ second audio sample."""
128
+ if not text.strip():
129
+ raise gr.Error("Please enter some text.")
130
+ if ref_audio is None:
131
+ raise gr.Error("Please upload a reference audio sample.")
132
+
133
+ model = get_model("clone")
134
+ lang = language if language != "Auto" else "Auto"
135
+
136
+ kwargs = {
137
+ "text": text,
138
+ "language": lang,
139
+ "ref_audio": ref_audio,
140
+ }
141
+ if ref_text and ref_text.strip():
142
+ kwargs["ref_text"] = ref_text.strip()
143
+ else:
144
+ kwargs["x_vector_only_mode"] = True
145
+
146
+ print(f"[TTS] Voice clone: lang={lang}, ref_text={'yes' if ref_text else 'speaker-embed only'}")
147
+ wavs, sr = model.generate_voice_clone(**kwargs)
148
+
149
+ output_path = os.path.join(tempfile.mkdtemp(), "voice_clone.wav")
150
+ sf.write(output_path, wavs[0], sr)
151
+ print(f"[TTS] Generated: {output_path}, {len(wavs[0])/sr:.1f}s")
152
+ return output_path
153
+
154
+
155
+ # ==========================================
156
+ # GRADIO UI
157
+ # ==========================================
158
+ DESCRIPTION = """
159
+ # Qwen3-TTS Demo
160
+ ### Open-Source Text-to-Speech (1.7B)
161
+
162
+ Three modes for generating speech:
163
+
164
+ | Mode | What it does |
165
+ |------|-------------|
166
+ | **Custom Voice** | Pick a preset voice + optional emotion/style instruction |
167
+ | **Voice Design** | Describe the voice you want in plain English |
168
+ | **Voice Clone** | Clone any voice from a 3-second audio sample |
169
+
170
+ Supports 10 languages: English, Chinese, Japanese, Korean, German, French, Russian, Portuguese, Spanish, Italian.
171
+ Running on ZeroGPU β€” completely free.
172
+ """
173
+
174
+ with gr.Blocks(title="Qwen3-TTS Demo") as demo:
175
+
176
+ gr.Markdown(DESCRIPTION)
177
+
178
+ with gr.Tab("Custom Voice"):
179
+ gr.Markdown("Pick a preset speaker and optionally add emotion/style instructions.")
180
+ with gr.Row():
181
+ with gr.Column():
182
+ cv_text = gr.Textbox(label="Text to Speak", lines=4,
183
+ placeholder="Enter the text you want spoken...")
184
+ cv_lang = gr.Dropdown(choices=LANGUAGES, value="Auto", label="Language")
185
+ cv_speaker = gr.Dropdown(choices=SPEAKER_CHOICES,
186
+ value="Ryan -- Dynamic male, strong rhythmic drive (English)",
187
+ label="Speaker")
188
+ cv_instruct = gr.Textbox(label="Emotion / Style Instruction (optional)",
189
+ placeholder="e.g. Very happy and excited, Speak sadly, Whisper softly...")
190
+ cv_btn = gr.Button("Generate", variant="primary")
191
+ with gr.Column():
192
+ cv_audio = gr.Audio(label="Generated Speech", type="filepath")
193
+
194
+ cv_btn.click(fn=generate_custom_voice,
195
+ inputs=[cv_text, cv_lang, cv_speaker, cv_instruct],
196
+ outputs=cv_audio)
197
+
198
+ with gr.Tab("Voice Design"):
199
+ gr.Markdown("Describe the voice you want in natural language β€” the AI creates it from scratch.")
200
+ with gr.Row():
201
+ with gr.Column():
202
+ vd_text = gr.Textbox(label="Text to Speak", lines=4,
203
+ placeholder="Enter the text you want spoken...")
204
+ vd_lang = gr.Dropdown(choices=LANGUAGES, value="English", label="Language")
205
+ vd_desc = gr.Textbox(label="Voice Description", lines=3,
206
+ placeholder="e.g. Warm, captivating storyteller with a slight British accent, male...")
207
+ gr.Examples(
208
+ examples=[[ex] for ex in [
209
+ "Warm, captivating male storyteller with a slight British accent",
210
+ "Young energetic female voice, cheerful and bright, American",
211
+ "Deep authoritative male voice, news anchor style, very clear",
212
+ "Gentle elderly grandmother voice, kind and soothing",
213
+ "Speak in an incredulous tone with panic creeping into the voice",
214
+ ]],
215
+ inputs=[vd_desc],
216
+ label="Example Descriptions",
217
+ )
218
+ vd_btn = gr.Button("Generate", variant="primary")
219
+ with gr.Column():
220
+ vd_audio = gr.Audio(label="Generated Speech", type="filepath")
221
+
222
+ vd_btn.click(fn=generate_voice_design,
223
+ inputs=[vd_text, vd_lang, vd_desc],
224
+ outputs=vd_audio)
225
+
226
+ with gr.Tab("Voice Clone"):
227
+ gr.Markdown("Clone any voice from a short audio sample (3+ seconds). Provide the transcript for best quality.")
228
+ with gr.Row():
229
+ with gr.Column():
230
+ vc_text = gr.Textbox(label="Text to Speak (in the cloned voice)", lines=4,
231
+ placeholder="Enter what you want the cloned voice to say...")
232
+ vc_lang = gr.Dropdown(choices=LANGUAGES, value="English", label="Language")
233
+ vc_ref_audio = gr.Audio(label="Reference Audio (3+ seconds)", type="filepath")
234
+ vc_ref_text = gr.Textbox(label="Transcript of Reference Audio (optional, improves quality)",
235
+ placeholder="Type what the person says in the reference audio...")
236
+ vc_btn = gr.Button("Clone & Generate", variant="primary")
237
+ with gr.Column():
238
+ vc_audio = gr.Audio(label="Generated Speech (Cloned Voice)", type="filepath")
239
+
240
+ vc_btn.click(fn=generate_voice_clone,
241
+ inputs=[vc_text, vc_lang, vc_ref_audio, vc_ref_text],
242
+ outputs=vc_audio)
243
+
244
+ gr.Markdown(
245
+ "---\n"
246
+ "**Model:** Qwen3-TTS-12Hz-1.7B (Apache 2.0) | "
247
+ "**Languages:** EN, ZH, JA, KO, DE, FR, RU, PT, ES, IT | "
248
+ "**Running on:** HF Spaces ZeroGPU"
249
+ )
250
+
251
+ if __name__ == "__main__":
252
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ qwen-tts>=0.1.0
2
+ torch>=2.1.0
3
+ soundfile>=0.12.0
4
+ gradio>=5.25.0
5
+ audioop-lts; python_version >= "3.13"