tonyshark commited on
Commit
1b32626
·
verified ·
1 Parent(s): 7014f5a

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +137 -53
  2. requirements.txt +4 -1
app.py CHANGED
@@ -1,54 +1,108 @@
1
  import re
2
- import torch
3
  import soundfile as sf
 
4
  import gradio as gr
5
- import numpy as np
6
  from styletts2 import tts
 
 
7
 
8
  SR_OUT = 24000
9
-
10
- # ---------------------------
11
- # Load StyleTTS2
12
- # ---------------------------
13
  model = tts.StyleTTS2()
14
 
15
  # ---------------------------
16
- # Helper: extract style embedding từ 1 file neutral
17
- # (trong demo này ta chỉ có neutral, các style khác dùng "neutral" luôn,
18
- # nhưng có thể giả lập bằng cách áp embedding_scale hoặc fine-tune thêm)
19
  # ---------------------------
20
  def extract_neutral(file):
21
  if file is None:
22
  return None
23
  wav, sr = sf.read(file)
24
  if wav.ndim > 1:
25
- wav = wav.mean(axis=1) # mixdown mono
26
  wav = torch.tensor(wav).float().unsqueeze(0)
27
  return model.get_style_embedding(wav, sr)
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  # ---------------------------
30
- # Core synthesis
31
  # ---------------------------
32
- # Regex sẽ bắt các tag mở/đóng như [whisper] ... [/whisper]
33
- TAG_PATTERN = r"(\[/?(?:whisper|giggle|laugh)\])"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
- def synthesize(text, neutral_ref, embedding_scale=1.0):
36
- style_neutral = extract_neutral(neutral_ref)
 
 
 
37
  if style_neutral is None:
38
- return None
39
 
40
- # Trong trường hợp bạn checkpoint style riêng, có thể thay thế ở đây.
41
- # Ở demo này, tất cả style = neutral clone (bạn có thể mở rộng).
42
- styles = {
43
- "neutral": style_neutral,
44
- "whisper": style_neutral,
45
- "giggle": style_neutral,
46
- "laugh": style_neutral,
47
- }
48
 
49
- # Parse text theo tag
50
  tokens = re.split(TAG_PATTERN, text)
51
-
52
  current_style = styles["neutral"]
53
  stack = []
54
  final_audio = []
@@ -56,17 +110,16 @@ def synthesize(text, neutral_ref, embedding_scale=1.0):
56
  for tok in tokens:
57
  if not tok or tok.isspace():
58
  continue
59
- if tok.startswith("[") and tok.endswith("]"):
60
- tag = tok[1:-1].lower().strip("/")
61
- if tok.startswith("[/"): # closing tag
62
  if stack:
63
  stack.pop()
64
  current_style = styles["neutral"] if not stack else styles[stack[-1]]
65
- else: # opening tag
66
  stack.append(tag)
67
- current_style = styles[tag]
68
  else:
69
- # synth đoạn text với style hiện tại
70
  audio = model.inference(
71
  tok,
72
  style_embedding=current_style * embedding_scale,
@@ -75,39 +128,70 @@ def synthesize(text, neutral_ref, embedding_scale=1.0):
75
  final_audio.append(audio.astype(np.float32))
76
 
77
  if not final_audio:
78
- return None
 
 
 
79
 
80
- audio_out = np.concatenate(final_audio, axis=0)
81
- return (SR_OUT, audio_out)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
  # ---------------------------
84
  # Gradio UI
85
  # ---------------------------
86
  with gr.Blocks() as demo:
87
- gr.Markdown("# 🎙️ StyleTTS2 với Tag mở/đóng ([whisper]...[/whisper], [giggle]...[/giggle], [laugh]...[/laugh])")
88
- gr.Markdown(
89
- "- Upload **1 file neutral** để clone giọng.\n"
90
- "- Trong text, bạn thể dùng tag mở/đóng để giữ style cho cả đoạn.\n"
91
- "- dụ: `Xin chào [whisper] tôi sẽ thì thầm trong đoạn này [/whisper] và giờ lại bình thường.`"
92
- )
 
93
 
94
  with gr.Row():
95
  with gr.Column():
96
- text_in = gr.Textbox(
97
- value="Xin chào [laugh] đoạn này cười [/laugh] và bây giờ [whisper] tôi sẽ thì thầm một lúc [/whisper] rồi lại bình thường.",
98
- label="Text với tags",
99
- lines=4
100
- )
101
  neutral_in = gr.File(label="Neutral reference (.wav)", file_types=[".wav"])
102
- emb_scale = gr.Slider(0.5, 2.0, value=1.0, step=0.1, label="Embedding Scale")
 
 
 
 
 
103
  btn = gr.Button("Generate")
 
104
  with gr.Column():
105
- audio_out = gr.Audio(label="Kết quả", type="numpy")
 
 
106
 
107
- btn.click(
108
- fn=synthesize,
109
- inputs=[text_in, neutral_in, emb_scale],
110
- outputs=audio_out
111
- )
112
 
113
  demo.launch()
 
1
  import re
2
+ import numpy as np
3
  import soundfile as sf
4
+ import torch
5
  import gradio as gr
6
+ import matplotlib.pyplot as plt
7
  from styletts2 import tts
8
+ from scipy.signal import fftconvolve
9
+ import librosa
10
 
11
  SR_OUT = 24000
 
 
 
 
12
  model = tts.StyleTTS2()
13
 
14
  # ---------------------------
15
+ # Helper
 
 
16
  # ---------------------------
17
  def extract_neutral(file):
18
  if file is None:
19
  return None
20
  wav, sr = sf.read(file)
21
  if wav.ndim > 1:
22
+ wav = wav.mean(axis=1)
23
  wav = torch.tensor(wav).float().unsqueeze(0)
24
  return model.get_style_embedding(wav, sr)
25
 
26
+ def load_wav(path, sr_target=SR_OUT):
27
+ wav, sr = sf.read(path)
28
+ if wav.ndim > 1:
29
+ wav = wav.mean(axis=1)
30
+ if sr != sr_target:
31
+ wav = librosa.resample(wav.astype(np.float32), orig_sr=sr, target_sr=sr_target)
32
+ sr = sr_target
33
+ return wav.astype(np.float32), sr
34
+
35
+ def apply_reverb(wav, ir_path):
36
+ ir, _ = load_wav(ir_path, sr_target=SR_OUT)
37
+ return fftconvolve(wav, ir, mode="full")
38
+
39
+ def add_noise(wav, noise_path, snr_db=10):
40
+ noise, _ = load_wav(noise_path, sr_target=SR_OUT)
41
+ if len(noise) < len(wav):
42
+ noise = np.tile(noise, int(len(wav)/len(noise)) + 1)
43
+ noise = noise[:len(wav)]
44
+ sig_power = np.mean(wav**2)
45
+ noise_power = np.mean(noise**2)
46
+ scale = np.sqrt(sig_power / (10**(snr_db/10) * noise_power))
47
+ return wav + noise * scale
48
+
49
+ def bandlimit_phone(wav, sr=SR_OUT):
50
+ return librosa.effects.preemphasis(wav)
51
+
52
+ def plot_waveforms(clean, processed, sr=SR_OUT):
53
+ fig, axes = plt.subplots(2, 1, figsize=(10, 4), sharex=True)
54
+ t_clean = np.arange(len(clean)) / sr
55
+ t_proc = np.arange(len(processed)) / sr
56
+ axes[0].plot(t_clean, clean, color="blue")
57
+ axes[0].set_title("Waveform sạch (StyleTTS2)")
58
+ axes[1].plot(t_proc, processed, color="red")
59
+ axes[1].set_title("Waveform sau khi áp môi trường/noise")
60
+ axes[1].set_xlabel("Thời gian (s)")
61
+ fig.tight_layout()
62
+ return fig
63
+
64
  # ---------------------------
65
+ # Tag list
66
  # ---------------------------
67
+ TAG_LIST = {
68
+ "laugh": "😆 Cười thoải mái",
69
+ "whisper": "🤫 Thì thầm",
70
+ "naughty": "😏 Tinh nghịch",
71
+ "giggle": "😂 Cười rúc rích",
72
+ "tease": "😉 Trêu chọc",
73
+ "smirk": "😼 Đắc ý",
74
+ "surprise": "😲 Ngạc nhiên",
75
+ "shock": "😱 Hoảng hốt",
76
+ "romantic": "❤️ Lãng mạn",
77
+ "shy": "🫣 Bẽn lẽn",
78
+ "excited": "🤩 Phấn khích",
79
+ "curious": "🧐 Tò mò",
80
+ "discover": "✨ Phát hiện",
81
+ "blush": "🌸 Ngượng ngùng",
82
+ "angry": "😡 Giận dữ",
83
+ "sad": "😢 Buồn",
84
+ "happy": "😊 Vui vẻ",
85
+ "fear": "😨 Sợ hãi",
86
+ "confident": "😎 Tự tin",
87
+ "serious": "😐 Nghiêm túc",
88
+ "tired": "🥱 Mệt mỏi",
89
+ "cry": "😭 Khóc",
90
+ "love": "😍 Yêu thương",
91
+ "disgust": "🤢 Ghê tởm",
92
+ }
93
+ TAG_PATTERN = r"(<\/?(?:" + "|".join(TAG_LIST.keys()) + ")>)"
94
 
95
+ # ---------------------------
96
+ # Core synthesis
97
+ # ---------------------------
98
+ def synthesize(text, env, neutral_file, embedding_scale=1.0, snr_db=10):
99
+ style_neutral = extract_neutral(neutral_file)
100
  if style_neutral is None:
101
+ return None, None, None
102
 
103
+ styles = {k: style_neutral for k in ["neutral"] + list(TAG_LIST.keys())}
 
 
 
 
 
 
 
104
 
 
105
  tokens = re.split(TAG_PATTERN, text)
 
106
  current_style = styles["neutral"]
107
  stack = []
108
  final_audio = []
 
110
  for tok in tokens:
111
  if not tok or tok.isspace():
112
  continue
113
+ if tok.startswith("<") and tok.endswith(">"):
114
+ tag = tok.strip("<>/").lower()
115
+ if tok.startswith("</"):
116
  if stack:
117
  stack.pop()
118
  current_style = styles["neutral"] if not stack else styles[stack[-1]]
119
+ else:
120
  stack.append(tag)
121
+ current_style = styles.get(tag, style_neutral)
122
  else:
 
123
  audio = model.inference(
124
  tok,
125
  style_embedding=current_style * embedding_scale,
 
128
  final_audio.append(audio.astype(np.float32))
129
 
130
  if not final_audio:
131
+ return None, None, None
132
+
133
+ clean_audio = np.concatenate(final_audio, axis=0)
134
+ processed = clean_audio.copy()
135
 
136
+ # Apply environment
137
+ if env == "Church":
138
+ processed = apply_reverb(processed, "ir_church.wav")
139
+ elif env == "Hall":
140
+ processed = apply_reverb(processed, "ir_hall.wav")
141
+ elif env == "Cafe":
142
+ processed = add_noise(processed, "noise_cafe.wav", snr_db=snr_db)
143
+ elif env == "Street":
144
+ processed = add_noise(processed, "noise_street.wav", snr_db=snr_db)
145
+ elif env == "Office":
146
+ processed = add_noise(processed, "noise_office.wav", snr_db=snr_db)
147
+ elif env == "Supermarket":
148
+ processed = add_noise(processed, "noise_supermarket.wav", snr_db=snr_db)
149
+ elif env == "Phone":
150
+ processed = bandlimit_phone(processed, sr=SR_OUT)
151
+
152
+ fig = plot_waveforms(clean_audio, processed, sr=SR_OUT)
153
+ return (SR_OUT, processed.astype(np.float32)), fig, (SR_OUT, clean_audio.astype(np.float32))
154
+
155
+ # ---------------------------
156
+ # Examples
157
+ # ---------------------------
158
+ EXAMPLES = [
159
+ "Xin chào <whisper> tôi nói nhỏ </whisper> rồi <laugh> bật cười </laugh>.",
160
+ "Tôi cảm thấy <happy> vui </happy> nhưng cũng <sad> buồn </sad>.",
161
+ "Khi <surprise> bất ngờ </surprise> tôi <shock> hoảng hốt </shock>.",
162
+ ]
163
 
164
  # ---------------------------
165
  # Gradio UI
166
  # ---------------------------
167
  with gr.Blocks() as demo:
168
+ gr.Markdown("# 🎙️ StyleTTS2 + Tags + Environment + Noise Level + Waveform Preview")
169
+
170
+ with gr.Accordion("📑 Danh sách Tags + Emoji", open=False):
171
+ md = "| Tag | Ý nghĩa |\n|-----|----------|\n"
172
+ for k, v in TAG_LIST.items():
173
+ md += f"| `<{k}>...</{k}>` | {v} |\n"
174
+ gr.Markdown(md)
175
 
176
  with gr.Row():
177
  with gr.Column():
178
+ text_in = gr.Textbox(value=EXAMPLES[0], label="Text với tags", lines=4)
 
 
 
 
179
  neutral_in = gr.File(label="Neutral reference (.wav)", file_types=[".wav"])
180
+ env_in = gr.Dropdown(
181
+ choices=["Neutral", "Church", "Hall", "Cafe", "Street", "Phone", "Office", "Supermarket"],
182
+ value="Neutral", label="Environment"
183
+ )
184
+ snr_slider = gr.Slider(0, 30, value=10, step=1, label="Noise SNR (dB)")
185
+ emb_scale = gr.Slider(0.5, 2.0, value=1.0, step=0.1, label="Embedding Scale")
186
  btn = gr.Button("Generate")
187
+ gr.Examples(examples=[[ex] for ex in EXAMPLES], inputs=[text_in], label="Ví dụ nhanh")
188
  with gr.Column():
189
+ audio_out = gr.Audio(label="Output (processed)", type="numpy")
190
+ clean_out = gr.Audio(label="Waveform sạch (StyleTTS2)", type="numpy")
191
+ wave_plot = gr.Plot(label="So sánh Waveform")
192
 
193
+ btn.click(fn=synthesize,
194
+ inputs=[text_in, env_in, neutral_in, emb_scale, snr_slider],
195
+ outputs=[audio_out, wave_plot, clean_out])
 
 
196
 
197
  demo.launch()
requirements.txt CHANGED
@@ -1,5 +1,8 @@
1
  styletts2
2
  torch
3
  soundfile
4
- gradio
5
  numpy
 
 
 
 
 
1
  styletts2
2
  torch
3
  soundfile
 
4
  numpy
5
+ scipy
6
+ gradio
7
+ librosa
8
+ matplotlib