Opera8 commited on
Commit
3b10964
·
verified ·
1 Parent(s): 454cbe7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -59
app.py CHANGED
@@ -174,7 +174,7 @@ def vevo_timbre(content_wav, reference_wav):
174
  raise ValueError("Please upload audio files")
175
 
176
  try:
177
- # --- آماده سازی Content ---
178
  if isinstance(content_wav, tuple):
179
  content_sr, content_data = content_wav if isinstance(content_wav[0], int) else (content_wav[1], content_wav[0])
180
  else:
@@ -189,7 +189,7 @@ def vevo_timbre(content_wav, reference_wav):
189
  content_sr = 24000
190
  content_tensor = content_tensor / (torch.max(torch.abs(content_tensor)) + 1e-6) * 0.95
191
 
192
- # --- آماده سازی Reference ---
193
  if isinstance(reference_wav, tuple):
194
  ref_sr, ref_data = reference_wav if isinstance(reference_wav[0], int) else (reference_wav[1], reference_wav[0])
195
  else:
@@ -209,94 +209,129 @@ def vevo_timbre(content_wav, reference_wav):
209
 
210
  save_audio_pcm16(ref_tensor, temp_reference_path, ref_sr)
211
 
212
- # --- تنظیمات میکسینگ عمیق (Deep Cross-Fade) ---
213
  pipeline = get_pipeline()
214
- SR = 24000
215
 
216
- # گام حرکت: ۱۰ ثانیه
217
- STEP_SIZE = 10 * SR
218
- # طول پردازش: ۱۲ ثانیه (۲ ثانیه همپوشانی)
219
- # این ۲ ثانیه اضافه باعث می‌شود هیچوقت لبه تیز نداشته باشیم
220
- PROCESS_LEN = 12 * SR
221
- OVERLAP = PROCESS_LEN - STEP_SIZE # 2 seconds
222
 
223
  total_samples = content_tensor.shape[1]
224
- print(f"[{session_id}] Processing Deep Mix Mode...")
225
 
226
- # آرایه نهایی را کمی بزرگتر می‌گیریم تا جا کم نیاید
227
- final_audio = np.zeros(total_samples + OVERLAP)
228
- # آرایه‌ای برای شمارش وزن‌ها (برای نرمال کردن میکس)
229
- weight_accumulator = np.zeros(total_samples + OVERLAP)
230
 
231
- # ایجاد پنجره محو شونده (Trapezoid Window)
232
- # 1 ثانیه Fade In -- 10 ثانیه ثابت -- 1 ثانیه Fade Out
233
- fade_samples = SR # 1 second fade
234
- window = np.ones(PROCESS_LEN)
235
- window[:fade_samples] = np.linspace(0, 1, fade_samples)
236
- window[-fade_samples:] = np.linspace(1, 0, fade_samples)
237
 
238
- for start in range(0, total_samples, STEP_SIZE):
239
- # انتخاب بازه ورودی (کمی بزرگتر از استپ)
240
- end = min(start + PROCESS_LEN, total_samples)
241
- current_len = end - start
 
 
 
 
 
242
 
243
- if current_len <= 0: break
 
 
 
 
 
 
 
 
 
244
 
245
- current_input_chunk = content_tensor[:, start:end]
246
- save_audio_pcm16(current_input_chunk, temp_content_path, SR)
247
 
248
  try:
249
  gen = pipeline.inference_fm(
250
  src_wav_path=temp_content_path,
251
  timbre_ref_wav_path=temp_reference_path,
252
- flow_matching_steps=64,
253
  )
254
 
255
  if torch.isnan(gen).any(): gen = torch.nan_to_num(gen, nan=0.0)
256
- gen = gen.cpu().squeeze().numpy()
 
257
 
258
- # اگر طول خروجی کمتر از انتظار بود، با سکوت پر کن
259
- if len(gen) < current_len:
260
- gen = np.pad(gen, (0, current_len - len(gen)))
261
- elif len(gen) > current_len:
262
- gen = gen[:current_len]
 
 
263
 
264
- # اعمال پنجره روی خروجی (اگر تکه آخر است، پنجره را برش بزن)
265
- current_window = window[:current_len]
266
- # برای تکه اول، Fade In نیاز نیست (چون شروع فایل است)
267
- if start == 0:
268
- current_window[:fade_samples] = 1.0
269
- # برای تکه آخر، Fade Out نیاز نیست (چون پایان فایل است)
270
- if end == total_samples:
271
- current_window[-fade_samples:] = 1.0
272
 
273
- weighted_gen = gen * current_window
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
 
275
- # اضافه کردن به آرایه اصلی (Overlap-Add)
276
- final_audio[start:end] += weighted_gen
277
- weight_accumulator[start:end] += current_window
 
 
 
 
 
 
 
278
 
279
  except Exception as e:
280
- print(f"Error: {e}")
 
 
 
 
 
281
 
282
- # نرمال‌سازی نهایی (تقسیم بر وزن‌ها برای یکنواخت شدن صدا در نقاط اتصال)
283
- # جایی که وزن صفر است را ۱ می‌کنیم تا تقسیم بر صفر نشود
284
- weight_accumulator[weight_accumulator == 0] = 1.0
285
- final_audio = final_audio / weight_accumulator
286
-
287
- # برش بخش‌های اضافه انتهای فایل
288
- final_audio = final_audio[:total_samples]
289
-
290
- save_audio_pcm16(final_audio, output_path, SR)
291
  return output_path
292
 
293
  finally:
294
  if os.path.exists(temp_content_path): os.remove(temp_content_path)
295
  if os.path.exists(temp_reference_path): os.remove(temp_reference_path)
296
 
297
- with gr.Blocks(title="Vevo-Timbre (Seamless)") as demo:
298
  gr.Markdown("## Vevo-Timbre: Zero-Shot Voice Conversion")
299
- gr.Markdown("نسخه Seamless: استفاده از تکنیک Overlap-Add برای حذف کامل پرش و لرزش صدا.")
300
 
301
  with gr.Row():
302
  with gr.Column():
 
174
  raise ValueError("Please upload audio files")
175
 
176
  try:
177
+ # --- پردازش ورودی ---
178
  if isinstance(content_wav, tuple):
179
  content_sr, content_data = content_wav if isinstance(content_wav[0], int) else (content_wav[1], content_wav[0])
180
  else:
 
189
  content_sr = 24000
190
  content_tensor = content_tensor / (torch.max(torch.abs(content_tensor)) + 1e-6) * 0.95
191
 
192
+ # --- پردازش رفرنس ---
193
  if isinstance(reference_wav, tuple):
194
  ref_sr, ref_data = reference_wav if isinstance(reference_wav[0], int) else (reference_wav[1], reference_wav[0])
195
  else:
 
209
 
210
  save_audio_pcm16(ref_tensor, temp_reference_path, ref_sr)
211
 
212
+ # --- منطق حرفه‌ای Warm-up Context Stitching ---
213
  pipeline = get_pipeline()
 
214
 
215
+ SR = 24000
216
+ STEP_SIZE = 10 * SR # هر 10 ثانیه جلو می‌رویم
217
+ WARMUP_SIZE = 3 * SR # 3 ثانیه کانتکست (نگاه به عقب) برای گرم شدن
218
+ CROSSFADE_SIZE = 1 * SR # 1 ثانیه میکس برای نرم کردن اتصال
 
 
219
 
220
  total_samples = content_tensor.shape[1]
221
+ print(f"[{session_id}] Duration: {total_samples/SR:.2f}s. Studio Mode (Warm-up + Crossfade)...")
222
 
223
+ final_audio = []
224
+ previous_tail = None # نگهداری ۱ ثانیه آخر تکه قبلی برای میکس
 
 
225
 
226
+ # حلقه روی تکه‌ها
227
+ current_pos = 0
 
 
 
 
228
 
229
+ while current_pos < total_samples:
230
+ # محاسبه دقیق بازه ورودی
231
+ # اگر اولین تکه نیستیم، 3 ثانیه عقب‌تر شروع می‌کنیم (Warm-up)
232
+ if current_pos == 0:
233
+ start_input = 0
234
+ warmup_cut = 0
235
+ else:
236
+ start_input = max(0, current_pos - WARMUP_SIZE)
237
+ warmup_cut = current_pos - start_input # مقداری که باید از اول خروجی دور بریزیم
238
 
239
+ # پایان این تکه (10 ثانیه جلوتر + 1 ثانیه اضافه برای میکس بعدی)
240
+ end_input = min(current_pos + STEP_SIZE + CROSSFADE_SIZE, total_samples)
241
+
242
+ # اگر دیتایی نمانده، تمام
243
+ if start_input >= end_input:
244
+ break
245
+
246
+ # استخراج تکه ورودی
247
+ chunk_tensor = content_tensor[:, start_input:end_input]
248
+ save_audio_pcm16(chunk_tensor, temp_content_path, SR)
249
 
250
+ print(f"[{session_id}] Processing chunk starting at {current_pos/SR:.1f}s (with context)")
 
251
 
252
  try:
253
  gen = pipeline.inference_fm(
254
  src_wav_path=temp_content_path,
255
  timbre_ref_wav_path=temp_reference_path,
256
+ flow_matching_steps=64, # کیفیت 64 پله‌ای
257
  )
258
 
259
  if torch.isnan(gen).any(): gen = torch.nan_to_num(gen, nan=0.0)
260
+ if gen.dim() == 1: gen = gen.unsqueeze(0)
261
+ gen = gen.cpu().squeeze(0).numpy()
262
 
263
+ # 1. حذف قسمت Warm-up (که قبلاً ساخته شده بود)
264
+ if warmup_cut > 0:
265
+ # اما صبر کن! ما باید CROSSFADE_SIZE تا قبل از نقطه برش را نگه داریم برای میکس
266
+ # پس برش را کمی عقب‌تر می‌زنیم تا همپوشانی داشته باشیم
267
+ valid_start = warmup_cut - CROSSFADE_SIZE
268
+ if valid_start < 0: valid_start = 0 # نباید پیش بیاد
269
+ gen = gen[valid_start:]
270
 
271
+ # الان `gen` شامل: [همپوشانی با قبلی] + [تکه جدید] + [همپوشانی با بعدی] است.
 
 
 
 
 
 
 
272
 
273
+ # 2. میکس با تکه قبلی (اگر وجود دارد)
274
+ if previous_tail is not None:
275
+ # جدا کردن قسمت همپوشانی از این تکه
276
+ overlap_part = gen[:CROSSFADE_SIZE]
277
+ new_part = gen[CROSSFADE_SIZE:]
278
+
279
+ # اگر سایزها یکی بود میکس کن
280
+ if len(overlap_part) == len(previous_tail):
281
+ alpha = np.linspace(0, 1, len(overlap_part))
282
+ blended = (previous_tail * (1 - alpha)) + (overlap_part * alpha)
283
+ final_audio.append(blended)
284
+ else:
285
+ # فال‌بک (نباید پیش بیاد)
286
+ final_audio.append(previous_tail)
287
+
288
+ # حالا قسمت جدید را پردازش می‌کنیم
289
+ # باید قسمت انتهایی را برای دور بعد ذخیره کنیم
290
+ if len(new_part) > CROSSFADE_SIZE and end_input < total_samples:
291
+ # ذخیره دم برای دور بعد
292
+ previous_tail = new_part[-CROSSFADE_SIZE:]
293
+ # اضافه کردن بدنه اصلی
294
+ final_audio.append(new_part[:-CROSSFADE_SIZE])
295
+ else:
296
+ # تکه آخر است، کلش را اضافه کن
297
+ final_audio.append(new_part)
298
+ previous_tail = None
299
 
300
+ else:
301
+ # تکه اول است
302
+ if len(gen) > CROSSFADE_SIZE and end_input < total_samples:
303
+ previous_tail = gen[-CROSSFADE_SIZE:]
304
+ final_audio.append(gen[:-CROSSFADE_SIZE])
305
+ else:
306
+ final_audio.append(gen)
307
+ previous_tail = None
308
+
309
+ current_pos += STEP_SIZE
310
 
311
  except Exception as e:
312
+ print(f"Error in chunk: {e}")
313
+ # در صورت خطا، پرش کن (بهتر از قطع شدن است)
314
+ current_pos += STEP_SIZE
315
+ # اضافه کردن سکوت
316
+ final_audio.append(np.zeros(STEP_SIZE))
317
+ previous_tail = None
318
 
319
+ # چسباندن نهایی
320
+ if len(final_audio) > 0:
321
+ full_audio = np.concatenate(final_audio)
322
+ else:
323
+ full_audio = np.zeros(24000)
324
+
325
+ save_audio_pcm16(full_audio, output_path, SR)
 
 
326
  return output_path
327
 
328
  finally:
329
  if os.path.exists(temp_content_path): os.remove(temp_content_path)
330
  if os.path.exists(temp_reference_path): os.remove(temp_reference_path)
331
 
332
+ with gr.Blocks(title="Vevo-Timbre (Studio)") as demo:
333
  gr.Markdown("## Vevo-Timbre: Zero-Shot Voice Conversion")
334
+ gr.Markdown("نسخه استودیویی: بدون پرش، بدون تداخل زمانی.")
335
 
336
  with gr.Row():
337
  with gr.Column():