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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +61 -83
app.py CHANGED
@@ -187,6 +187,7 @@ def vevo_timbre(content_wav, reference_wav):
187
  if content_sr != 24000:
188
  content_tensor = torchaudio.functional.resample(content_tensor, content_sr, 24000)
189
  content_sr = 24000
 
190
  content_tensor = content_tensor / (torch.max(torch.abs(content_tensor)) + 1e-6) * 0.95
191
 
192
  # --- پردازش رفرنس ---
@@ -202,123 +203,100 @@ def vevo_timbre(content_wav, reference_wav):
202
  if ref_sr != 24000:
203
  ref_tensor = torchaudio.functional.resample(ref_tensor, ref_sr, 24000)
204
  ref_sr = 24000
205
- ref_tensor = ref_tensor / (torch.max(torch.abs(ref_tensor)) + 1e-6) * 0.95
 
 
206
 
207
  if ref_tensor.shape[1] > 24000 * 20:
208
  ref_tensor = ref_tensor[:, :24000 * 20]
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
 
@@ -329,9 +307,9 @@ def vevo_timbre(content_wav, reference_wav):
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():
 
187
  if content_sr != 24000:
188
  content_tensor = torchaudio.functional.resample(content_tensor, content_sr, 24000)
189
  content_sr = 24000
190
+
191
  content_tensor = content_tensor / (torch.max(torch.abs(content_tensor)) + 1e-6) * 0.95
192
 
193
  # --- پردازش رفرنس ---
 
203
  if ref_sr != 24000:
204
  ref_tensor = torchaudio.functional.resample(ref_tensor, ref_sr, 24000)
205
  ref_sr = 24000
206
+
207
+ ref_max = torch.max(torch.abs(ref_tensor)) + 1e-6
208
+ ref_tensor = ref_tensor / ref_max * 0.95
209
 
210
  if ref_tensor.shape[1] > 24000 * 20:
211
  ref_tensor = ref_tensor[:, :24000 * 20]
212
 
213
  save_audio_pcm16(ref_tensor, temp_reference_path, ref_sr)
214
 
215
+ # --- منطق Pre-roll Chunking (حفظ لحن پیوسته) ---
216
  pipeline = get_pipeline()
217
 
218
  SR = 24000
219
+ TARGET_CHUNK_LEN = 10 * SR # طول هدف برای هر تکه جدید (۱۰ ثانیه)
220
+ CONTEXT_LEN = int(2.5 * SR) # مقدار پیش‌خوانی (۲.۵ ثانیه)
 
221
 
222
  total_samples = content_tensor.shape[1]
223
+ print(f"[{session_id}] Processing with Pre-roll context...")
224
 
225
+ final_output = []
 
226
 
227
+ # مکان‌نما: نشان می‌دهد تا کجای فایل را "نهایی" کرده‌ایم
228
+ current_cursor = 0
229
 
230
+ while current_cursor < total_samples:
231
+ # تعیین بازه ورودی به مدل
232
+ # شروع: از ۲.۵ ثانیه قبل (اگر وجود داشته باشد)
233
+ start_input = max(0, current_cursor - CONTEXT_LEN)
234
+ # پایان: ۱۰ ثانیه جلوتر از مکان‌نما
235
+ end_input = min(current_cursor + TARGET_CHUNK_LEN, total_samples)
 
 
 
236
 
237
+ # اگر به ته فایل رسیدیم و چیزی نمانده
 
 
 
238
  if start_input >= end_input:
239
  break
240
+
241
+ # استخراج تکه (شامل کانتکست + دیتای جدید)
242
  chunk_tensor = content_tensor[:, start_input:end_input]
243
  save_audio_pcm16(chunk_tensor, temp_content_path, SR)
244
 
 
 
245
  try:
246
+ # تولید صدا
247
  gen = pipeline.inference_fm(
248
  src_wav_path=temp_content_path,
249
  timbre_ref_wav_path=temp_reference_path,
250
+ flow_matching_steps=64,
251
  )
252
 
253
  if torch.isnan(gen).any(): gen = torch.nan_to_num(gen, nan=0.0)
254
  if gen.dim() == 1: gen = gen.unsqueeze(0)
255
  gen = gen.cpu().squeeze(0).numpy()
256
 
257
+ # --- برش هوشمند ---
258
+ # ما به مدل (start_input تا end_input) را دادیم.
259
+ # اما فقط قسمت (current_cursor تا end_input) را می‌خواهیم.
260
+ # پس باید قسمت اول (که مربوط به کانتکست است) را ببریم.
 
 
 
261
 
262
+ cut_amount = current_cursor - start_input
263
 
264
+ if len(gen) > cut_amount:
265
+ valid_part = gen[cut_amount:]
 
 
 
266
 
267
+ # اعمال یک میکرو-فید خیلی کوتاه (10 میلی ثانیه) فقط برای رفع نویز اتصال PCM
268
+ # این Cross-fade نیست، فقط De-click است
269
+ if len(final_output) > 0:
270
+ fade_samples = int(0.01 * SR) # 10ms
271
+ if len(valid_part) > fade_samples and len(final_output[-1]) > fade_samples:
272
+ # نرم کردن اتصال
273
+ fade_in = np.linspace(0, 1, fade_samples)
274
+ fade_out = np.linspace(1, 0, fade_samples)
275
+
276
+ # میکس ۱۰ میلی ثانیه آخرِ قبلی با ۱۰ میلی ثانیه اولِ جدید
277
+ prev_tail = final_output[-1][-fade_samples:]
278
+ curr_head = valid_part[:fade_samples]
279
+
280
+ # جایگزینی دم قبلی (میکس شده)
281
+ final_output[-1][-fade_samples:] = (prev_tail * fade_out) + (curr_head * fade_in)
282
+ # حذف سرِ فعلی (چون میکس شد)
283
+ valid_part = valid_part[fade_samples:]
284
+
285
+ final_output.append(valid_part)
 
 
 
 
 
 
 
 
 
 
286
 
287
+ # آپدیت مکان‌نما
288
+ current_cursor = end_input
289
 
290
  except Exception as e:
291
+ print(f"Error: {e}")
292
+ # مدیریت خطا
293
+ missing = end_input - current_cursor
294
+ final_output.append(np.zeros(missing))
295
+ current_cursor = end_input
296
+
297
+ # چسباندن
298
+ if len(final_output) > 0:
299
+ full_audio = np.concatenate(final_output)
 
300
  else:
301
  full_audio = np.zeros(24000)
302
 
 
307
  if os.path.exists(temp_content_path): os.remove(temp_content_path)
308
  if os.path.exists(temp_reference_path): os.remove(temp_reference_path)
309
 
310
+ with gr.Blocks(title="Vevo-Timbre (Seamless)") as demo:
311
  gr.Markdown("## Vevo-Timbre: Zero-Shot Voice Conversion")
312
+ gr.Markdown("نسخه نهایی: اتصال کاملاً پیوسته و بدون شوک (Pre-roll Context).")
313
 
314
  with gr.Row():
315
  with gr.Column():