Opera8 commited on
Commit
56bf13d
·
verified ·
1 Parent(s): faead0f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +79 -161
app.py CHANGED
@@ -6,21 +6,19 @@ import time
6
  import gradio as gr
7
  import spaces
8
  from huggingface_hub import snapshot_download
9
- from huggingface_hub.utils import GatedRepoError, RepositoryNotFoundError, RevisionNotFoundError
10
  from pathlib import Path
11
  import tempfile
12
  from pydub import AudioSegment
 
13
 
14
- # افزودن پوشه src به مسیر سیستم برای ایمپورت‌های داخلی
15
  sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'src')))
16
 
17
  from models.inference.moda_test import LiveVASAPipeline, emo_map, set_seed
18
 
19
  # --- تنظیمات ---
20
- # تنظیم Seed برای تکرارپذیری نتایج
21
  set_seed(42)
22
 
23
- # مسیرها و ثابت‌ها
24
  DEFAULT_CFG_PATH = "configs/audio2motion/inference/inference.yaml"
25
  DEFAULT_MOTION_MEAN_STD_PATH = "src/datasets/mean.pt"
26
  DEFAULT_SILENT_AUDIO_PATH = "src/examples/silent-audio.wav"
@@ -28,26 +26,19 @@ OUTPUT_DIR = "gradio_output"
28
  WEIGHTS_DIR = "pretrain_weights"
29
  REPO_ID = "lixinyizju/moda"
30
 
31
- # نگاشت احساسات به فارسی برای نمایش در منو
32
- # این دیکشنری نام فارسی را به نام انگلیسی موجود در مدل اصلی متصل می‌کند
33
  PERSIAN_EMOTION_MAP = {
34
  "خنثی (Neutral)": "Neutral",
35
  "خوشحال (Happy)": "Happy",
36
  "عصبانی (Angry)": "Angry",
37
  "غمگین (Sad)": "Sad",
38
  "متعجب (Surprise)": "Surprise"
39
- # اگر مدل احساسات دیگری دارد، به طور پیش‌فرض روی خنثی تنظیم می‌شود
40
  }
41
 
42
- # --- دانلود وزن‌های پیش‌آموزش دیده ---
43
  def download_weights():
44
- """
45
- دانلود مدل‌ها از هاگینگ‌فیس در صورتی که موجود نباشند.
46
- """
47
  motion_model_file = os.path.join(WEIGHTS_DIR, "moda", "net-200.pth")
48
-
49
  if not os.path.exists(motion_model_file):
50
- print("مدل‌ها یافت نشدند. در حال دانلود...")
51
  try:
52
  snapshot_download(
53
  repo_id=REPO_ID,
@@ -55,24 +46,14 @@ def download_weights():
55
  local_dir_use_symlinks=False,
56
  resume_download=True,
57
  )
58
- print("دانلود مدل‌ها با موفقیت انجام شد.")
59
  except Exception as e:
60
- print(f"خطا در دانلود: {e}")
61
- raise gr.Error(f"خطا در دریافت مدل‌ها. لطفاً اتصال اینترنت را بررسی کنید: {e}")
62
- else:
63
- print(f"مدل‌ها در مسیر '{WEIGHTS_DIR}' موجود هستند.")
64
 
65
- # --- تبدیل فرمت صدا ---
66
  def ensure_wav_format(audio_path):
67
- if audio_path is None:
68
- return None
69
-
70
  audio_path = Path(audio_path)
71
-
72
- if audio_path.suffix.lower() == '.wav':
73
- return str(audio_path)
74
-
75
- print(f"در حال تبدیل فایل صوتی به فرمت WAV...")
76
  try:
77
  audio = AudioSegment.from_file(audio_path)
78
  with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmp_file:
@@ -80,58 +61,42 @@ def ensure_wav_format(audio_path):
80
  audio.export(wav_path, format='wav', parameters=["-ar", "16000", "-ac", "1"])
81
  return wav_path
82
  except Exception as e:
83
- raise gr.Error(f"خطا در تبدیل فایل صوتی: {e}")
84
 
85
- # --- راه‌اندازی اولیه ---
86
  os.makedirs(OUTPUT_DIR, exist_ok=True)
87
  download_weights()
88
 
89
- print("در حال بارگذاری مدل MoDA...")
90
  try:
91
- pipeline = LiveVASAPipeline(
92
- cfg_path=DEFAULT_CFG_PATH,
93
- motion_mean_std_path=DEFAULT_MOTION_MEAN_STD_PATH
94
- )
95
- print("مدل با موفقیت بارگذاری شد.")
96
  except Exception as e:
97
- print(f"خطا در بارگذاری پایپ‌لاین: {e}")
98
  pipeline = None
99
 
100
- # ایجاد دیکشنری معکوس برای پیدا کردن ID احساسات
101
  emo_name_to_id = {v: k for k, v in emo_map.items()}
102
 
103
- # --- تابع اصلی تولید ویدیو ---
104
  @spaces.GPU(duration=120)
105
  def generate_motion(source_image_path, driving_audio_path, persian_emotion_name, cfg_scale, progress=gr.Progress(track_tqdm=True)):
106
- """
107
- تابع اصلی که ورودی‌ها را گرفته و ویدیو را تولید می‌کند.
108
- """
109
- if pipeline is None:
110
- raise gr.Error("مدل به درستی بارگذاری نشده است. لطفاً لاگ‌ها را بررسی کنید.")
111
-
112
- if source_image_path is None:
113
- raise gr.Error("لطفاً یک تصویر چهره آپلود کنید.")
114
- if driving_audio_path is None:
115
- raise gr.Error("لطفاً فایل صوتی را آپلود کنید.")
116
-
117
- # نگاشت نام فارسی انتخاب شده به نام انگلیسی
118
- english_emo_name = PERSIAN_EMOTION_MAP.get(persian_emotion_name, "Neutral")
119
-
120
- # پیدا کردن ID احساسات
121
- emotion_id = emo_name_to_id.get(english_emo_name, 8) # پیش‌فرض 8
122
-
123
- start_time = time.time()
124
-
125
- wav_audio_path = ensure_wav_format(driving_audio_path)
126
- temp_wav_created = wav_audio_path != driving_audio_path
127
-
128
- timestamp = time.strftime("%Y%m%d-%H%M%S")
129
- run_output_dir = os.path.join(OUTPUT_DIR, timestamp)
130
- os.makedirs(run_output_dir, exist_ok=True)
131
-
132
- print(f"شروع پردازش برای احساس: {persian_emotion_name} (ID: {emotion_id})")
133
-
134
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  result_video_path = pipeline.driven_sample(
136
  image_path=source_image_path,
137
  audio_path=wav_audio_path,
@@ -141,122 +106,75 @@ def generate_motion(source_image_path, driving_audio_path, persian_emotion_name,
141
  smooth=False,
142
  silent_audio_path=DEFAULT_SILENT_AUDIO_PATH,
143
  )
144
- except gr.Error as ge:
145
- # بازنشر خطاهای گرادیو
146
- raise ge
147
- except Exception as e:
148
- # مدیریت خطاهای خاص ZeroGPU و دیگر خطاها
149
- error_msg = str(e).lower()
150
- if "quota" in error_msg or "gpu" in error_msg or "queue" in error_msg:
151
- raise gr.Error("❌ سهمیه GPU شما (Quota) به پایان رسیده یا سرور مشغول است. لطفاً مدتی صبر کنید و دوباره تلاش کنید.")
152
- else:
153
- import traceback
154
- traceback.print_exc()
155
- raise gr.Error(f"خطای غیرمنتظره در پردازش: {str(e)}")
156
- finally:
157
- if temp_wav_created and os.path.exists(wav_audio_path):
158
- try:
159
- os.remove(wav_audio_path)
160
- except:
161
- pass
162
 
163
- final_path = Path(result_video_path)
164
- # تغییر نام برای اطمینان از فرمت درست در خروجی
165
- renamed_path = final_path.with_name(f"final_{final_path.stem}{final_path.suffix}")
166
- if final_path.exists():
167
  final_path.rename(renamed_path)
168
-
169
- processing_time = time.time() - start_time
170
- print(f"ویدیو در {processing_time:.2f} ثانیه آماده شد.")
171
 
172
- return str(renamed_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
- # --- رابط کاربری (CSS راست‌چین) ---
175
  css = """
176
- .gradio-container {
177
- max-width: 960px !important;
178
- margin: 0 auto !important;
179
- font-family: 'Tahoma', 'Segoe UI', sans-serif;
180
- }
181
- h1, p, .prose {
182
- direction: rtl;
183
- text-align: center;
184
- }
185
- /* راست‌چین کردن لیبل‌ها و ورودی‌ها */
186
- label, span {
187
- direction: rtl;
188
- text-align: right;
189
- width: 100%;
190
- }
191
- .svelte-1b6s6s { direction: rtl; }
192
  """
193
 
194
- with gr.Blocks(theme=gr.themes.Soft(), css=css, title="MoDA فارسی") as demo:
195
 
196
  gr.HTML(
197
  """
198
- <div align='center'>
199
- <h1>هوش مصنوعی MoDA: ساخت ویدیو از روی عکس و صدا</h1>
200
- <p style="font-size: 1.1em; color: #666;">
201
- تصویر چهره و فایل صوتی خود را آپلود کنید تا ویدیوی سخنگو ساخته شود.
202
- </p>
203
  </div>
204
  """
205
  )
206
 
207
- with gr.Row(variant="panel"):
208
- with gr.Column(scale=1):
209
- # ورودی تصویر
210
- source_image = gr.Image(
211
- label="تصویر چهره (ورودی)",
212
- type="filepath",
213
- value="src/examples/reference_images/7.jpg",
214
- height=300
215
- )
216
 
217
- # ورودی صدا
218
- driving_audio = gr.Audio(
219
- label="فایل صوتی (صدا)",
220
- type="filepath",
221
- value="src/examples/driving_audios/5.wav"
222
- )
223
-
224
- # تنظیمات پیشرفته
225
  with gr.Row():
226
  emotion_dropdown = gr.Dropdown(
227
- label="حالت چهره (احساسات)",
228
- choices=list(PERSIAN_EMOTION_MAP.keys()),
229
- value="خنثی (Neutral)",
230
- interactive=True
231
- )
232
-
233
- with gr.Row():
234
- cfg_slider = gr.Slider(
235
- label="شدت اعمال تنظیمات (CFG Scale)",
236
- info="اعداد بالاتر دقت را بیشتر می‌کنند اما ممکن است تصویر را خشک کنند. عدد پیشنهادی: ۱.۲",
237
- minimum=1.0,
238
- maximum=3.0,
239
- step=0.05,
240
- value=1.2
241
  )
 
 
 
 
 
 
 
 
 
 
242
 
243
- submit_button = gr.Button("🎥 تولید ویدیو", variant="primary", size="lg")
244
 
245
- with gr.Column(scale=1):
246
- # خروجی
247
- output_video = gr.Video(label="ویدیوی نهایی", height=500)
248
 
249
- # فوتر و سلب مسئولیت
250
- gr.Markdown(
251
- """
252
- ---
253
- ### **توجه:**
254
- این پروژه صرفاً جهت استفاده تحقیقاتی و آموزشی است. لطفاً از ساخت محتوای نامناسب یا جعل هویت افراد بدون اجازه خودداری کنید.
255
- در صورتی که با خطای **"Quota exceeded"** مواجه شدید، به این معناست که سهمیه رایگان GPU شما موقتاً تمام شده است.
256
- """
257
- )
258
-
259
- submit_button.click(
260
  fn=generate_motion,
261
  inputs=[source_image, driving_audio, emotion_dropdown, cfg_slider],
262
  outputs=output_video
 
6
  import gradio as gr
7
  import spaces
8
  from huggingface_hub import snapshot_download
 
9
  from pathlib import Path
10
  import tempfile
11
  from pydub import AudioSegment
12
+ import traceback
13
 
14
+ # افزودن پوشه src به مسیر سیستم
15
  sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'src')))
16
 
17
  from models.inference.moda_test import LiveVASAPipeline, emo_map, set_seed
18
 
19
  # --- تنظیمات ---
 
20
  set_seed(42)
21
 
 
22
  DEFAULT_CFG_PATH = "configs/audio2motion/inference/inference.yaml"
23
  DEFAULT_MOTION_MEAN_STD_PATH = "src/datasets/mean.pt"
24
  DEFAULT_SILENT_AUDIO_PATH = "src/examples/silent-audio.wav"
 
26
  WEIGHTS_DIR = "pretrain_weights"
27
  REPO_ID = "lixinyizju/moda"
28
 
 
 
29
  PERSIAN_EMOTION_MAP = {
30
  "خنثی (Neutral)": "Neutral",
31
  "خوشحال (Happy)": "Happy",
32
  "عصبانی (Angry)": "Angry",
33
  "غمگین (Sad)": "Sad",
34
  "متعجب (Surprise)": "Surprise"
 
35
  }
36
 
37
+ # --- دانلود وزن‌ها ---
38
  def download_weights():
 
 
 
39
  motion_model_file = os.path.join(WEIGHTS_DIR, "moda", "net-200.pth")
 
40
  if not os.path.exists(motion_model_file):
41
+ print("در حال دانلود مدل‌ها...")
42
  try:
43
  snapshot_download(
44
  repo_id=REPO_ID,
 
46
  local_dir_use_symlinks=False,
47
  resume_download=True,
48
  )
 
49
  except Exception as e:
50
+ raise gr.Error(f"خطا در دانلود مدل‌ها. اینترنت سرور قطع است: {e}")
 
 
 
51
 
52
+ # --- تبدیل صدا ---
53
  def ensure_wav_format(audio_path):
54
+ if audio_path is None: return None
 
 
55
  audio_path = Path(audio_path)
56
+ if audio_path.suffix.lower() == '.wav': return str(audio_path)
 
 
 
 
57
  try:
58
  audio = AudioSegment.from_file(audio_path)
59
  with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmp_file:
 
61
  audio.export(wav_path, format='wav', parameters=["-ar", "16000", "-ac", "1"])
62
  return wav_path
63
  except Exception as e:
64
+ raise gr.Error(f"فرمت فایل صوتی پشتیبانی نمی‌شود: {e}")
65
 
66
+ # --- لود اولیه ---
67
  os.makedirs(OUTPUT_DIR, exist_ok=True)
68
  download_weights()
69
 
70
+ print("در حال لود مدل...")
71
  try:
72
+ pipeline = LiveVASAPipeline(cfg_path=DEFAULT_CFG_PATH, motion_mean_std_path=DEFAULT_MOTION_MEAN_STD_PATH)
 
 
 
 
73
  except Exception as e:
74
+ print(f"Error Init: {e}")
75
  pipeline = None
76
 
 
77
  emo_name_to_id = {v: k for k, v in emo_map.items()}
78
 
79
+ # --- تابع اصلی با مدیریت خطای ZeroGPU ---
80
  @spaces.GPU(duration=120)
81
  def generate_motion(source_image_path, driving_audio_path, persian_emotion_name, cfg_scale, progress=gr.Progress(track_tqdm=True)):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  try:
83
+ # 1. بررسی‌های اولیه
84
+ if pipeline is None:
85
+ raise ValueError("مدل روی سرور لود نشده است. لطفا اسپیس را Restart کنید.")
86
+ if source_image_path is None:
87
+ raise ValueError("لطفاً یک تصویر چهره انتخاب کنید.")
88
+ if driving_audio_path is None:
89
+ raise ValueError("لطفاً یک فایل صوتی انتخاب کنید.")
90
+
91
+ # 2. آماده‌سازی
92
+ english_emo_name = PERSIAN_EMOTION_MAP.get(persian_emotion_name, "Neutral")
93
+ emotion_id = emo_name_to_id.get(english_emo_name, 8)
94
+
95
+ wav_audio_path = ensure_wav_format(driving_audio_path)
96
+
97
+ # 3. اجرا
98
+ print(f"Processing: {persian_emotion_name}, CFG: {cfg_scale}")
99
+
100
  result_video_path = pipeline.driven_sample(
101
  image_path=source_image_path,
102
  audio_path=wav_audio_path,
 
106
  smooth=False,
107
  silent_audio_path=DEFAULT_SILENT_AUDIO_PATH,
108
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
+ # 4. خروجی
111
+ final_path = Path(result_video_path)
112
+ renamed_path = final_path.with_name(f"final_{int(time.time())}.mp4")
 
113
  final_path.rename(renamed_path)
114
+ return str(renamed_path)
 
 
115
 
116
+ except gr.Error:
117
+ # اگر ارور گرادیو بود، همان را پاس بده
118
+ raise
119
+ except Exception as e:
120
+ # لاگ کردن خطای واقعی در کنسول برای دیباگ
121
+ error_msg = str(e)
122
+ traceback.print_exc()
123
+
124
+ # تشخیص خطاهای رایج ZeroGPU
125
+ if "GPU" in error_msg or "device" in error_msg or "cuda" in error_msg.lower():
126
+ raise gr.Error("⚠️ خطا در دسترسی به GPU: سهمیه شما تمام شده یا سرور شلوغ است.")
127
+ elif "duration" in error_msg or "time" in error_msg:
128
+ raise gr.Error("⚠️ زمان پردازش طولانی شد و سرور آن را قطع کرد.")
129
+ else:
130
+ # نمایش خطای کلی اما فارسی
131
+ raise gr.Error(f"❌ خطا در پردازش: {error_msg}. (احتمالاً سهمیه GPU تمام شده است)")
132
 
133
+ # --- رابط کاربری ---
134
  css = """
135
+ .gradio-container {max-width: 900px !important; margin: auto !important; font-family: 'Tahoma', sans-serif;}
136
+ h1, h2, h3, p, span, div {direction: rtl; text-align: right;}
137
+ .center-text {text-align: center !important;}
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  """
139
 
140
+ with gr.Blocks(theme=gr.themes.Soft(), css=css, title="MoDA Farsi") as demo:
141
 
142
  gr.HTML(
143
  """
144
+ <div class="center-text">
145
+ <h1>MoDA: ساخت ویدیو سخنگو</h1>
 
 
 
146
  </div>
147
  """
148
  )
149
 
150
+ with gr.Row():
151
+ with gr.Column():
152
+ source_image = gr.Image(label="۱. تصویر چهره", type="filepath", height=250)
153
+ driving_audio = gr.Audio(label="۲. فایل صوتی", type="filepath")
 
 
 
 
 
154
 
 
 
 
 
 
 
 
 
155
  with gr.Row():
156
  emotion_dropdown = gr.Dropdown(
157
+ label="۳. حالت چهره",
158
+ choices=list(PERSIAN_EMOTION_MAP.keys()),
159
+ value="خنثی (Neutral)"
 
 
 
 
 
 
 
 
 
 
 
160
  )
161
+ cfg_slider = gr.Slider(label="۴. دقت (CFG)", minimum=1.0, maximum=3.0, value=1.2, step=0.1)
162
+
163
+ # پیام هشدار واضح برای کاربر درباره Quota
164
+ gr.Markdown(
165
+ """
166
+ <div style="background-color: #fff3cd; color: #856404; padding: 10px; border-radius: 5px; border: 1px solid #ffeeba; margin-top: 10px; font-size: 0.9em;">
167
+ ⚠️ <b>توجه مهم:</b> اگر بعد از زدن دکمه با پیغام <b>Error</b> مواجه شدید، به این معنی است که <b>سهمیه رایگان (ZeroGPU Quota)</b> شما تمام شده است. لطفاً بعداً تلاش کنید.
168
+ </div>
169
+ """
170
+ )
171
 
172
+ submit_btn = gr.Button("🎥 ساخت ویدیو", variant="primary", size="lg")
173
 
174
+ with gr.Column():
175
+ output_video = gr.Video(label="خروجی نهایی", height=400)
 
176
 
177
+ submit_btn.click(
 
 
 
 
 
 
 
 
 
 
178
  fn=generate_motion,
179
  inputs=[source_image, driving_audio, emotion_dropdown, cfg_slider],
180
  outputs=output_video