Update app.py
Browse files
app.py
CHANGED
|
@@ -14,7 +14,7 @@ import shutil
|
|
| 14 |
import re
|
| 15 |
import gradio as gr
|
| 16 |
|
| 17 |
-
# --- 1. نصب و تنظیمات اولیه ---
|
| 18 |
|
| 19 |
def install_espeak():
|
| 20 |
try:
|
|
@@ -204,6 +204,84 @@ def get_pipeline():
|
|
| 204 |
inference_pipelines["timbre"] = pipeline
|
| 205 |
return pipeline
|
| 206 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
# =========================================================================
|
| 208 |
# سیستم استنتاج مستقیم بر روی Zero-GPU با رابط کاربری Gradio
|
| 209 |
# =========================================================================
|
|
@@ -218,10 +296,10 @@ def predict_voice_conversion(source_audio_path, ref_audio_path):
|
|
| 218 |
SR = 24000
|
| 219 |
|
| 220 |
try:
|
| 221 |
-
# پردازش
|
| 222 |
content_data, _ = librosa.load(source_audio_path, sr=SR)
|
| 223 |
|
| 224 |
-
# پردازش
|
| 225 |
ref_data, _ = librosa.load(ref_audio_path, sr=SR)
|
| 226 |
ref_tensor = torch.FloatTensor(ref_data).unsqueeze(0)
|
| 227 |
ref_tensor = ref_tensor / (torch.max(torch.abs(ref_tensor)) + 1e-6) * 0.95
|
|
@@ -229,26 +307,13 @@ def predict_voice_conversion(source_audio_path, ref_audio_path):
|
|
| 229 |
temp_r = f"wav/{task_id}_temp_r.wav"
|
| 230 |
save_audio_pcm16(ref_tensor, temp_r, SR)
|
| 231 |
|
| 232 |
-
#
|
| 233 |
-
|
| 234 |
-
min_chunk_duration = 1.5 # حداقل طول مجاز برای آخرین قطعه (جهت جلوگیری از خطا)
|
| 235 |
-
chunk_samples = int(chunk_duration * SR)
|
| 236 |
-
min_samples = int(min_chunk_duration * SR)
|
| 237 |
-
total_samples = len(content_data)
|
| 238 |
-
|
| 239 |
-
chunks = []
|
| 240 |
-
for i in range(0, total_samples, chunk_samples):
|
| 241 |
-
chunks.append(content_data[i:i + chunk_samples])
|
| 242 |
-
|
| 243 |
-
# در صورتی که قطعه آخر بسیار کوتاه باشد، آن را با قطعه قبلی ترکیب میکنیم
|
| 244 |
-
if len(chunks) > 1 and len(chunks[-1]) < min_samples:
|
| 245 |
-
last_chunk = chunks.pop()
|
| 246 |
-
chunks[-1] = np.concatenate([chunks[-1], last_chunk])
|
| 247 |
|
| 248 |
generated_chunks = []
|
| 249 |
pipeline = get_pipeline()
|
| 250 |
|
| 251 |
-
#
|
| 252 |
for idx, chunk in enumerate(chunks):
|
| 253 |
chunk_tensor = torch.FloatTensor(chunk).unsqueeze(0)
|
| 254 |
chunk_tensor = chunk_tensor / (torch.max(torch.abs(chunk_tensor)) + 1e-6) * 0.95
|
|
@@ -256,7 +321,6 @@ def predict_voice_conversion(source_audio_path, ref_audio_path):
|
|
| 256 |
temp_c_chunk = f"wav/{task_id}_temp_c_{idx}.wav"
|
| 257 |
save_audio_pcm16(chunk_tensor, temp_c_chunk, SR)
|
| 258 |
|
| 259 |
-
# فراخوانی خط لوله مدل برای قطعه فعلی
|
| 260 |
gen = pipeline.inference_fm(
|
| 261 |
src_wav_path=temp_c_chunk,
|
| 262 |
timbre_ref_wav_path=temp_r,
|
|
@@ -269,17 +333,15 @@ def predict_voice_conversion(source_audio_path, ref_audio_path):
|
|
| 269 |
|
| 270 |
generated_chunks.append(gen_np)
|
| 271 |
|
| 272 |
-
# پاکسازی قطعه موقت فعلی
|
| 273 |
if os.path.exists(temp_c_chunk):
|
| 274 |
os.remove(temp_c_chunk)
|
| 275 |
|
| 276 |
-
# ادغام
|
| 277 |
final_gen = np.concatenate(generated_chunks, axis=0)
|
| 278 |
|
| 279 |
-
# ذخیره
|
| 280 |
save_audio_pcm16(final_gen, out_path, SR)
|
| 281 |
|
| 282 |
-
# پاکسازی فایل مرجع موقت
|
| 283 |
if os.path.exists(temp_r):
|
| 284 |
os.remove(temp_r)
|
| 285 |
|
|
@@ -288,7 +350,7 @@ def predict_voice_conversion(source_audio_path, ref_audio_path):
|
|
| 288 |
except Exception as e:
|
| 289 |
raise gr.Error(f"خطایی در حین پردازش رخ داد: {str(e)}")
|
| 290 |
|
| 291 |
-
# طراحی ساختار وب با استفاده از Gradio
|
| 292 |
with gr.Blocks() as demo:
|
| 293 |
gr.Markdown("# سامانه تبدیل صدا (Vevo Voice Conversion) - نسخه Zero-GPU PRO")
|
| 294 |
|
|
|
|
| 14 |
import re
|
| 15 |
import gradio as gr
|
| 16 |
|
| 17 |
+
# --- 1. نصب و تنظیمات اولیه (بدون تغییر) ---
|
| 18 |
|
| 19 |
def install_espeak():
|
| 20 |
try:
|
|
|
|
| 204 |
inference_pipelines["timbre"] = pipeline
|
| 205 |
return pipeline
|
| 206 |
|
| 207 |
+
|
| 208 |
+
# --- تابع جدید برش هوشمند با قابلیت افزایش داینامیک بازه جستجو ---
|
| 209 |
+
def smart_split_audio_vevo(y, sr, min_segment=4):
|
| 210 |
+
total_samples = len(y)
|
| 211 |
+
current_sample = 0
|
| 212 |
+
chunks = []
|
| 213 |
+
|
| 214 |
+
# اگر طول کل فایل کمتر از حد مجاز اولیه (8 ثانیه) باشد، نیازی به برش نیست
|
| 215 |
+
if total_samples <= 8 * sr:
|
| 216 |
+
return [y]
|
| 217 |
+
|
| 218 |
+
while current_sample < total_samples:
|
| 219 |
+
# اگر حجم نمونههای باقیمانده کمتر از حداقل زمان قطعه (4 ثانیه) باشد، بقیه فایل را یکجا بردار
|
| 220 |
+
if total_samples - current_sample <= min_segment * sr:
|
| 221 |
+
chunks.append(y[current_sample:])
|
| 222 |
+
break
|
| 223 |
+
|
| 224 |
+
# بازههای گامبهگام برای جستجوی سکوت: ابتدا ۸، سپس ۱۲، ۱۵ و نهایتاً ۲۰ ثانیه
|
| 225 |
+
steps = [8, 12, 15, 20]
|
| 226 |
+
silence_threshold = 0.015 # آستانه شدت انرژی سیگنال برای پذیرش به عنوان سکوت واقعی
|
| 227 |
+
|
| 228 |
+
best_split_point = None
|
| 229 |
+
best_rms_val = float('inf')
|
| 230 |
+
|
| 231 |
+
for max_sec in steps:
|
| 232 |
+
start_search = current_sample + int(min_segment * sr)
|
| 233 |
+
end_search = current_sample + int(max_sec * sr)
|
| 234 |
+
|
| 235 |
+
if start_search >= total_samples:
|
| 236 |
+
best_split_point = total_samples
|
| 237 |
+
break
|
| 238 |
+
|
| 239 |
+
if end_search > total_samples:
|
| 240 |
+
end_search = total_samples
|
| 241 |
+
|
| 242 |
+
search_region = y[start_search:end_search]
|
| 243 |
+
if len(search_region) == 0:
|
| 244 |
+
best_split_point = total_samples
|
| 245 |
+
break
|
| 246 |
+
|
| 247 |
+
# محاسبه میزان انرژی (RMS) قطعه
|
| 248 |
+
rms = librosa.feature.rms(y=search_region, frame_length=2048, hop_length=512)[0]
|
| 249 |
+
if len(rms) == 0:
|
| 250 |
+
continue
|
| 251 |
+
|
| 252 |
+
min_rms_idx = np.argmin(rms)
|
| 253 |
+
min_rms_val = rms[min_rms_idx]
|
| 254 |
+
candidate_point = start_search + (min_rms_idx * 512)
|
| 255 |
+
|
| 256 |
+
# ذخیره بهترین نقطه کمصدا (در صورتی که سکوت مطلق پیدا نشود، کمصداترین نقطه انتخاب میشود)
|
| 257 |
+
if min_rms_val < best_rms_val:
|
| 258 |
+
best_rms_val = min_rms_val
|
| 259 |
+
best_split_point = candidate_point
|
| 260 |
+
|
| 261 |
+
# اگر شدت صدای نقطه پیدا شده پایینتر از آستانه سکوت بود، فرآیند جستجو را متوقف کرده و برش بزن
|
| 262 |
+
if min_rms_val < silence_threshold:
|
| 263 |
+
break
|
| 264 |
+
|
| 265 |
+
if best_split_point is None:
|
| 266 |
+
best_split_point = min(current_sample + int(8 * sr), total_samples)
|
| 267 |
+
|
| 268 |
+
chunk_audio = y[current_sample:best_split_point]
|
| 269 |
+
chunks.append(chunk_audio)
|
| 270 |
+
current_sample = best_split_point
|
| 271 |
+
|
| 272 |
+
# بررسی قطعه آخر: اگر بیش از حد کوتاه باشد، آن را با بخش قبلی ادغام میکنیم
|
| 273 |
+
if len(chunks) > 1 and len(chunks[-1]) < int(1.5 * sr):
|
| 274 |
+
last_chunk = chunks.pop()
|
| 275 |
+
combined_len = len(chunks[-1]) + len(last_chunk)
|
| 276 |
+
# تا سقف حداکثر ۲۰ ثانیه ادغام قطعه آخر مجاز است
|
| 277 |
+
if combined_len <= int(20 * sr):
|
| 278 |
+
chunks[-1] = np.concatenate([chunks[-1], last_chunk])
|
| 279 |
+
else:
|
| 280 |
+
chunks.append(last_chunk)
|
| 281 |
+
|
| 282 |
+
return chunks
|
| 283 |
+
|
| 284 |
+
|
| 285 |
# =========================================================================
|
| 286 |
# سیستم استنتاج مستقیم بر روی Zero-GPU با رابط کاربری Gradio
|
| 287 |
# =========================================================================
|
|
|
|
| 296 |
SR = 24000
|
| 297 |
|
| 298 |
try:
|
| 299 |
+
# پردازش صوت اصلی
|
| 300 |
content_data, _ = librosa.load(source_audio_path, sr=SR)
|
| 301 |
|
| 302 |
+
# پردازش صوت مرجع
|
| 303 |
ref_data, _ = librosa.load(ref_audio_path, sr=SR)
|
| 304 |
ref_tensor = torch.FloatTensor(ref_data).unsqueeze(0)
|
| 305 |
ref_tensor = ref_tensor / (torch.max(torch.abs(ref_tensor)) + 1e-6) * 0.95
|
|
|
|
| 307 |
temp_r = f"wav/{task_id}_temp_r.wav"
|
| 308 |
save_audio_pcm16(ref_tensor, temp_r, SR)
|
| 309 |
|
| 310 |
+
# اعمال تقسیمکننده هوشمند چندمرحلهای
|
| 311 |
+
chunks = smart_split_audio_vevo(content_data, SR, min_segment=4)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
|
| 313 |
generated_chunks = []
|
| 314 |
pipeline = get_pipeline()
|
| 315 |
|
| 316 |
+
# شبیهسازی قطعه به قطعه
|
| 317 |
for idx, chunk in enumerate(chunks):
|
| 318 |
chunk_tensor = torch.FloatTensor(chunk).unsqueeze(0)
|
| 319 |
chunk_tensor = chunk_tensor / (torch.max(torch.abs(chunk_tensor)) + 1e-6) * 0.95
|
|
|
|
| 321 |
temp_c_chunk = f"wav/{task_id}_temp_c_{idx}.wav"
|
| 322 |
save_audio_pcm16(chunk_tensor, temp_c_chunk, SR)
|
| 323 |
|
|
|
|
| 324 |
gen = pipeline.inference_fm(
|
| 325 |
src_wav_path=temp_c_chunk,
|
| 326 |
timbre_ref_wav_path=temp_r,
|
|
|
|
| 333 |
|
| 334 |
generated_chunks.append(gen_np)
|
| 335 |
|
|
|
|
| 336 |
if os.path.exists(temp_c_chunk):
|
| 337 |
os.remove(temp_c_chunk)
|
| 338 |
|
| 339 |
+
# ادغام قطعات خروجی
|
| 340 |
final_gen = np.concatenate(generated_chunks, axis=0)
|
| 341 |
|
| 342 |
+
# ذخیره نهایی
|
| 343 |
save_audio_pcm16(final_gen, out_path, SR)
|
| 344 |
|
|
|
|
| 345 |
if os.path.exists(temp_r):
|
| 346 |
os.remove(temp_r)
|
| 347 |
|
|
|
|
| 350 |
except Exception as e:
|
| 351 |
raise gr.Error(f"خطایی در حین پردازش رخ داد: {str(e)}")
|
| 352 |
|
| 353 |
+
# طراحی ساختار وب با استفاده از Gradio
|
| 354 |
with gr.Blocks() as demo:
|
| 355 |
gr.Markdown("# سامانه تبدیل صدا (Vevo Voice Conversion) - نسخه Zero-GPU PRO")
|
| 356 |
|