Opera8 commited on
Commit
9454331
·
verified ·
1 Parent(s): 8c417b6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +81 -77
app.py CHANGED
@@ -9,12 +9,10 @@ import torchaudio
9
  import numpy as np
10
  from huggingface_hub import snapshot_download, hf_hub_download
11
  import subprocess
12
- import re
13
- import spaces
14
  import uuid
15
  import soundfile as sf
16
 
17
- # --- تنظیمات و نصب ---
18
  downloaded_resources = {
19
  "configs": False,
20
  "tokenizer_vq8192": False,
@@ -146,7 +144,7 @@ def vevo_timbre(content_wav, reference_wav):
146
  try:
147
  SR = 24000
148
 
149
- # --- 1. پردازش ورودی ---
150
  if isinstance(content_wav, tuple):
151
  content_sr, content_data = content_wav if isinstance(content_wav[0], int) else (content_wav[1], content_wav[0])
152
  else:
@@ -159,7 +157,7 @@ def vevo_timbre(content_wav, reference_wav):
159
  content_tensor = content_tensor / (torch.max(torch.abs(content_tensor)) + 1e-6) * 0.95
160
  content_full_np = content_tensor.squeeze().numpy()
161
 
162
- # --- 2. پردازش رفرنس ---
163
  if isinstance(reference_wav, tuple):
164
  ref_sr, ref_data = reference_wav if isinstance(reference_wav[0], int) else (reference_wav[1], reference_wav[0])
165
  else:
@@ -173,52 +171,43 @@ def vevo_timbre(content_wav, reference_wav):
173
  if ref_tensor.shape[1] > SR * 20: ref_tensor = ref_tensor[:, :SR * 20]
174
  save_audio_pcm16(ref_tensor, temp_reference_path, SR)
175
 
176
- # --- 3. استراتژی جوش دادن Equal Power (500ms) ---
177
  pipeline = get_pipeline()
178
 
179
- # تنظیمات حیاتی
180
- CHUNK_DURATION = 10.0 # طول خالص هر تکه
181
- CROSSFADE_SEC = 0.5 # طول هم‌پوشانی (نیم ثانیه برای حذف لرزش)
182
 
183
- chunk_samples = int(CHUNK_DURATION * SR)
184
- crossfade_samples = int(CROSSFADE_SEC * SR)
185
  total_samples = len(content_full_np)
186
 
187
- final_output = np.array([], dtype=np.float32)
188
-
189
- # ایجاد منحنی فید Equal Power (سینوسی)
190
- # این منحنی باعث می‌شود حجم صدا در محل اتصال ثابت بماند
191
- fade_out_curve = np.cos(np.linspace(0, np.pi/2, crossfade_samples))
192
- fade_in_curve = np.sin(np.linspace(0, np.pi/2, crossfade_samples))
193
-
194
- # شروع حلقه پردازش
195
- # ما در هر مرحله به اندازه chunk_samples جلو می‌رویم
196
- # اما برای ورودی مدل، crossfade_samples را از قبل هم برمی‌داریم
197
 
198
  cursor = 0
199
- print(f"[{session_id}] Processing with 500ms Equal-Power Crossfade...")
200
 
201
  while cursor < total_samples:
202
- # تعیین بازه ورودی برای مدل
203
- # اگر اولین تکه نیست، باید کمی از عقب‌تر شروع کنیم (برای هم‌پوشانی)
204
- is_first_chunk = (cursor == 0)
 
205
 
206
- start_idx = cursor
207
- if not is_first_chunk:
208
- start_idx -= crossfade_samples # عقب‌گرد برای هم‌پوشانی
209
-
210
- end_idx = min(total_samples, cursor + chunk_samples)
211
-
212
- # اگر به انتهای فایل رسیدیم و تکه خیلی کوچک است
213
- if start_idx >= end_idx:
214
  break
215
-
216
- current_chunk_input = content_full_np[start_idx:end_idx]
217
 
218
- # ذخیره و اجرا
219
- save_audio_pcm16(torch.FloatTensor(current_chunk_input).unsqueeze(0), temp_content_path, SR)
 
 
 
 
 
 
220
 
221
  try:
 
222
  gen = pipeline.inference_fm(
223
  src_wav_path=temp_content_path,
224
  timbre_ref_wav_path=temp_reference_path,
@@ -227,60 +216,75 @@ def vevo_timbre(content_wav, reference_wav):
227
  if torch.isnan(gen).any(): gen = torch.nan_to_num(gen, nan=0.0)
228
  gen_np = gen.detach().cpu().squeeze().numpy()
229
 
230
- # --- عملیات میکس هوشمند ---
 
231
 
232
- if is_first_chunk:
233
- # تکه اول: مستقیماً اضافه کن
234
- final_output = np.concatenate([final_output, gen_np])
 
235
  else:
236
- # تکه‌های بعدی:
237
- # 1. بخش هم‌پوشانی (Crossfade Area)
238
- # 2. بخش جدید (New Area)
 
 
 
 
 
 
 
 
 
 
 
239
 
240
- if len(gen_np) < crossfade_samples:
241
- # اگر خروجی خیلی کوتاه بود (نادر)، فقط بچسبان
242
- final_output = np.concatenate([final_output, gen_np])
243
- else:
244
- # جدا کردن بخش میکس و بخش جدید از خروجی فعلی
245
- overlap_part_new = gen_np[:crossfade_samples]
246
- rest_part_new = gen_np[crossfade_samples:]
 
247
 
248
- # جدا کردن بخش میکس از انتهای خروجی قبلی
249
- if len(final_output) >= crossfade_samples:
250
- overlap_part_old = final_output[-crossfade_samples:]
251
-
252
- # فرمول Equal Power Crossfade
253
- # Old * Cos + New * Sin
254
- blended = (overlap_part_old * fade_out_curve) + (overlap_part_new * fade_in_curve)
255
-
256
- # جایگزینی انتهای آرایه اصلی با بخش میکس شده
257
- final_output[-crossfade_samples:] = blended
258
 
259
- # اضافه کردن باقی‌مانده
260
- final_output = np.concatenate([final_output, rest_part_new])
261
- else:
262
- # اگر بافر قبلی خیلی کوتاه بود (نباید پیش بیاید)
263
- final_output = np.concatenate([final_output, gen_np])
264
-
265
  except Exception as e:
266
- print(f"Error at {cursor}: {e}")
267
- # در صورت خطا سکوت اضافه کن
268
- missing = end_idx - start_idx
269
- final_output = np.concatenate([final_output, np.zeros(missing)])
270
-
271
- # حرکت به جلو
272
- cursor += chunk_samples
273
 
274
- save_audio_pcm16(final_output, output_path, SR)
 
 
 
 
 
 
275
  return output_path
276
 
277
  finally:
278
  if os.path.exists(temp_content_path): os.remove(temp_content_path)
279
  if os.path.exists(temp_reference_path): os.remove(temp_reference_path)
280
 
281
- with gr.Blocks(title="Vevo-Timbre (Pro Stitch)") as demo:
282
  gr.Markdown("## Vevo-Timbre: Zero-Shot Voice Conversion")
283
- gr.Markdown("Professional Stitching: 500ms Equal-Power Crossfade (No Jitter, No Ghosting).")
284
 
285
  with gr.Row():
286
  with gr.Column():
 
9
  import numpy as np
10
  from huggingface_hub import snapshot_download, hf_hub_download
11
  import subprocess
 
 
12
  import uuid
13
  import soundfile as sf
14
 
15
+ # --- تنظیمات و نصب پیش‌نیازها ---
16
  downloaded_resources = {
17
  "configs": False,
18
  "tokenizer_vq8192": False,
 
144
  try:
145
  SR = 24000
146
 
147
+ # --- آماده‌سازی ورودی ---
148
  if isinstance(content_wav, tuple):
149
  content_sr, content_data = content_wav if isinstance(content_wav[0], int) else (content_wav[1], content_wav[0])
150
  else:
 
157
  content_tensor = content_tensor / (torch.max(torch.abs(content_tensor)) + 1e-6) * 0.95
158
  content_full_np = content_tensor.squeeze().numpy()
159
 
160
+ # --- آماده‌سازی رفرنس ---
161
  if isinstance(reference_wav, tuple):
162
  ref_sr, ref_data = reference_wav if isinstance(reference_wav[0], int) else (reference_wav[1], reference_wav[0])
163
  else:
 
171
  if ref_tensor.shape[1] > SR * 20: ref_tensor = ref_tensor[:, :SR * 20]
172
  save_audio_pcm16(ref_tensor, temp_reference_path, SR)
173
 
174
+ # --- استراتژی Center-Only Processing (حذف لرزش) ---
175
  pipeline = get_pipeline()
176
 
177
+ # تنظیمات اصلی (به ثانیه)
178
+ CORE_CHUNK_SEC = 10.0 # مقداری که نهایتاً نگه می‌داریم
179
+ PADDING_SEC = 2.0 # مقداری که از هر طرف اضافه می‌خوانیم و دور می‌ریزیم
180
 
181
+ core_samples = int(CORE_CHUNK_SEC * SR)
182
+ padding_samples = int(PADDING_SEC * SR)
183
  total_samples = len(content_full_np)
184
 
185
+ final_output = []
 
 
 
 
 
 
 
 
 
186
 
187
  cursor = 0
188
+ print(f"[{session_id}] Starting Center-Only processing...")
189
 
190
  while cursor < total_samples:
191
+ # محاسبه بازه خواندن از فایل اصلی (Source)
192
+ # ما PADDING را از عقب و جلو اضافه می‌کنیم
193
+ read_start = max(0, cursor - padding_samples)
194
+ read_end = min(total_samples, cursor + core_samples + padding_samples)
195
 
196
+ # اگر به ته فایل رسیدیم و چیزی برای پردازش نمانده
197
+ if cursor >= total_samples:
 
 
 
 
 
 
198
  break
 
 
199
 
200
+ # استخراج تکه "پد شده"
201
+ chunk_input = content_full_np[read_start:read_end]
202
+
203
+ # اگر تکه خیلی کوچک است (انتهای فایل)، فقط پردازش کن
204
+ if len(chunk_input) < SR * 0.5:
205
+ break
206
+
207
+ save_audio_pcm16(torch.FloatTensor(chunk_input).unsqueeze(0), temp_content_path, SR)
208
 
209
  try:
210
+ # تولید صدا با حاشیه امن
211
  gen = pipeline.inference_fm(
212
  src_wav_path=temp_content_path,
213
  timbre_ref_wav_path=temp_reference_path,
 
216
  if torch.isnan(gen).any(): gen = torch.nan_to_num(gen, nan=0.0)
217
  gen_np = gen.detach().cpu().squeeze().numpy()
218
 
219
+ # --- برش هوشمند (Trimming) ---
220
+ # حالا باید حاشیه‌های ناپایدار (لرزش دار) را حذف کنیم
221
 
222
+ # 1. محاسبه مقدار برش از ابتدا (Front Trim)
223
+ # اگر اولین تکه است، ما PADDING نداشتیم (چون read_start=0 بود)
224
+ if cursor == 0:
225
+ trim_front = 0
226
  else:
227
+ # در غیر این صورت، دقیقاً به اندازه PADDING از جلو می‌بریم
228
+ trim_front = padding_samples
229
+
230
+ # 2. محاسبه مقدار برش از انتها (End Trim)
231
+ # ما می‌خواهیم فقط به اندازه CORE (10 ثانیه) نگه داریم
232
+ # اما باید مراقب انتهای فایل باشیم
233
+
234
+ # طول معتبر این تکه در خروجی نهایی
235
+ valid_length = min(core_samples, total_samples - cursor)
236
+
237
+ # استخراج بخش مرکزی (Stable Core)
238
+ # از trim_front شروع کن و به اندازه valid_length بردار
239
+ if len(gen_np) > trim_front:
240
+ core_audio = gen_np[trim_front : trim_front + valid_length]
241
 
242
+ # --- اتصال میکروسکوپی (Micro Crossfade 50ms) ---
243
+ # این فقط برای جلوگیری از کلیک دیجیتال است، نه برای تغییر لحن
244
+ fade_len = int(0.05 * SR) # 50ms
245
+
246
+ if len(final_output) > 0 and len(core_audio) > fade_len:
247
+ # نرم کردن اتصال
248
+ fade_out = np.linspace(1, 0, fade_len)
249
+ fade_in = np.linspace(0, 1, fade_len)
250
 
251
+ # آخرین تکه لیست
252
+ prev_tail = final_output[-1][-fade_len:]
253
+ curr_head = core_audio[:fade_len]
254
+
255
+ # اگر سایزها یکی بود میکس کن
256
+ if len(prev_tail) == fade_len:
257
+ mixed = (prev_tail * fade_out) + (curr_head * fade_in)
258
+ final_output[-1][-fade_len:] = mixed
259
+ # حذف بخش میکس شده از تکه جدید
260
+ core_audio = core_audio[fade_len:]
261
 
262
+ final_output.append(core_audio)
263
+
 
 
 
 
264
  except Exception as e:
265
+ print(f"Error processing chunk at {cursor}: {e}")
266
+ missing = min(core_samples, total_samples - cursor)
267
+ final_output.append(np.zeros(missing))
268
+
269
+ # جلو رفتن نشانگر به اندازه هسته اصلی (بدون هم‌پوشانی منطقی)
270
+ cursor += core_samples
 
271
 
272
+ # چسباندن نهایی
273
+ if len(final_output) > 0:
274
+ full_audio = np.concatenate(final_output)
275
+ else:
276
+ full_audio = np.zeros(SR)
277
+
278
+ save_audio_pcm16(full_audio, output_path, SR)
279
  return output_path
280
 
281
  finally:
282
  if os.path.exists(temp_content_path): os.remove(temp_content_path)
283
  if os.path.exists(temp_reference_path): os.remove(temp_reference_path)
284
 
285
+ with gr.Blocks(title="Vevo-Timbre (Stable Core)") as demo:
286
  gr.Markdown("## Vevo-Timbre: Zero-Shot Voice Conversion")
287
+ gr.Markdown("Center-Only Processing Strategy: Generates extra padding and discards unstable edges to remove jitter.")
288
 
289
  with gr.Row():
290
  with gr.Column():