Opera8 commited on
Commit
c0012d4
·
verified ·
1 Parent(s): 1978007

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +291 -0
app.py ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import importlib.util
4
+ import torch
5
+ import torchaudio
6
+ import numpy as np
7
+ from huggingface_hub import snapshot_download, hf_hub_download
8
+ import subprocess
9
+ import uuid
10
+ import soundfile as sf
11
+ import spaces
12
+ import librosa
13
+ import shutil
14
+ import re
15
+ import gradio as gr
16
+
17
+ # --- 1. نصب و تنظیمات اولیه (بدون تغییر) ---
18
+
19
+ def install_espeak():
20
+ try:
21
+ result = subprocess.run(["which", "espeak-ng"], capture_output=True, text=True)
22
+ if result.returncode != 0:
23
+ print("Installing espeak-ng...")
24
+ subprocess.run(["apt-get", "update"], check=True)
25
+ subprocess.run(["apt-get", "install", "-y", "espeak-ng", "espeak-ng-data"], check=True)
26
+ except Exception as e:
27
+ print(f"Error installing espeak-ng: {e}")
28
+
29
+ install_espeak()
30
+
31
+ def patch_langsegment_init():
32
+ try:
33
+ spec = importlib.util.find_spec("LangSegment")
34
+ if spec is None or spec.origin is None: return
35
+ init_path = os.path.join(os.path.dirname(spec.origin), '__init__.py')
36
+ with open(init_path, 'r') as f: lines = f.readlines()
37
+ modified = False
38
+ new_lines =[]
39
+ target_line_prefix = "from .LangSegment import"
40
+ for line in lines:
41
+ if line.strip().startswith(target_line_prefix) and ('setLangfilters' in line or 'getLangfilters' in line):
42
+ mod_line = line.replace(',setLangfilters', '').replace(',getLangfilters', '')
43
+ mod_line = mod_line.replace('setLangfilters,', '').replace('getLangfilters,', '').rstrip(',')
44
+ new_lines.append(mod_line + '\n')
45
+ modified = True
46
+ else:
47
+ new_lines.append(line)
48
+ if modified:
49
+ with open(init_path, 'w') as f: f.writelines(new_lines)
50
+ try:
51
+ import LangSegment
52
+ importlib.reload(LangSegment)
53
+ except: pass
54
+ except: pass
55
+
56
+ patch_langsegment_init()
57
+
58
+ if not os.path.exists("Amphion"):
59
+ print("Cloning Amphion repository...")
60
+ subprocess.run(["git", "clone", "https://github.com/open-mmlab/Amphion.git"])
61
+
62
+ amphion_path = os.path.abspath("Amphion")
63
+ if amphion_path not in sys.path:
64
+ sys.path.append(amphion_path)
65
+
66
+ # --- پچ کردن تمام کدهای Amphion برای سازگاری با نسخه جدید transformers ---
67
+ def patch_amphion_llama_config():
68
+ try:
69
+ # جستجو در تمام فایل‌های پایتون پوشه Amphion
70
+ for root, dirs, files in os.walk(amphion_path):
71
+ for file in files:
72
+ if file.endswith(".py"):
73
+ file_path = os.path.join(root, file)
74
+ try:
75
+ with open(file_path, "r", encoding="utf-8") as f:
76
+ content = f.read()
77
+
78
+ # جایگزینی عبارات مشکل‌دار
79
+ new_content = re.sub(
80
+ r'LlamaConfig\(\s*0\s*,\s*256\s*,\s*1024\s*,\s*1\s*,\s*1\s*\)',
81
+ r'LlamaConfig(vocab_size=0, hidden_size=256, intermediate_size=1024, num_hidden_layers=1, num_attention_heads=1)',
82
+ content
83
+ )
84
+
85
+ if new_content != content:
86
+ with open(file_path, "w", encoding="utf-8") as f:
87
+ f.write(new_content)
88
+ print(f"Patched {file} successfully.")
89
+ except Exception:
90
+ pass
91
+ except Exception as e:
92
+ print(f"Failed to patch Amphion: {e}")
93
+
94
+ # اجرای پچ به صورت سراسری
95
+ patch_amphion_llama_config()
96
+
97
+ # --- رفع مشکل rope_theta با تزریق (Monkey Patch) در LlamaConfig ---
98
+ import transformers
99
+ from transformers.models.llama.configuration_llama import LlamaConfig
100
+
101
+ _original_llama_init = LlamaConfig.__init__
102
+ def _patched_llama_init(self, *args, **kwargs):
103
+ # تزریق مقادیر پیش‌فرض مورد نیاز نسخه‌های جدید Transformers
104
+ kwargs.setdefault('rope_theta', 10000.0)
105
+ kwargs.setdefault('attention_bias', False)
106
+ kwargs.setdefault('rope_scaling', None)
107
+ kwargs.setdefault('max_position_embeddings', 4096)
108
+
109
+ _original_llama_init(self, *args, **kwargs)
110
+
111
+ # اطمینان صد درصدی از وجود ویژگی‌ها روی آبجکت
112
+ if not hasattr(self, 'rope_theta'):
113
+ self.rope_theta = 10000.0
114
+ if not hasattr(self, 'attention_bias'):
115
+ self.attention_bias = False
116
+
117
+ # اعمال پچ
118
+ LlamaConfig.__init__ = _patched_llama_init
119
+
120
+ os.makedirs("wav", exist_ok=True)
121
+ os.makedirs("ckpts/Vevo", exist_ok=True)
122
+
123
+ try:
124
+ from models.vc.vevo.vevo_utils import VevoInferencePipeline
125
+ except ImportError:
126
+ sys.path.append(os.path.join(amphion_path))
127
+ from models.vc.vevo.vevo_utils import VevoInferencePipeline
128
+
129
+ # --- توابع ذخیره و تنظیمات م��ل ---
130
+
131
+ def save_audio_pcm16(waveform, output_path, sample_rate=24000):
132
+ try:
133
+ if isinstance(waveform, torch.Tensor):
134
+ waveform = waveform.detach().cpu()
135
+ if waveform.dim() == 2 and waveform.shape[0] == 1:
136
+ waveform = waveform.squeeze(0)
137
+ waveform = waveform.numpy()
138
+ sf.write(output_path, waveform, sample_rate, subtype='PCM_16')
139
+ except Exception as e:
140
+ print(f"Save error: {e}")
141
+
142
+ downloaded_resources = {
143
+ "configs": False,
144
+ "tokenizer_vq8192": False,
145
+ "fmt_Vq8192ToMels": False,
146
+ "vocoder": False
147
+ }
148
+
149
+ def setup_configs():
150
+ if downloaded_resources["configs"]: return
151
+ target_config_path = "models/vc/vevo/config"
152
+ os.makedirs(target_config_path, exist_ok=True)
153
+
154
+ config_files =["Vq8192ToMels.json", "Vocoder.json", "hubert_large_l18_mean_std.npz"]
155
+ source_config_path = os.path.join(amphion_path, "models/vc/vevo/config")
156
+
157
+ for file in config_files:
158
+ target_file_path = f"{target_config_path}/{file}"
159
+ source_file_path = os.path.join(source_config_path, file)
160
+
161
+ if not os.path.exists(target_file_path):
162
+ if os.path.exists(source_file_path):
163
+ shutil.copy(source_file_path, target_file_path)
164
+ else:
165
+ try:
166
+ file_data = hf_hub_download(repo_id="amphion/Vevo", filename=f"config/{file}", repo_type="model")
167
+ if os.path.exists(target_file_path): os.remove(target_file_path)
168
+ subprocess.run(["cp", file_data, target_file_path])
169
+ except Exception as e:
170
+ print(f"Error downloading config {file}: {e}")
171
+
172
+ downloaded_resources["configs"] = True
173
+
174
+ setup_configs()
175
+ device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
176
+ inference_pipelines = {}
177
+
178
+ downloaded_content_style_tokenizer_path = None
179
+ downloaded_fmt_path = None
180
+ downloaded_vocoder_path = None
181
+
182
+ def preload_all_resources():
183
+ setup_configs()
184
+ global downloaded_content_style_tokenizer_path, downloaded_fmt_path, downloaded_vocoder_path
185
+ if not downloaded_resources["tokenizer_vq8192"]:
186
+ downloaded_content_style_tokenizer_path = snapshot_download(repo_id="amphion/Vevo", repo_type="model", cache_dir="./ckpts/Vevo", allow_patterns=["tokenizer/vq8192/"])
187
+ downloaded_resources["tokenizer_vq8192"] = True
188
+ if not downloaded_resources["fmt_Vq8192ToMels"]:
189
+ downloaded_fmt_path = snapshot_download(repo_id="amphion/Vevo", repo_type="model", cache_dir="./ckpts/Vevo", allow_patterns=["acoustic_modeling/Vq8192ToMels/"])
190
+ downloaded_resources["fmt_Vq8192ToMels"] = True
191
+ if not downloaded_resources["vocoder"]:
192
+ downloaded_vocoder_path = snapshot_download(repo_id="amphion/Vevo", repo_type="model", cache_dir="./ckpts/Vevo", allow_patterns=["acoustic_modeling/Vocoder/*"])
193
+ downloaded_resources["vocoder"] = True
194
+
195
+ preload_all_resources()
196
+
197
+ def get_pipeline():
198
+ if "timbre" in inference_pipelines: return inference_pipelines["timbre"]
199
+ tokenizer_path = os.path.join(downloaded_content_style_tokenizer_path, "tokenizer/vq8192")
200
+ fmt_ckpt = os.path.join(downloaded_fmt_path, "acoustic_modeling/Vq8192ToMels")
201
+ vocoder_ckpt = os.path.join(downloaded_vocoder_path, "acoustic_modeling/Vocoder")
202
+ pipeline = VevoInferencePipeline(
203
+ content_style_tokenizer_ckpt_path=tokenizer_path,
204
+ fmt_cfg_path="./models/vc/vevo/config/Vq8192ToMels.json",
205
+ fmt_ckpt_path=fmt_ckpt,
206
+ vocoder_cfg_path="./models/vc/vevo/config/Vocoder.json",
207
+ vocoder_ckpt_path=vocoder_ckpt,
208
+ device=device,
209
+ )
210
+ inference_pipelines["timbre"] = pipeline
211
+ return pipeline
212
+
213
+ # =========================================================================
214
+ # سیستم استنتاج مستقیم بر روی Zero-GPU با رابط کاربری Gradio
215
+ # =========================================================================
216
+
217
+ @spaces.GPU(duration=120)
218
+ def predict_voice_conversion(source_audio_path, ref_audio_path):
219
+ """
220
+ این تابع به صورت بومی روی Zero-GPU اجرا شده و مستقیماً توسط سیستم صف گرادیو مدیریت می‌شود.
221
+ """
222
+ if source_audio_path is None or ref_audio_path is None:
223
+ raise gr.Error("لطفاً هر دو فایل صوتی (اصلی و مرجع) را وارد کنید.")
224
+
225
+ task_id = str(uuid.uuid4())
226
+ out_path = f"wav/{task_id}_out.wav"
227
+ SR = 24000
228
+
229
+ try:
230
+ # پردازش صوت اصلی
231
+ content_data, _ = librosa.load(source_audio_path, sr=SR)
232
+ content_tensor = torch.FloatTensor(content_data).unsqueeze(0)
233
+ content_tensor = content_tensor / (torch.max(torch.abs(content_tensor)) + 1e-6) * 0.95
234
+ temp_c = f"wav/{task_id}_temp_c.wav"
235
+ save_audio_pcm16(content_tensor, temp_c, SR)
236
+
237
+ # پردازش صوت مرجع
238
+ ref_data, _ = librosa.load(ref_audio_path, sr=SR)
239
+ ref_tensor = torch.FloatTensor(ref_data).unsqueeze(0)
240
+ ref_tensor = ref_tensor / (torch.max(torch.abs(ref_tensor)) + 1e-6) * 0.95
241
+
242
+ # حذف محدودیت ۲۰ ثانیه به دلیل ارتقا به حساب پرو و عدم نیاز به تیکه‌تیکه کردن
243
+ temp_r = f"wav/{task_id}_temp_r.wav"
244
+ save_audio_pcm16(ref_tensor, temp_r, SR)
245
+
246
+ # فراخوانی مدل روی بافت خط لوله GPU
247
+ pipeline = get_pipeline()
248
+ gen = pipeline.inference_fm(
249
+ src_wav_path=temp_c,
250
+ timbre_ref_wav_path=temp_r,
251
+ flow_matching_steps=32,
252
+ )
253
+
254
+ if torch.isnan(gen).any():
255
+ gen = torch.nan_to_num(gen, nan=0.0)
256
+ gen_np = gen.detach().cpu().squeeze().numpy()
257
+
258
+ # ذخیره نهایی
259
+ save_audio_pcm16(gen_np, out_path, SR)
260
+
261
+ # پاکسازی فایل‌های موقت تولید شده
262
+ if os.path.exists(temp_c): os.remove(temp_c)
263
+ if os.path.exists(temp_r): os.remove(temp_r)
264
+
265
+ return out_path
266
+
267
+ except Exception as e:
268
+ raise gr.Error(f"خطایی در حین پردازش رخ داد: {str(e)}")
269
+
270
+ # طراحی ساختار وب با استفاده از Gradio
271
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
272
+ gr.Markdown("# سامانه تبدیل صدا (Vevo Voice Conversion) - نسخه Zero-GPU PRO")
273
+
274
+ with gr.Row():
275
+ with gr.Column():
276
+ source_input = gr.Audio(source="upload", type="filepath", label="فایل صوتی اصلی (Source Audio)")
277
+ ref_input = gr.Audio(source="upload", type="filepath", label="فایل صوتی مرجع/هدف (Reference Timbre)")
278
+ submit_btn = gr.Button("شروع فرآیند تبدیل صدا", variant="primary")
279
+
280
+ with gr.Column():
281
+ audio_output = gr.Audio(label="خروجی صدای شبیه‌سازی شده", type="filepath")
282
+
283
+ submit_btn.click(
284
+ fn=predict_voice_conversion,
285
+ inputs=[source_input, ref_input],
286
+ outputs=audio_output
287
+ )
288
+
289
+ if __name__ == "__main__":
290
+ # فعال‌سازی سیستم صف خودکار روی سرور گرادیو برای مدیریت بهینه کاربران
291
+ demo.queue(max_size=20).launch(server_name="0.0.0.0", server_port=7860)