Opera8 commited on
Commit
501163c
·
verified ·
1 Parent(s): 84bdd36

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -75
app.py CHANGED
@@ -13,11 +13,8 @@ import re
13
  import spaces
14
  import uuid
15
  import soundfile as sf
16
- # اضافه شدن کتابخانه PyDub
17
- from pydub import AudioSegment
18
- import io
19
 
20
- # --- نصب و پچ کردن ---
21
  downloaded_resources = {
22
  "configs": False,
23
  "tokenizer_vq8192": False,
@@ -75,22 +72,13 @@ os.makedirs("ckpts/Vevo", exist_ok=True)
75
 
76
  from models.vc.vevo.vevo_utils import VevoInferencePipeline
77
 
78
- # --- توابع کمکی جدید برای PyDub ---
79
- def numpy_to_audiosegment(audio_arr, sample_rate=24000):
80
- """تبدیل آرایه نامپای (Float32) به آبجکت AudioSegment"""
81
- # تبدیل به PCM 16-bit
82
- audio_int16 = (audio_arr * 32767).astype(np.int16)
83
- # ایجاد فایل در حافظه
84
- byte_io = io.BytesIO()
85
- sf.write(byte_io, audio_int16, sample_rate, format='WAV', subtype='PCM_16')
86
- byte_io.seek(0)
87
- return AudioSegment.from_wav(byte_io)
88
-
89
  def save_audio_pcm16(waveform, output_path, sample_rate=24000):
90
- # این تابع فقط برای ذخیره فایل‌های موقت ورودی مدل است
91
  try:
92
  if isinstance(waveform, torch.Tensor):
93
- waveform = waveform.detach().cpu().squeeze().numpy()
 
 
 
94
  sf.write(output_path, waveform, sample_rate, subtype='PCM_16')
95
  except Exception as e:
96
  print(f"Save error: {e}")
@@ -115,10 +103,8 @@ device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cp
115
  inference_pipelines = {}
116
 
117
  def preload_all_resources():
118
- print("Preloading resources...")
119
  setup_configs()
120
  global downloaded_content_style_tokenizer_path, downloaded_fmt_path, downloaded_vocoder_path
121
-
122
  if not downloaded_resources["tokenizer_vq8192"]:
123
  downloaded_content_style_tokenizer_path = snapshot_download(repo_id="amphion/Vevo", repo_type="model", cache_dir="./ckpts/Vevo", allow_patterns=["tokenizer/vq8192/*"])
124
  downloaded_resources["tokenizer_vq8192"] = True
@@ -128,7 +114,6 @@ def preload_all_resources():
128
  if not downloaded_resources["vocoder"]:
129
  downloaded_vocoder_path = snapshot_download(repo_id="amphion/Vevo", repo_type="model", cache_dir="./ckpts/Vevo", allow_patterns=["acoustic_modeling/Vocoder/*"])
130
  downloaded_resources["vocoder"] = True
131
- print("Resources ready.")
132
 
133
  downloaded_content_style_tokenizer_path = None
134
  downloaded_fmt_path = None
@@ -159,6 +144,8 @@ def vevo_timbre(content_wav, reference_wav):
159
  raise ValueError("Please upload audio files")
160
 
161
  try:
 
 
162
  # --- 1. پردازش ورودی ---
163
  if isinstance(content_wav, tuple):
164
  content_sr, content_data = content_wav if isinstance(content_wav[0], int) else (content_wav[1], content_wav[0])
@@ -167,8 +154,8 @@ def vevo_timbre(content_wav, reference_wav):
167
  if len(content_data.shape) > 1: content_data = np.mean(content_data, axis=1)
168
 
169
  content_tensor = torch.FloatTensor(content_data).unsqueeze(0)
170
- if content_sr != 24000:
171
- content_tensor = torchaudio.functional.resample(content_tensor, content_sr, 24000)
172
  content_tensor = content_tensor / (torch.max(torch.abs(content_tensor)) + 1e-6) * 0.95
173
  content_full_np = content_tensor.squeeze().numpy()
174
 
@@ -180,37 +167,56 @@ def vevo_timbre(content_wav, reference_wav):
180
  if len(ref_data.shape) > 1: ref_data = np.mean(ref_data, axis=1)
181
 
182
  ref_tensor = torch.FloatTensor(ref_data).unsqueeze(0)
183
- if ref_sr != 24000:
184
- ref_tensor = torchaudio.functional.resample(ref_tensor, ref_sr, 24000)
185
  ref_tensor = ref_tensor / (torch.max(torch.abs(ref_tensor)) + 1e-6) * 0.95
186
- if ref_tensor.shape[1] > 24000 * 20: ref_tensor = ref_tensor[:, :24000 * 20]
187
- save_audio_pcm16(ref_tensor, temp_reference_path, 24000)
188
 
189
- # --- 3. منطق پردازش با استفاده از PyDub ---
190
  pipeline = get_pipeline()
191
- SR = 24000
192
 
193
- NEW_CHUNK_SEC = 10.0
194
- CONTEXT_SEC = 3.0
 
195
 
196
- new_chunk_samples = int(NEW_CHUNK_SEC * SR)
197
- context_samples = int(CONTEXT_SEC * SR)
198
  total_samples = len(content_full_np)
199
 
200
- # ایجاد یک AudioSegment خالی برای جمع‌آوری خروجی نهایی
201
- final_audio_segment = AudioSegment.empty()
202
 
203
- current_cursor = 0
204
- print(f"[{session_id}] Processing with PyDub stitching...")
 
 
205
 
206
- while current_cursor < total_samples:
207
- start_slice = max(0, current_cursor - context_samples)
208
- end_slice = min(total_samples, current_cursor + new_chunk_samples)
 
 
 
 
 
 
 
 
209
 
210
- if start_slice >= end_slice: break
 
 
211
 
212
- chunk_np = content_full_np[start_slice:end_slice]
213
- save_audio_pcm16(torch.FloatTensor(chunk_np).unsqueeze(0), temp_content_path, SR)
 
 
 
 
 
 
 
 
214
 
215
  try:
216
  gen = pipeline.inference_fm(
@@ -218,53 +224,63 @@ def vevo_timbre(content_wav, reference_wav):
218
  timbre_ref_wav_path=temp_reference_path,
219
  flow_matching_steps=64,
220
  )
221
-
222
  if torch.isnan(gen).any(): gen = torch.nan_to_num(gen, nan=0.0)
223
  gen_np = gen.detach().cpu().squeeze().numpy()
224
 
225
- # محاسبه مقدار برشی (حذف کانتکست تکراری)
226
- trim_samples = current_cursor - start_slice
227
 
228
- if len(gen_np) > trim_samples:
229
- valid_part_np = gen_np[trim_samples:]
230
-
231
- # تبدیل به فرمت PyDub
232
- new_segment = numpy_to_audiosegment(valid_part_np, SR)
 
 
233
 
234
- # اتصال:
235
- # اگر اولین تکه نیست، یک فید (Crossfade) بسیار کوتاه (5 میلی ثانیه)
236
- # اعمال می‌کنیم تا صدای "تیک" حذف شود.
237
- if len(final_audio_segment) > 0:
238
- # تکنیک: یک فید بسیار ریز (Crossfade 5ms)
239
- # نکته: PyDub برای کراس‌فید نیاز به همپوشانی دارد، اما چون ما کانتکست را دقیق بریدیم،
240
- # اینجا از append ساده استفاده می‌کنیم و فقط لبه‌ها را نرم می‌کنیم.
241
-
242
- # نرم کردن ابتدای تکه جدید (Fade In 5ms)
243
- new_segment = new_segment.fade_in(5)
244
- # نرم کردن انتهای تکه قبلی (Fade Out 5ms) - (قبلاً انجام شده یا الان انجام میدیم)
245
- # در اینجا فقط چسباندن (Append) با فید این کافیست.
246
-
247
- final_audio_segment += new_segment
248
  else:
249
- final_audio_segment += new_segment
250
-
251
- current_cursor = end_slice
252
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  except Exception as e:
254
- print(f"Error: {e}")
255
- current_cursor = end_slice # Skip on error
 
 
 
 
 
256
 
257
- # ذخیره خروجی نهایی با PyDub
258
- final_audio_segment.export(output_path, format="wav")
259
  return output_path
260
 
261
  finally:
262
  if os.path.exists(temp_content_path): os.remove(temp_content_path)
263
  if os.path.exists(temp_reference_path): os.remove(temp_reference_path)
264
 
265
- with gr.Blocks(title="Vevo-Timbre (PyDub)") as demo:
266
- gr.Markdown("## Vevo-Timbre: Voice Conversion")
267
- gr.Markdown("Seamless stitching powered by PyDub library.")
268
 
269
  with gr.Row():
270
  with gr.Column():
 
13
  import spaces
14
  import uuid
15
  import soundfile as sf
 
 
 
16
 
17
+ # --- تنظیمات و نصب ---
18
  downloaded_resources = {
19
  "configs": False,
20
  "tokenizer_vq8192": False,
 
72
 
73
  from models.vc.vevo.vevo_utils import VevoInferencePipeline
74
 
 
 
 
 
 
 
 
 
 
 
 
75
  def save_audio_pcm16(waveform, output_path, sample_rate=24000):
 
76
  try:
77
  if isinstance(waveform, torch.Tensor):
78
+ waveform = waveform.detach().cpu()
79
+ if waveform.dim() == 2 and waveform.shape[0] == 1:
80
+ waveform = waveform.squeeze(0)
81
+ waveform = waveform.numpy()
82
  sf.write(output_path, waveform, sample_rate, subtype='PCM_16')
83
  except Exception as e:
84
  print(f"Save error: {e}")
 
103
  inference_pipelines = {}
104
 
105
  def preload_all_resources():
 
106
  setup_configs()
107
  global downloaded_content_style_tokenizer_path, downloaded_fmt_path, downloaded_vocoder_path
 
108
  if not downloaded_resources["tokenizer_vq8192"]:
109
  downloaded_content_style_tokenizer_path = snapshot_download(repo_id="amphion/Vevo", repo_type="model", cache_dir="./ckpts/Vevo", allow_patterns=["tokenizer/vq8192/*"])
110
  downloaded_resources["tokenizer_vq8192"] = True
 
114
  if not downloaded_resources["vocoder"]:
115
  downloaded_vocoder_path = snapshot_download(repo_id="amphion/Vevo", repo_type="model", cache_dir="./ckpts/Vevo", allow_patterns=["acoustic_modeling/Vocoder/*"])
116
  downloaded_resources["vocoder"] = True
 
117
 
118
  downloaded_content_style_tokenizer_path = None
119
  downloaded_fmt_path = None
 
144
  raise ValueError("Please upload audio files")
145
 
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])
 
154
  if len(content_data.shape) > 1: content_data = np.mean(content_data, axis=1)
155
 
156
  content_tensor = torch.FloatTensor(content_data).unsqueeze(0)
157
+ if content_sr != SR:
158
+ content_tensor = torchaudio.functional.resample(content_tensor, content_sr, SR)
159
  content_tensor = content_tensor / (torch.max(torch.abs(content_tensor)) + 1e-6) * 0.95
160
  content_full_np = content_tensor.squeeze().numpy()
161
 
 
167
  if len(ref_data.shape) > 1: ref_data = np.mean(ref_data, axis=1)
168
 
169
  ref_tensor = torch.FloatTensor(ref_data).unsqueeze(0)
170
+ if ref_sr != SR:
171
+ ref_tensor = torchaudio.functional.resample(ref_tensor, ref_sr, SR)
172
  ref_tensor = ref_tensor / (torch.max(torch.abs(ref_tensor)) + 1e-6) * 0.95
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(
 
224
  timbre_ref_wav_path=temp_reference_path,
225
  flow_matching_steps=64,
226
  )
 
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():