Opera8 commited on
Commit
1935d38
·
verified ·
1 Parent(s): 1ebd128

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +111 -42
app.py CHANGED
@@ -4,11 +4,12 @@ import numpy as np
4
  import spaces
5
  import torch
6
  import random
7
- from PIL import Image
8
  from typing import Iterable
9
  from gradio.themes import Soft
10
  from gradio.themes.utils import colors, fonts, sizes
11
  from deep_translator import GoogleTranslator
 
12
 
13
  # --- تعریف تم ---
14
  colors.steel_blue = colors.Color(
@@ -28,7 +29,22 @@ colors.steel_blue = colors.Color(
28
 
29
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
30
 
31
- # --- بارگذاری مدل ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  from diffusers import FlowMatchEulerDiscreteScheduler
33
  from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
34
  from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
@@ -78,7 +94,6 @@ pipe.load_lora_weights("vafipas663/Qwen-Edit-2509-Upscale-LoRA",
78
  pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
79
  MAX_SEED = np.iinfo(np.int32).max
80
 
81
- # --- نگاشت نام‌های فارسی به نام‌های داخلی مدل ---
82
  LORA_MAPPING = {
83
  "تبدیل عکس به انیمه": "anime",
84
  "تغییر زاویه دید": "multiple-angles",
@@ -90,16 +105,38 @@ LORA_MAPPING = {
90
  "افزایش کیفیت (Upscale)": "upscale-image"
91
  }
92
 
93
- # --- گزینه‌های نسبت ابعاد ---
94
- ASPECT_RATIOS = {
 
 
 
 
 
 
 
 
95
  "خودکار (پیش‌فرض)": "Auto",
96
- "۱:۱ (مربع)": (1024, 1024),
97
- "۱۶:۹ (افقی - لنداسکیپ)": (1344, 768),
98
- "۹:۱۶ (عمودی - استوری)": (768, 1344)
 
99
  }
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  def translate_prompt(text):
102
- """ترجمه متن فارسی به انگلیسی"""
103
  if not text:
104
  return ""
105
  try:
@@ -129,6 +166,13 @@ def update_dimensions_on_upload(image):
129
 
130
  return new_width, new_height
131
 
 
 
 
 
 
 
 
132
  @spaces.GPU(duration=30)
133
  def infer(
134
  input_image,
@@ -138,15 +182,24 @@ def infer(
138
  randomize_seed,
139
  guidance_scale,
140
  steps,
141
- aspect_ratio_selection, # پارامتر جدید
 
 
142
  progress=gr.Progress(track_tqdm=True)
143
  ):
144
  if input_image is None:
145
  raise gr.Error("لطفاً یک تصویر برای ویرایش بارگذاری کنید.")
146
 
 
 
 
 
 
147
  english_prompt = translate_prompt(prompt)
 
 
 
148
  adapter_internal_name = LORA_MAPPING.get(lora_adapter_persian)
149
-
150
  if adapter_internal_name:
151
  pipe.set_adapters([adapter_internal_name], adapter_weights=[1.0])
152
 
@@ -154,26 +207,30 @@ def infer(
154
  seed = random.randint(0, MAX_SEED)
155
 
156
  generator = torch.Generator(device=device).manual_seed(seed)
157
- negative_prompt = "worst quality, low quality, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, jpeg artifacts, signature, watermark, username, blurry"
 
 
 
158
 
159
  original_image = input_image.convert("RGB")
160
 
161
- # --- منطق تعیین ابعاد ---
162
- if aspect_ratio_selection == "خودکار (پیش‌فرض)" or aspect_ratio_selection is None:
 
 
 
 
 
 
163
  width, height = update_dimensions_on_upload(original_image)
164
  else:
165
- # دریافت ابعاد از دیکشنری
166
- dims = ASPECT_RATIOS.get(aspect_ratio_selection)
167
- if dims and isinstance(dims, tuple):
168
- width, height = dims
169
- else:
170
- # fallback to auto
171
- width, height = update_dimensions_on_upload(original_image)
172
 
173
  result = pipe(
174
  image=original_image,
175
  prompt=english_prompt,
176
- negative_prompt=negative_prompt,
177
  height=height,
178
  width=width,
179
  num_inference_steps=steps,
@@ -181,6 +238,9 @@ def infer(
181
  true_cfg_scale=guidance_scale,
182
  ).images[0]
183
 
 
 
 
184
  return result, seed
185
 
186
  @spaces.GPU(duration=30)
@@ -188,8 +248,8 @@ def infer_example(input_image, prompt, lora_adapter):
188
  input_pil = input_image.convert("RGB")
189
  guidance_scale = 1.0
190
  steps = 4
191
- # برای نمونه‌ها حالت خودکار را پیش‌فرض قرار می‌دهیم
192
- result, seed = infer(input_pil, prompt, lora_adapter, 0, True, guidance_scale, steps, "خودکار (پیش‌فرض)")
193
  return result, seed
194
 
195
 
@@ -214,12 +274,12 @@ async (image) => {
214
  }
215
  """
216
 
217
- # --- تنظیمات HTML (CSS زیبا و مدرن + JS اجبار تم روشن) ---
218
  html_code = """
219
  <style>
220
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap');
221
 
222
- /* --- 1. تنظیمات کلی و اجبار تم روشن --- */
223
  :root, .dark, body, .gradio-container {
224
  --body-background-fill: #f5f7fa !important;
225
  --body-text-color: #1f2937 !important;
@@ -240,7 +300,6 @@ body {
240
  padding: 10px;
241
  }
242
 
243
- /* --- 2. کانتینر اصلی --- */
244
  #col-container {
245
  margin: 0 auto;
246
  max-width: 980px;
@@ -253,7 +312,6 @@ body {
253
  border: 1px solid rgba(255,255,255,0.8);
254
  }
255
 
256
- /* --- 3. هدر و توضیحات --- */
257
  #main-title h1 {
258
  font-size: 2.4em !important;
259
  text-align: center;
@@ -273,7 +331,6 @@ body {
273
  line-height: 1.6;
274
  }
275
 
276
- /* --- 4. استایل ورودی‌ها و لیبل‌ها --- */
277
  .gr-input-label, span.label-wrap, label span {
278
  font-weight: 700 !important;
279
  color: #374151 !important;
@@ -302,7 +359,6 @@ textarea:focus, input[type="text"]:focus {
302
  border-radius: 12px !important;
303
  }
304
 
305
- /* --- 5. دکمه‌ها --- */
306
  .primary-btn, button.primary {
307
  background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
308
  border: none !important;
@@ -335,7 +391,7 @@ textarea:focus, input[type="text"]:focus {
335
  box-shadow: 0 8px 25px rgba(59, 130, 246, 0.45) !important;
336
  }
337
 
338
- /* --- 6. بخش نمونه‌ها --- */
339
  .gradio-container .prose table,
340
  .gradio-container table {
341
  background-color: #ffffff !important;
@@ -372,12 +428,10 @@ textarea:focus, input[type="text"]:focus {
372
  }
373
 
374
  .gradio-container tbody td span,
375
- .gradio-container tbody td p,
376
- .gradio-container tbody td div {
377
  color: #374151 !important;
378
  }
379
 
380
- /* --- 7. مخفی‌سازی‌ها --- */
381
  footer { display: none !important; }
382
  .flagging { display: none !important; }
383
 
@@ -413,7 +467,6 @@ document.addEventListener('DOMContentLoaded', forceLightMode);
413
  """
414
 
415
  with gr.Blocks() as demo:
416
- # تزریق HTML (استایل و اسکریپت)
417
  gr.HTML(html_code)
418
 
419
  with gr.Column(elem_id="col-container"):
@@ -440,7 +493,6 @@ with gr.Blocks() as demo:
440
  with gr.Column():
441
  output_image = gr.Image(label="تصویر نهایی", interactive=False, format="png", height=380)
442
 
443
- # دکمه دانلود اختصاصی برای ارسال به Iframe
444
  download_button = gr.Button("📥 دانلود و ذخیره تصویر", variant="secondary", elem_id="download-btn", elem_classes="primary-btn")
445
 
446
  with gr.Row():
@@ -450,20 +502,39 @@ with gr.Blocks() as demo:
450
  value="تبدیل عکس به انیمه"
451
  )
452
 
453
- # --- تنظیمات پیشرفته با قابلیت انتخاب سایز ---
454
  with gr.Accordion("تنظیمات پیشرفته", open=False, visible=True):
455
  aspect_ratio_selection = gr.Dropdown(
456
  label="ابعاد تصویر خروجی",
457
- choices=list(ASPECT_RATIOS.keys()),
458
  value="خودکار (پیش‌فرض)",
459
  interactive=True
460
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
  seed = gr.Slider(label="دانه تصادفی (Seed)", minimum=0, maximum=MAX_SEED, step=1, value=0)
462
  randomize_seed = gr.Checkbox(label="استفاده از Seed تصادفی", value=True)
463
  guidance_scale = gr.Slider(label="میزان وفاداری به متن (Guidance Scale)", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
464
  steps = gr.Slider(label="تعداد مراحل پردازش (Steps)", minimum=1, maximum=50, step=1, value=4)
465
 
466
- # تعریف نمونه‌ها (Examples)
 
 
 
 
 
 
467
  gr.Examples(
468
  examples=[
469
  ["examples/1.jpg", "تبدیل به انیمه کن.", "تبدیل عکس به انیمه"],
@@ -485,14 +556,12 @@ with gr.Blocks() as demo:
485
  label="نمونه‌ها (برای تست کلیک کنید)"
486
  )
487
 
488
- # اتصال دکمه اجرا
489
  run_button.click(
490
  fn=infer,
491
- inputs=[input_image, prompt, lora_adapter, seed, randomize_seed, guidance_scale, steps, aspect_ratio_selection],
492
  outputs=[output_image, seed]
493
  )
494
 
495
- # اتصال دکمه دانلود
496
  download_button.click(
497
  fn=None,
498
  inputs=[output_image],
 
4
  import spaces
5
  import torch
6
  import random
7
+ from PIL import Image, ImageFilter
8
  from typing import Iterable
9
  from gradio.themes import Soft
10
  from gradio.themes.utils import colors, fonts, sizes
11
  from deep_translator import GoogleTranslator
12
+ from transformers import pipeline
13
 
14
  # --- تعریف تم ---
15
  colors.steel_blue = colors.Color(
 
29
 
30
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
31
 
32
+ # --- بارگذاری مدل تشخیص محتوای نامناسب (NSFW) ---
33
+ print("Loading Safety Checker...")
34
+ safety_classifier = pipeline("image-classification", model="Falconsai/nsfw_image_detection", device=-1)
35
+
36
+ def is_image_nsfw(image):
37
+ try:
38
+ results = safety_classifier(image)
39
+ for result in results:
40
+ if result['label'] == 'nsfw' and result['score'] > 0.8:
41
+ return True
42
+ return False
43
+ except Exception as e:
44
+ print(f"Safety check error: {e}")
45
+ return False
46
+
47
+ # --- بارگذاری مدل اصلی ---
48
  from diffusers import FlowMatchEulerDiscreteScheduler
49
  from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
50
  from qwenimage.transformer_qwenimage import QwenImageTransformer2DModel
 
94
  pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
95
  MAX_SEED = np.iinfo(np.int32).max
96
 
 
97
  LORA_MAPPING = {
98
  "تبدیل عکس به انیمه": "anime",
99
  "تغییر زاویه دید": "multiple-angles",
 
105
  "افزایش کیفیت (Upscale)": "upscale-image"
106
  }
107
 
108
+ # لیست ابعاد، شامل گزینه شخصی‌سازی
109
+ ASPECT_RATIOS = [
110
+ "خودکار (پیش‌فرض)",
111
+ "۱:۱ (مربع - 1024x1024)",
112
+ "۱۶:۹ (افقی - 1344x768)",
113
+ "۹:۱۶ (عمودی - 768x1344)",
114
+ "شخصی‌سازی (Custom)"
115
+ ]
116
+
117
+ ASPECT_RATIOS_MAP = {
118
  "خودکار (پیش‌فرض)": "Auto",
119
+ "۱:۱ (مربع - 1024x1024)": (1024, 1024),
120
+ "۱۶:۹ (افقی - 1344x768)": (1344, 768),
121
+ "۹:۱۶ (عمودی - 768x1344)": (768, 1344),
122
+ "شخصی‌سازی (Custom)": "Custom"
123
  }
124
 
125
+ BANNED_WORDS = [
126
+ "nude", "naked", "sex", "porn", "undressed", "nsfw", "erotic", "xxx",
127
+ "breast", "nipple", "genital", "vagina", "penis", "ass", "butt", "sexual",
128
+ "lingerie", "bikini", "swimwear", "underwear", "fetish", "topless",
129
+ "exhibitionism", "hentai", "ecchi", "18+"
130
+ ]
131
+
132
+ def check_text_safety(text):
133
+ text_lower = text.lower()
134
+ for word in BANNED_WORDS:
135
+ if f" {word} " in f" {text_lower} ":
136
+ return False
137
+ return True
138
+
139
  def translate_prompt(text):
 
140
  if not text:
141
  return ""
142
  try:
 
166
 
167
  return new_width, new_height
168
 
169
+ # تابع برای نمایش/مخفی کردن اسلایدرهای ابعاد
170
+ def toggle_custom_dimensions(value):
171
+ if value == "شخصی‌سازی (Custom)":
172
+ return gr.update(visible=True), gr.update(visible=True)
173
+ else:
174
+ return gr.update(visible=False), gr.update(visible=False)
175
+
176
  @spaces.GPU(duration=30)
177
  def infer(
178
  input_image,
 
182
  randomize_seed,
183
  guidance_scale,
184
  steps,
185
+ aspect_ratio_selection,
186
+ custom_width,
187
+ custom_height,
188
  progress=gr.Progress(track_tqdm=True)
189
  ):
190
  if input_image is None:
191
  raise gr.Error("لطفاً یک تصویر برای ویرایش بارگذاری کنید.")
192
 
193
+ # 1. بررسی امنیت تصویر ورودی
194
+ if is_image_nsfw(input_image):
195
+ raise gr.Error("تصویر ورودی دارای محتوای نامناسب تشخیص داده شد.")
196
+
197
+ # 2. ترجمه و بررسی متن
198
  english_prompt = translate_prompt(prompt)
199
+ if not check_text_safety(english_prompt):
200
+ raise gr.Error("متن درخواست شامل کلمات غیرمجاز است.")
201
+
202
  adapter_internal_name = LORA_MAPPING.get(lora_adapter_persian)
 
203
  if adapter_internal_name:
204
  pipe.set_adapters([adapter_internal_name], adapter_weights=[1.0])
205
 
 
207
  seed = random.randint(0, MAX_SEED)
208
 
209
  generator = torch.Generator(device=device).manual_seed(seed)
210
+
211
+ safety_negative = "nsfw, nude, naked, porn, sexual, xxx, breast, nipple, genital, vagina, penis, ass, lingerie, bikini, swimwear, underwear, fetish, topless, gore, violence, blood"
212
+ base_negative = "worst quality, low quality, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, jpeg artifacts, signature, watermark, username, blurry"
213
+ final_negative_prompt = f"{safety_negative}, {base_negative}"
214
 
215
  original_image = input_image.convert("RGB")
216
 
217
+ # --- منطق ابعاد ---
218
+ selection_value = ASPECT_RATIOS_MAP.get(aspect_ratio_selection)
219
+
220
+ if selection_value == "Custom":
221
+ # استفاده از ابعاد شخصی‌سازی شده (باید مضرب 8 باشد)
222
+ width = (int(custom_width) // 8) * 8
223
+ height = (int(custom_height) // 8) * 8
224
+ elif selection_value == "Auto" or selection_value is None:
225
  width, height = update_dimensions_on_upload(original_image)
226
  else:
227
+ # استفاده از ابعاد پیش‌فرض (تاپل)
228
+ width, height = selection_value
 
 
 
 
 
229
 
230
  result = pipe(
231
  image=original_image,
232
  prompt=english_prompt,
233
+ negative_prompt=final_negative_prompt,
234
  height=height,
235
  width=width,
236
  num_inference_steps=steps,
 
238
  true_cfg_scale=guidance_scale,
239
  ).images[0]
240
 
241
+ if is_image_nsfw(result):
242
+ raise gr.Error("تصویر تولید شده حاوی محتوای نامناسب بود و حذف شد.")
243
+
244
  return result, seed
245
 
246
  @spaces.GPU(duration=30)
 
248
  input_pil = input_image.convert("RGB")
249
  guidance_scale = 1.0
250
  steps = 4
251
+ # برای نمونه‌ها، ابعاد مهم نیست چون روی خودکار است
252
+ result, seed = infer(input_pil, prompt, lora_adapter, 0, True, guidance_scale, steps, "خودکار (پیش‌فرض)", 1024, 1024)
253
  return result, seed
254
 
255
 
 
274
  }
275
  """
276
 
277
+ # --- تنظیمات HTML (استایل و اسکریپت) ---
278
  html_code = """
279
  <style>
280
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700&display=swap');
281
 
282
+ /* --- تنظیمات تم روشن اجباری --- */
283
  :root, .dark, body, .gradio-container {
284
  --body-background-fill: #f5f7fa !important;
285
  --body-text-color: #1f2937 !important;
 
300
  padding: 10px;
301
  }
302
 
 
303
  #col-container {
304
  margin: 0 auto;
305
  max-width: 980px;
 
312
  border: 1px solid rgba(255,255,255,0.8);
313
  }
314
 
 
315
  #main-title h1 {
316
  font-size: 2.4em !important;
317
  text-align: center;
 
331
  line-height: 1.6;
332
  }
333
 
 
334
  .gr-input-label, span.label-wrap, label span {
335
  font-weight: 700 !important;
336
  color: #374151 !important;
 
359
  border-radius: 12px !important;
360
  }
361
 
 
362
  .primary-btn, button.primary {
363
  background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
364
  border: none !important;
 
391
  box-shadow: 0 8px 25px rgba(59, 130, 246, 0.45) !important;
392
  }
393
 
394
+ /* --- استایل بخش نمونه‌ها --- */
395
  .gradio-container .prose table,
396
  .gradio-container table {
397
  background-color: #ffffff !important;
 
428
  }
429
 
430
  .gradio-container tbody td span,
431
+ .gradio-container tbody td p {
 
432
  color: #374151 !important;
433
  }
434
 
 
435
  footer { display: none !important; }
436
  .flagging { display: none !important; }
437
 
 
467
  """
468
 
469
  with gr.Blocks() as demo:
 
470
  gr.HTML(html_code)
471
 
472
  with gr.Column(elem_id="col-container"):
 
493
  with gr.Column():
494
  output_image = gr.Image(label="تصویر نهایی", interactive=False, format="png", height=380)
495
 
 
496
  download_button = gr.Button("📥 دانلود و ذخیره تصویر", variant="secondary", elem_id="download-btn", elem_classes="primary-btn")
497
 
498
  with gr.Row():
 
502
  value="تبدیل عکس به انیمه"
503
  )
504
 
 
505
  with gr.Accordion("تنظیمات پیشرفته", open=False, visible=True):
506
  aspect_ratio_selection = gr.Dropdown(
507
  label="ابعاد تصویر خروجی",
508
+ choices=ASPECT_RATIOS,
509
  value="خودکار (پیش‌فرض)",
510
  interactive=True
511
  )
512
+
513
+ # --- بخش تنظیمات شخصی‌سازی ابعاد ---
514
+ with gr.Row():
515
+ custom_width = gr.Slider(
516
+ label="عرض دلخواه (Width)",
517
+ minimum=256, maximum=2048, step=8, value=1024,
518
+ visible=False
519
+ )
520
+ custom_height = gr.Slider(
521
+ label="ارتفاع دلخواه (Height)",
522
+ minimum=256, maximum=2048, step=8, value=1024,
523
+ visible=False
524
+ )
525
+
526
  seed = gr.Slider(label="دانه تصادفی (Seed)", minimum=0, maximum=MAX_SEED, step=1, value=0)
527
  randomize_seed = gr.Checkbox(label="استفاده از Seed تصادفی", value=True)
528
  guidance_scale = gr.Slider(label="میزان وفاداری به متن (Guidance Scale)", minimum=1.0, maximum=10.0, step=0.1, value=1.0)
529
  steps = gr.Slider(label="تعداد مراحل پردازش (Steps)", minimum=1, maximum=50, step=1, value=4)
530
 
531
+ # رویداد تغییر منوی ابعاد
532
+ aspect_ratio_selection.change(
533
+ fn=toggle_custom_dimensions,
534
+ inputs=aspect_ratio_selection,
535
+ outputs=[custom_width, custom_height]
536
+ )
537
+
538
  gr.Examples(
539
  examples=[
540
  ["examples/1.jpg", "تبدیل به انیمه کن.", "تبدیل عکس به انیمه"],
 
556
  label="نمونه‌ها (برای تست کلیک کنید)"
557
  )
558
 
 
559
  run_button.click(
560
  fn=infer,
561
+ inputs=[input_image, prompt, lora_adapter, seed, randomize_seed, guidance_scale, steps, aspect_ratio_selection, custom_width, custom_height],
562
  outputs=[output_image, seed]
563
  )
564
 
 
565
  download_button.click(
566
  fn=None,
567
  inputs=[output_image],