tonyshark commited on
Commit
1f495a1
·
verified ·
1 Parent(s): 9ce98c6

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +198 -113
  2. requirements.txt +3 -13
app.py CHANGED
@@ -1,127 +1,212 @@
1
- # app.py
2
- import gradio as gr
3
- import torch
4
- import os
5
  import numpy as np
 
6
  import soundfile as sf
7
- import time
8
- from model import StyleTTModel
9
-
10
- SPEAKER_WAV_PATH = "speakers/example_female.wav"
11
- OUTPUT_FILENAME = "output.wav"
12
- SAMPLE_RATE = 24000
13
-
14
- # Kiểm tra xem file giọng nói tham chiếu có tồn tại không
15
- if not os.path.exists(SPEAKER_WAV_PATH):
16
- raise FileNotFoundError(f"Không tìm thấy file giọng nói tham chiếu tại: {SPEAKER_WAV_PATH}. "
17
- "Vui lòng tạo thư mục và đặt file .wav của bạn vào đó.")
18
-
19
- print("Bắt đầu khởi tạo StyleTTS2 Model...")
20
- model = StyleTTModel(speaker_wav=SPEAKER_WAV_PATH)
21
- print("Đang tải model StyleTTS2. Quá trình này có thể mất vài phút...")
22
- start_time = time.time()
23
- model.load()
24
- end_time = time.time()
25
- print(f"Model đã được tải thành công sau {end_time - start_time:.2f} giây.")
26
-
27
-
28
- def process_expressive_tags(text: str) -> str:
29
- """
30
- Chuyển đổi các tag biểu cảm như <laugh> thành các cụm từ mô tả
31
- StyleTTS2 có thể hiểu để thay đổi giọng điệu.
32
- """
33
- tag_map = {
34
- "<laugh>": "[laughing]",
35
- "<whisper>": "[whispering]",
36
- "<sigh>": "[sighs]",
37
- "<cry>": "[crying]",
38
- "<naughty>": "[mischievous tone]",
39
- "<giggle>": "[giggling]",
40
- "<tease>": "[teasingly]",
41
- "<smirk>": "[sly tone]",
42
- "<surprise>": "[surprised tone]",
43
- "<romantic>": "[romantic tone]",
44
- "<shy>": "[shyly]",
45
- "<excited>": "[excitedly]",
46
- "<shock>": "[shocked tone]",
47
- "<curious>": "[curious tone]",
48
- "<discover>": "[discovering tone]",
49
- "<blush>": "[blushing voice]"
50
- }
51
-
52
- processed_text = text
53
- for tag, style_prompt in tag_map.items():
54
- processed_text = processed_text.replace(tag, style_prompt)
55
-
56
- print(f"Văn bản sau khi xử lý tag: '{processed_text}'")
57
- return processed_text
58
-
59
- # --- Phần 3: Hàm chính để tổng hợp giọng nói ---
60
-
61
- def generate_speech(text: str, speed: float):
62
- """
63
- Hàm chính được gọi bởi Gradio để tạo ra âm thanh từ văn bản.
64
- """
65
- if not text:
66
  return None
 
 
 
 
 
 
 
 
 
 
67
 
68
- print(f"Nhận được văn bản: '{text}' với tốc độ {speed}")
69
-
70
- # Bước 1: Xử lý các tag biểu cảm
71
- styled_text = process_expressive_tags(text)
72
-
73
- # Bước 2: Sử dụng plugin để tổng hợp âm thanh
74
- # Hàm synthesize trả về một mảng numpy
75
- audio_array = model.synthesize(styled_text, speed=speed)
76
-
77
- sf.write(OUTPUT_FILENAME, audio_array, SAMPLE_RATE, 'FLOAT')
78
-
79
- print(f"Đã tạo file âm thanh thành công tại: {OUTPUT_FILENAME}")
80
-
81
- return OUTPUT_FILENAME
82
 
 
 
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  with gr.Blocks() as demo:
 
85
  gr.Markdown(
86
- """
87
- # 🎙️ Demo StyleTTS2 Lite với Tag biểu cảm
88
- Nhập văn bản vào ô bên dưới. Bạn thể sử dụng các tag như `<laugh>`, `<whisper>`, `<sigh>`
89
- để thêm cảm xúc cho giọng nói. Điều chỉnh thanh trượt để thay đổi tốc độ nói.
90
- """
91
  )
92
-
93
  with gr.Row():
94
- text_input = gr.Textbox(
95
- label="Nhập văn bản ở đây",
96
- placeholder=" dụ: Chào bạn, tôi một trợ ảo. <laugh> Thật vui được gặp bạn!",
97
- lines=4
98
- )
99
-
100
- speed_slider = gr.Slider(
101
- minimum=0.5,
102
- maximum=2.0,
103
- value=1.0,
104
- step=0.1,
105
- label="Tốc độ nói (Speed)"
106
- )
107
-
108
- generate_button = gr.Button("Tạo giọng nói 🎙️", variant="primary")
109
-
110
- audio_output = gr.Audio(label="Kết quả", type="filepath")
111
-
112
- # Kết nối các thành phần
113
- generate_button.click(
114
- fn=generate_speech,
115
- inputs=[text_input, speed_slider],
116
- outputs=audio_output
117
- )
118
 
119
- gr.Markdown("### Các tag biểu cảm được hỗ trợ:")
120
- gr.Markdown("- `<laugh>`: Tiếng cười\n- `<whisper>`: Thì thầm\n- `<sigh>`: Thở dài\n- `<cry>`: Khóc\n- `<giggle>`: Cười khúc khích\n- `<tease>`: Trêu chọc\n- `<surprised>`: Ngạc nhiên\n- Và nhiều tag khác trong mã nguồn...")
 
 
121
 
 
 
 
 
122
 
123
- # --- Phần 5: Chạy ứng dụng ---
 
 
 
 
124
 
125
- if __name__ == "__main__":
126
- # Chia sẻ (share=True) sẽ tạo một liên kết công khai tạm thời
127
- demo.launch(share=False)
 
1
+ import re
2
+ import io
 
 
3
  import numpy as np
4
+ import torch
5
  import soundfile as sf
6
+ import librosa
7
+ import gradio as gr
8
+ from styletts2 import tts
9
+
10
+ SR_OUT = 24000 # sample rate output cho toàn bộ hệ
11
+
12
+ # ---------------------------
13
+ # Load StyleTTS2
14
+ # ---------------------------
15
+ model = tts.StyleTTS2()
16
+
17
+ # ---------------------------
18
+ # Audio utils
19
+ # ---------------------------
20
+ def load_wav_any(file_or_path, target_sr=None, mono=True):
21
+ """Load wav (from path hoặc Gradio file object), optional resample."""
22
+ if file_or_path is None:
23
+ return None, None
24
+ if hasattr(file_or_path, "name"): # Uploaded file (tempfile)
25
+ path = file_or_path.name
26
+ else:
27
+ path = file_or_path
28
+ wav, sr = sf.read(path, always_2d=False)
29
+ if wav.ndim > 1 and mono:
30
+ wav = wav.mean(axis=1)
31
+ if target_sr and sr != target_sr:
32
+ wav = librosa.resample(wav.astype(np.float32), orig_sr=sr, target_sr=target_sr)
33
+ sr = target_sr
34
+ return wav.astype(np.float32), sr
35
+
36
+ def to_tensor_batch1(wav_np):
37
+ return torch.tensor(wav_np).float().unsqueeze(0)
38
+
39
+ def fade(wav, fade_ms=10, sr=SR_OUT):
40
+ """Fade in/out để tránh click khi nối."""
41
+ if wav is None or len(wav) == 0:
42
+ return wav
43
+ n = len(wav)
44
+ fade_len = max(1, int(sr * fade_ms / 1000.0))
45
+ env = np.ones(n, dtype=np.float32)
46
+ ramp = np.linspace(0.0, 1.0, fade_len, dtype=np.float32)
47
+ env[:fade_len] *= ramp
48
+ env[-fade_len:] *= ramp[::-1]
49
+ return wav * env
50
+
51
+ def match_gain(wav, gain_db):
52
+ """Áp gain dB lên clip."""
53
+ g = 10 ** (gain_db / 20.0)
54
+ return (wav * g).astype(np.float32)
55
+
56
+ # ---------------------------
57
+ # Style extraction
58
+ # ---------------------------
59
+ def get_style_embedding(file):
60
+ if file is None:
 
 
 
 
61
  return None
62
+ wav, sr = load_wav_any(file, target_sr=SR_OUT)
63
+ if wav is None:
64
+ return None
65
+ wav_t = to_tensor_batch1(wav)
66
+ return model.get_style_embedding(wav_t, SR_OUT) # (1, D)
67
+
68
+ # ---------------------------
69
+ # Core synthesis
70
+ # ---------------------------
71
+ TAG_PATTERN = r"(\[(?:laugh|whisper|giggle)\])"
72
 
73
+ def synthesize(
74
+ text,
75
+ neutral_ref, whisper_ref, giggle_ref,
76
+ laugh_sfx, # <-- audio tiếng cười để chèn
77
+ embedding_scale=1.0,
78
+ laugh_gain_db=0.0, # chỉnh âm lượng sfx
79
+ laugh_stretch=1.0, # time-stretch sfx (1.0 = nguyên gốc)
80
+ ):
81
+ # 1) Chuẩn bị style embeddings
82
+ style_neutral = get_style_embedding(neutral_ref)
83
+ style_whisper = get_style_embedding(whisper_ref)
84
+ style_giggle = get_style_embedding(giggle_ref)
 
 
85
 
86
+ if style_neutral is None:
87
+ return None
88
 
89
+ # 2) Load sfx cười (resample, fade & gain)
90
+ laugh_np, _ = load_wav_any(laugh_sfx, target_sr=SR_OUT)
91
+ if laugh_np is not None:
92
+ if laugh_stretch and abs(laugh_stretch - 1.0) > 1e-3:
93
+ laugh_np = librosa.effects.time_stretch(laugh_np, rate=1.0/float(laugh_stretch))
94
+ laugh_np = fade(laugh_np, fade_ms=12, sr=SR_OUT)
95
+ if laugh_gain_db != 0.0:
96
+ laugh_np = match_gain(laugh_np, laugh_gain_db)
97
+
98
+ # 3) Parse text theo tag
99
+ tokens = re.split(TAG_PATTERN, text)
100
+
101
+ pieces = []
102
+ for tok in tokens:
103
+ if tok is None:
104
+ continue
105
+ t = tok.strip()
106
+ if not t:
107
+ continue
108
+
109
+ if t.startswith("[") and t.endswith("]"):
110
+ tag = t[1:-1].lower()
111
+ if tag == "laugh":
112
+ # chèn trực tiếp sfx tiếng cười
113
+ if laugh_np is not None:
114
+ pieces.append(laugh_np)
115
+ # nếu chưa upload sfx, bỏ qua hoặc có thể synthesize "hahaha" bằng style_giggle
116
+ else:
117
+ # fallback: synthesize một âm tiết ngắn với giggle style nếu có
118
+ style_use = style_giggle if style_giggle is not None else style_neutral
119
+ audio = model.inference(
120
+ "ha ha", style_embedding=style_use * embedding_scale, output_sample_rate=SR_OUT
121
+ )
122
+ pieces.append(audio.astype(np.float32))
123
+ elif tag == "whisper":
124
+ # tạo một đoạn ngắn im lặng mang "breath" hoặc synth 1 khoảng ngắn trống
125
+ # ở đây ta không synth text vì tag đơn lẻ, chỉ chuyển style kế tiếp
126
+ # => chèn đoạn im lặng rất ngắn để tách
127
+ pieces.append(np.zeros(int(0.05*SR_OUT), dtype=np.float32))
128
+ # Đặt "current style" cho phần text tiếp theo
129
+ # Cách đơn giản: lưu "style kế tiếp" trong biến
130
+ pieces.append(("__STYLE__", "whisper"))
131
+ elif tag == "giggle":
132
+ pieces.append(np.zeros(int(0.05*SR_OUT), dtype=np.float32))
133
+ pieces.append(("__STYLE__", "giggle"))
134
+ else:
135
+ # default: bỏ qua
136
+ pass
137
+ else:
138
+ # text bình thường => synth với style hiện thời (nếu có)
139
+ # tìm xem có cờ "__STYLE__" trước đó không
140
+ curr_style = style_neutral
141
+ # duyệt từ cuối pieces để tìm chỉ thị style gần nhất (nếu có)
142
+ for it in reversed(pieces):
143
+ if isinstance(it, tuple) and it[0] == "__STYLE__":
144
+ mode = it[1]
145
+ if mode == "whisper" and style_whisper is not None:
146
+ curr_style = style_whisper
147
+ elif mode == "giggle" and style_giggle is not None:
148
+ curr_style = style_giggle
149
+ break
150
+
151
+ audio = model.inference(
152
+ t, style_embedding=curr_style * embedding_scale, output_sample_rate=SR_OUT
153
+ )
154
+ pieces.append(audio.astype(np.float32))
155
+
156
+ # 4) Gộp các đoạn
157
+ # Lọc bỏ các marker style "__STYLE__"
158
+ merged = []
159
+ for it in pieces:
160
+ if isinstance(it, tuple):
161
+ continue
162
+ if it is None:
163
+ continue
164
+ merged.append(it)
165
+
166
+ if not merged:
167
+ return None
168
+
169
+ out = np.concatenate(merged, axis=0)
170
+ out = fade(out, fade_ms=8, sr=SR_OUT)
171
+ return (SR_OUT, out)
172
+
173
+ # ---------------------------
174
+ # Gradio UI
175
+ # ---------------------------
176
  with gr.Blocks() as demo:
177
+ gr.Markdown("# 🎙️ StyleTTS2 Tags + Laugh SFX (Hugging Face Radio App)")
178
  gr.Markdown(
179
+ "Nhập text có tag: `[whisper]`, `[giggle]`, và **`[laugh]`**.\n\n"
180
+ "- Với `[laugh]`: app **chèn trực tiếp audio tiếng cười** bạn upload.\n"
181
+ "- Với `[whisper]` / `[giggle]`: app dùng **style embedding** từ file tham chiếu.\n"
182
+ "- Upload *ít nhất* 1 file neutral để lấy giọng bản."
 
183
  )
184
+
185
  with gr.Row():
186
+ with gr.Column():
187
+ text_in = gr.Textbox(
188
+ value="Xin chào mọi người [laugh] bây giờ tôi sẽ nói nhỏ [whisper] rồi khúc khích [giggle] lại bình thường.",
189
+ label="Text có tags",
190
+ lines=4
191
+ )
192
+ neutral_in = gr.File(label="Neutral reference (.wav)", file_types=[".wav"])
193
+ whisper_in = gr.File(label="Whisper reference (.wav)", file_types=[".wav"])
194
+ giggle_in = gr.File(label="Giggle reference (.wav)", file_types=[".wav"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
 
196
+ gr.Markdown("### 🎧 Laugh SFX (chèn trực tiếp khi gặp [laugh])")
197
+ laugh_in = gr.File(label="Laugh SFX (.wav)", file_types=[".wav"])
198
+ laugh_gain = gr.Slider(-12, 12, value=0.0, step=0.5, label="Laugh gain (dB)")
199
+ laugh_stch = gr.Slider(0.5, 2.0, value=1.0, step=0.05, label="Laugh time-stretch (x)")
200
 
201
+ emb_scale = gr.Slider(0.5, 2.0, value=1.0, step=0.1, label="Embedding scale (StyleTTS2)")
202
+ btn = gr.Button("Generate")
203
+ with gr.Column():
204
+ audio_out = gr.Audio(label="Kết quả", type="numpy")
205
 
206
+ btn.click(
207
+ fn=synthesize,
208
+ inputs=[text_in, neutral_in, whisper_in, giggle_in, laugh_in, emb_scale, laugh_gain, laugh_stch],
209
+ outputs=audio_out
210
+ )
211
 
212
+ demo.launch()
 
 
requirements.txt CHANGED
@@ -1,16 +1,6 @@
 
1
  torch
2
- torchaudio
3
- transformers
4
- huggingface_hub
5
- gradio
6
  soundfile
7
- numpy
8
- pyyaml
9
- pydantic
10
- httpx
11
- phonemizer
12
- nltk
13
- munch
14
- noisereduce
15
  librosa
16
- pyttsx3
 
 
1
+ styletts2
2
  torch
 
 
 
 
3
  soundfile
 
 
 
 
 
 
 
 
4
  librosa
5
+ gradio
6
+ numpy