Opera8 commited on
Commit
d081a94
·
verified ·
1 Parent(s): 82faa29

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +125 -62
app.py CHANGED
@@ -11,9 +11,10 @@ from huggingface_hub import snapshot_download, hf_hub_download
11
  import subprocess
12
  import uuid
13
  import soundfile as sf
14
- import spaces # این خط برای ZeroGPU ضروری است
 
15
 
16
- # --- تنظیمات و نصب پیش‌نیازها ---
17
  downloaded_resources = {
18
  "configs": False,
19
  "tokenizer_vq8192": False,
@@ -132,6 +133,93 @@ def get_pipeline():
132
  inference_pipelines["timbre"] = pipeline
133
  return pipeline
134
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  @spaces.GPU()
136
  def vevo_timbre(content_wav, reference_wav):
137
  session_id = str(uuid.uuid4())[:8]
@@ -145,7 +233,7 @@ def vevo_timbre(content_wav, reference_wav):
145
  try:
146
  SR = 24000
147
 
148
- # --- آماده‌سازی ورودی ---
149
  if isinstance(content_wav, tuple):
150
  content_sr, content_data = content_wav if isinstance(content_wav[0], int) else (content_wav[1], content_wav[0])
151
  else:
@@ -158,7 +246,7 @@ def vevo_timbre(content_wav, reference_wav):
158
  content_tensor = content_tensor / (torch.max(torch.abs(content_tensor)) + 1e-6) * 0.95
159
  content_full_np = content_tensor.squeeze().numpy()
160
 
161
- # --- آماده‌سازی رفرنس ---
162
  if isinstance(reference_wav, tuple):
163
  ref_sr, ref_data = reference_wav if isinstance(reference_wav[0], int) else (reference_wav[1], reference_wav[0])
164
  else:
@@ -172,40 +260,27 @@ def vevo_timbre(content_wav, reference_wav):
172
  if ref_tensor.shape[1] > SR * 20: ref_tensor = ref_tensor[:, :SR * 20]
173
  save_audio_pcm16(ref_tensor, temp_reference_path, SR)
174
 
175
- # --- استراتژی Center-Only Processing (حذف لرزش) ---
176
  pipeline = get_pipeline()
177
 
178
- # تنظیمات: ۱۰ ثانیه تمیز نگه می‌داریم، ۲ ثانیه از هر طرف دور می‌ریزیم
179
- CORE_CHUNK_SEC = 10.0
180
- PADDING_SEC = 2.0
181
-
182
- core_samples = int(CORE_CHUNK_SEC * SR)
183
- padding_samples = int(PADDING_SEC * SR)
184
- total_samples = len(content_full_np)
185
 
186
  final_output = []
 
187
 
188
- cursor = 0
189
- print(f"[{session_id}] Starting Center-Only processing...")
190
-
191
- while cursor < total_samples:
192
- # ۱. خواندن بازه وسیع‌تر (شامل پدینگ)
193
- read_start = max(0, cursor - padding_samples)
194
- read_end = min(total_samples, cursor + core_samples + padding_samples)
195
 
196
- if cursor >= total_samples:
197
- break
198
 
199
  chunk_input = content_full_np[read_start:read_end]
200
-
201
- # اگر تکه انتهایی خیلی کوچک است، بیخیال شو
202
- if len(chunk_input) < SR * 0.5:
203
- break
204
-
205
  save_audio_pcm16(torch.FloatTensor(chunk_input).unsqueeze(0), temp_content_path, SR)
206
 
207
  try:
208
- # ۲. تولید صدا
209
  gen = pipeline.inference_fm(
210
  src_wav_path=temp_content_path,
211
  timbre_ref_wav_path=temp_reference_path,
@@ -214,47 +289,35 @@ def vevo_timbre(content_wav, reference_wav):
214
  if torch.isnan(gen).any(): gen = torch.nan_to_num(gen, nan=0.0)
215
  gen_np = gen.detach().cpu().squeeze().numpy()
216
 
217
- # ۳. حذف حاشیه‌های خراب (Trimming)
218
 
219
- # محاسبه برش از جلو
220
- if cursor == 0:
221
- trim_front = 0 # در اولین تکه، پدینگ جلو نداریم
222
- else:
223
- trim_front = padding_samples # در بقیه، به اندازه پدینگ جلو می‌بریم
224
-
225
- # محاسبه طول مفید
226
- valid_length = min(core_samples, total_samples - cursor)
227
-
228
- if len(gen_np) > trim_front:
229
- # استخراج فقط هسته مرکزی (بدون لرزش)
230
- core_audio = gen_np[trim_front : trim_front + valid_length]
231
-
232
- # ۴. اتصال میکروسکوپی (۵۰ میلی ثانیه) فقط برای حذف کلیک
233
- fade_len = int(0.05 * SR)
234
 
235
- if len(final_output) > 0 and len(core_audio) > fade_len:
236
- fade_out = np.linspace(1, 0, fade_len)
237
- fade_in = np.linspace(0, 1, fade_len)
 
 
238
 
239
- prev_tail = final_output[-1][-fade_len:]
240
- curr_head = core_audio[:fade_len]
241
-
242
- if len(prev_tail) == fade_len:
 
 
 
243
  mixed = (prev_tail * fade_out) + (curr_head * fade_in)
244
  final_output[-1][-fade_len:] = mixed
245
- core_audio = core_audio[fade_len:]
246
 
247
- final_output.append(core_audio)
248
-
249
  except Exception as e:
250
- print(f"Error processing chunk at {cursor}: {e}")
251
- missing = min(core_samples, total_samples - cursor)
252
- final_output.append(np.zeros(missing))
253
-
254
- # ۵. جلو رفتن دقیق به اندازه ۱۰ ثانیه
255
- cursor += core_samples
256
 
257
- # چسباندن نهایی
258
  if len(final_output) > 0:
259
  full_audio = np.concatenate(final_output)
260
  else:
@@ -267,9 +330,9 @@ def vevo_timbre(content_wav, reference_wav):
267
  if os.path.exists(temp_content_path): os.remove(temp_content_path)
268
  if os.path.exists(temp_reference_path): os.remove(temp_reference_path)
269
 
270
- with gr.Blocks(title="Vevo-Timbre (Stable Core)") as demo:
271
  gr.Markdown("## Vevo-Timbre: Zero-Shot Voice Conversion")
272
- gr.Markdown("Stable Core Logic: Removes generated artifacts at boundaries.")
273
 
274
  with gr.Row():
275
  with gr.Column():
 
11
  import subprocess
12
  import uuid
13
  import soundfile as sf
14
+ import spaces
15
+ import librosa
16
 
17
+ # --- 1. نصب و راه‌اندازی ---
18
  downloaded_resources = {
19
  "configs": False,
20
  "tokenizer_vq8192": False,
 
133
  inference_pipelines["timbre"] = pipeline
134
  return pipeline
135
 
136
+ # --- 2. الگوریتم برش فوق هوشمند ---
137
+ def find_advanced_split_points(audio_np, sr):
138
+ """
139
+ پیدا کردن نقاط برش با استراتژی فال‌بک (Fallback Strategy):
140
+ ۱. تلاش برای پیدا کردن سکوت در بازه ۸ تا ۱۲ ثانیه.
141
+ ۲. اگر نشد، تلاش در بازه وسیع‌تر ۶ تا ۱۴ ثانیه.
142
+ ۳. انتخاب نقطه با کمترین انرژی (حتی اگر سکوت نباشد).
143
+ ۴. تنظیم دقیق روی نزدیک‌ترین Zero-Crossing.
144
+ """
145
+ total_samples = len(audio_np)
146
+
147
+ # تنظیمات بازه جستجو
148
+ MIN_PREFERRED = 8.0
149
+ MAX_PREFERRED = 12.0
150
+ MIN_HARD = 6.0
151
+ MAX_HARD = 15.0
152
+
153
+ split_points = [0]
154
+ current_pos = 0
155
+
156
+ hop_length = 512
157
+ frame_length = 1024
158
+
159
+ while current_pos < total_samples:
160
+ # استراتژی ۱: بازه ایده‌آل
161
+ start_search = current_pos + int(MIN_PREFERRED * sr)
162
+ end_search = current_pos + int(MAX_PREFERRED * sr)
163
+
164
+ # اگر به انتهای فایل نزدیکیم
165
+ if start_search >= total_samples:
166
+ split_points.append(total_samples)
167
+ break
168
+
169
+ end_search = min(end_search, total_samples)
170
+
171
+ # استراتژی ۲: اگر بازه ایده‌آل خیلی کوتاه است (ته فایل)، گسترش بده
172
+ if end_search - start_search < sr:
173
+ # استفاده از بازه سخت (وسیع)
174
+ start_search = current_pos + int(MIN_HARD * sr)
175
+ end_search = current_pos + int(MAX_HARD * sr)
176
+ start_search = min(start_search, total_samples)
177
+ end_search = min(end_search, total_samples)
178
+
179
+ # برش منطقه جستجو
180
+ region = audio_np[start_search:end_search]
181
+
182
+ if len(region) == 0:
183
+ split_points.append(total_samples)
184
+ break
185
+
186
+ # محاسبه انرژی
187
+ rms = librosa.feature.rms(y=region, frame_length=frame_length, hop_length=hop_length)[0]
188
+
189
+ # پیدا کردن کم‌انرژی‌ترین نقطه (Local Minimum)
190
+ min_idx = np.argmin(rms)
191
+ local_cut_sample = min_idx * hop_length
192
+
193
+ # --- تکنیک Zero Crossing ---
194
+ # نقطه برش تقریبی را پیدا کردیم. حالا باید دقیقاً روی محور صفر برش دهیم
195
+ # تا صدای "کلیک" ایجاد نشود.
196
+
197
+ cut_absolute_approx = start_search + local_cut_sample
198
+
199
+ # جستجو در اطراف نقطه تقریبی (±500 نمونه) برای پیدا کردن صفر
200
+ search_radius = 500
201
+ zc_start = max(0, cut_absolute_approx - search_radius)
202
+ zc_end = min(total_samples, cut_absolute_approx + search_radius)
203
+
204
+ zc_region = audio_np[zc_start:zc_end]
205
+
206
+ # پیدا کردن نزدیک‌ترین عبور از صفر
207
+ # (جایی که علامت عدد تغییر می‌کند)
208
+ zero_crossings = np.where(np.diff(np.signbit(zc_region)))[0]
209
+
210
+ if len(zero_crossings) > 0:
211
+ # نزدیک‌ترین صفر به وسط بازه جستجو
212
+ closest_zc = zero_crossings[np.argmin(np.abs(zero_crossings - search_radius))]
213
+ best_cut_absolute = zc_start + closest_zc
214
+ else:
215
+ # اگر صفر پیدا نشد (خیلی بعید)، همان نقطه کم‌انرژی را بگیر
216
+ best_cut_absolute = cut_absolute_approx
217
+
218
+ split_points.append(best_cut_absolute)
219
+ current_pos = best_cut_absolute
220
+
221
+ return split_points
222
+
223
  @spaces.GPU()
224
  def vevo_timbre(content_wav, reference_wav):
225
  session_id = str(uuid.uuid4())[:8]
 
233
  try:
234
  SR = 24000
235
 
236
+ # --- ورودی ---
237
  if isinstance(content_wav, tuple):
238
  content_sr, content_data = content_wav if isinstance(content_wav[0], int) else (content_wav[1], content_wav[0])
239
  else:
 
246
  content_tensor = content_tensor / (torch.max(torch.abs(content_tensor)) + 1e-6) * 0.95
247
  content_full_np = content_tensor.squeeze().numpy()
248
 
249
+ # --- رفرنس ---
250
  if isinstance(reference_wav, tuple):
251
  ref_sr, ref_data = reference_wav if isinstance(reference_wav[0], int) else (reference_wav[1], reference_wav[0])
252
  else:
 
260
  if ref_tensor.shape[1] > SR * 20: ref_tensor = ref_tensor[:, :SR * 20]
261
  save_audio_pcm16(ref_tensor, temp_reference_path, SR)
262
 
 
263
  pipeline = get_pipeline()
264
 
265
+ # --- تقسیم‌بندی پیشرفته ---
266
+ print(f"[{session_id}] Finding best energy split points (Zero-Crossing)...")
267
+ split_points = find_advanced_split_points(content_full_np, SR)
268
+ print(f"[{session_id}] Split into {len(split_points)-1} chunks.")
 
 
 
269
 
270
  final_output = []
271
+ PADDING_SAMPLES = int(2.5 * SR) # کمی پدینگ بیشتر برای اطمینان
272
 
273
+ for i in range(len(split_points) - 1):
274
+ start = split_points[i]
275
+ end = split_points[i+1]
 
 
 
 
276
 
277
+ read_start = max(0, start - PADDING_SAMPLES)
278
+ read_end = end
279
 
280
  chunk_input = content_full_np[read_start:read_end]
 
 
 
 
 
281
  save_audio_pcm16(torch.FloatTensor(chunk_input).unsqueeze(0), temp_content_path, SR)
282
 
283
  try:
 
284
  gen = pipeline.inference_fm(
285
  src_wav_path=temp_content_path,
286
  timbre_ref_wav_path=temp_reference_path,
 
289
  if torch.isnan(gen).any(): gen = torch.nan_to_num(gen, nan=0.0)
290
  gen_np = gen.detach().cpu().squeeze().numpy()
291
 
292
+ trim_amount = start - read_start
293
 
294
+ if len(gen_np) > trim_amount:
295
+ valid_audio = gen_np[trim_amount:]
 
 
 
 
 
 
 
 
 
 
 
 
 
296
 
297
+ # اتصال
298
+ if len(final_output) > 0:
299
+ # اگر برش روی سکوت نبوده (اجباری)، باید کمی بیشتر کراس‌فید کنیم
300
+ # تا تغییر ناگهانی لحن مخفی شود.
301
+ fade_len = int(0.03 * SR) # 30ms standard
302
 
303
+ if len(final_output[-1]) > fade_len and len(valid_audio) > fade_len:
304
+ fade_out = np.linspace(1, 0, fade_len)
305
+ fade_in = np.linspace(0, 1, fade_len)
306
+
307
+ prev_tail = final_output[-1][-fade_len:]
308
+ curr_head = valid_audio[:fade_len]
309
+
310
  mixed = (prev_tail * fade_out) + (curr_head * fade_in)
311
  final_output[-1][-fade_len:] = mixed
312
+ valid_audio = valid_audio[fade_len:]
313
 
314
+ final_output.append(valid_audio)
315
+
316
  except Exception as e:
317
+ print(f"Error segment {i}: {e}")
318
+ # پر کردن جای خالی با سکوت برای به هم نریختن تایم
319
+ final_output.append(np.zeros(end - start))
 
 
 
320
 
 
321
  if len(final_output) > 0:
322
  full_audio = np.concatenate(final_output)
323
  else:
 
330
  if os.path.exists(temp_content_path): os.remove(temp_content_path)
331
  if os.path.exists(temp_reference_path): os.remove(temp_reference_path)
332
 
333
+ with gr.Blocks(title="Vevo-Timbre (Pro Logic)") as demo:
334
  gr.Markdown("## Vevo-Timbre: Zero-Shot Voice Conversion")
335
+ gr.Markdown("Robust Splitting: Uses Minimum Energy + Zero Crossing detection to handle fast speech without glitches.")
336
 
337
  with gr.Row():
338
  with gr.Column():