Surae007 commited on
Commit
6171212
·
verified ·
1 Parent(s): 8a34363
Files changed (1) hide show
  1. app.py +198 -233
app.py CHANGED
@@ -1,10 +1,9 @@
1
- import os, io, time, json
2
  from typing import List, Dict, Optional, Tuple
3
 
4
  import gradio as gr
5
  import numpy as np
6
  from PIL import Image, ImageDraw
7
-
8
  import torch
9
  from diffusers import (
10
  StableDiffusionXLPipeline,
@@ -17,14 +16,12 @@ from diffusers import (
17
  EulerAncestralDiscreteScheduler, HeunDiscreteScheduler,
18
  )
19
 
20
- # -------- Optional deps (SAFE IMPORTS: ไม่มีโมดูลก็ไม่พัง) --------
21
- # Remove background
22
  try:
23
  from rembg import remove as rembg_remove
24
  except Exception:
25
  rembg_remove = None
26
 
27
- # Face restore (GFPGAN)
28
  _HAS_GFP = False
29
  GFPGANer = None
30
  GFP = None
@@ -36,7 +33,6 @@ try:
36
  except Exception as e:
37
  print("[WARN] GFPGAN not available:", e)
38
 
39
- # RealESRGAN (upscale; ใช้ได้เมื่อมี)
40
  _HAS_REALESRGAN = False
41
  RealESRGAN = None
42
  REALSR = None
@@ -46,59 +42,52 @@ try:
46
  except Exception as e:
47
  print("[WARN] RealESRGAN not available:", e)
48
 
49
- # ---------------------------------------------------------------
50
-
51
  device = "cuda" if torch.cuda.is_available() else "cpu"
52
  dtype = torch.float16 if device == "cuda" else torch.float32
53
 
54
- # ---------------- Registry (ตัวเลือกยอดนิยม + เพิ่มเองได้) ----------------
55
  MODELS: List[Tuple[str,str,str]] = [
56
- ("stabilityai/stable-diffusion-xl-base-1.0", "SDXL Base 1.0", "เอนกประสงค์ สมดุล"),
57
- ("stabilityai/stable-diffusion-xl-refiner-1.0","SDXL Refiner", "เก็บรายละเอียด (pass 2)"),
58
  ("SG161222/RealVisXL_V4.0", "RealVis XL v4", "โฟโต้เรียล คน/สินค้า"),
59
- ("Lykon/dreamshaper-xl-v2", "DreamShaper XL","แฟนตาซี–เรียลลิสติก"),
60
- ("RunDiffusion/Juggernaut-XL", "Juggernaut XL", "คอนทราสต์แรง รายละเอียดหนัก"),
61
  ("emilianJR/epiCRealismXL", "EpicRealism XL","แฟชั่น/พอร์เทรต"),
62
- ("black-forest-labs/FLUX.1-dev", "FLUX.1-dev", "สมัยใหม่/คุมสไตล์ดี (ไม่ใช่ SDXL)"),
63
- ("stabilityai/sd-turbo", "SD-Turbo", "ไวมาก เหมาะกับร่างไอเดีย"),
64
- ("stabilityai/stable-diffusion-2-1", "SD 2.1", "แลนด์สเคป/องค์ประกอบกว้าง"),
65
- ("runwayml/stable-diffusion-v1-5", "SD 1.5", "สไตล์คลาสสิก"),
66
- ("timbrooks/instruct-pix2pix", "Instruct-Pix2Pix","แก้ภาพตามคำสั่ง (img2img)"),
67
  ]
68
 
69
  LORAS: List[Tuple[str,str,str]] = [
70
- ("ByteDance/SDXL-Lightning", "SDXL-Lightning", "สปีดเร็ว (LoRA)"),
71
- ("ostris/epicrealism-xl-lora", "EpicrealismXL-LoRA","โทนเรียลลิสติก"),
72
- ("XLabs-AI/flux-prompt-lora", "FLUX Prompt LoRA", "ปรับ prompt style (FLUX)"),
73
- ("XLabs-AI/realvisxl-v4-lora", "RealVisXL LoRA", "พอร์เทรต/สินค้า"),
74
- ("alpha-diffusion/sdxl-anime-lora", "Anime-Style XL", "อนิเม/เส้นใส"),
75
- ("alpha-diffusion/sdxl-cinematic-lora", "Cinematic-Drama", "แสงเงาแบบหนัง"),
76
- ("alpha-diffusion/sdxl-watercolor-lora", "Watercolor-Soft", "สีน้ำ/พาสเทล"),
77
- ("alpha-diffusion/sdxl-fashion-lora", "Fashion-Editorial","แฟชั่น/กองถ่าย"),
78
- ("alpha-diffusion/sdxl-product-lora", "Product-Studio", "สินค้า/แส��สตูดิโอ"),
79
- ("alpha-diffusion/sdxl-interior-lora", "Interior-Archi", "ห้อง/สถาปัตย์"),
80
- ("alpha-diffusion/sdxl-food-lora", "Food-Tasty", "อาหารฉ่ำ/เงางาม"),
81
  ]
82
 
 
83
  CONTROLNETS: List[Tuple[str,str,str,str]] = [
84
- ("diffusers/controlnet-canny-sdxl-1.0", "Canny", "คุมเส้นขอบ", "canny"),
85
- ("diffusers/controlnet-openpose-sdxl-1.0", "OpenPose", "คุมท่าทางคน", "pose"),
86
- ("diffusers/controlnet-depth-sdxl-1.0", "Depth", "คุมมุมมอง/ระยะลึก", "depth"),
87
- ("diffusers/controlnet-softedge-sdxl-1.0", "SoftEdge", "เส้นนุ่ม/ลดแตก", "softedge"),
88
- ("diffusers/controlnet-lineart-sdxl-1.0", "Lineart", "เส้นร่าง/การ์ตูน", "lineart"),
89
- ("diffusers/controlnet-anime-lineart-sdxl-1.0","Anime Lineart","เส้นอนิเม", "anime_lineart"),
90
- ("diffusers/controlnet-normal-sdxl-1.0", "Normal", "ทิศทางพื้นผิว", "normal"),
91
- ("diffusers/controlnet-mlsd-sdxl-1.0", "MLSD", "เส้นตรง/สถาปัตย์", "mlsd"),
92
- ("diffusers/controlnet-scribble-sdxl-1.0", "Scribble", "สเก็ตช์หยาบ→จริง", "scribble"),
93
- ("diffusers/controlnet-seg-sdxl-1.0", "Segmentation", "แบ่งส่วน/สี", "seg"),
94
- ("diffusers/controlnet-tile-sdxl-1.0", "Tile", "อัปสเกลแบบกระเบื้อง", "tile"),
95
  ]
96
 
97
  PRESETS = {
98
  "Cinematic": ", cinematic lighting, 50mm, bokeh, film grain, high dynamic range",
99
  "Studio": ", studio photo, softbox lighting, sharp focus, high detail",
100
- "Product": ", product photography, seamless background, diffused light, reflections",
101
  "Anime": ", anime style, clean lineart, vibrant colors, high quality",
 
102
  }
103
  NEG_DEFAULT = "lowres, blurry, bad anatomy, extra fingers, watermark, jpeg artifacts, text"
104
 
@@ -109,7 +98,7 @@ SCHEDULERS = {
109
  "Heun": HeunDiscreteScheduler,
110
  }
111
 
112
- # ---------------- Cache ----------------
113
  PIPE_CACHE: Dict[str, object] = {}
114
  CONTROL_CACHE: Dict[str, ControlNetModel] = {}
115
  UPSCALE_PIPE: Optional[StableDiffusionUpscalePipeline] = None
@@ -121,7 +110,7 @@ def set_sched(pipe, name: str):
121
 
122
  def seed_gen(sd: int):
123
  if sd is None or sd < 0: return None
124
- g = torch.Generator(device=device if device=="cuda" else "cpu")
125
  g.manual_seed(int(sd)); return g
126
 
127
  def prep_pipe(model_id: str, control_ids: List[str]):
@@ -129,41 +118,39 @@ def prep_pipe(model_id: str, control_ids: List[str]):
129
  if key in PIPE_CACHE: return PIPE_CACHE[key]
130
 
131
  if control_ids:
132
- cns = []
133
  for cid in control_ids:
134
  if cid not in CONTROL_CACHE:
135
  CONTROL_CACHE[cid] = ControlNetModel.from_pretrained(cid, torch_dtype=dtype, use_safetensors=True)
136
- cns.append(CONTROL_CACHE[cid])
137
- pipe = StableDiffusionXLControlNetPipeline.from_pretrained(model_id, controlnet=cns, torch_dtype=dtype, use_safetensors=True)
138
  else:
139
  pipe = StableDiffusionXLPipeline.from_pretrained(model_id, torch_dtype=dtype, use_safetensors=True)
140
 
141
- if device == "cuda":
142
- pipe.to("cuda"); pipe.enable_vae_tiling(); pipe.enable_vae_slicing()
143
- try: pipe.enable_xformers_memory_efficient_attention()
144
- except Exception: pass
145
- else:
146
- pipe.to("cpu"); pipe.enable_attention_slicing()
 
 
 
147
 
148
  PIPE_CACHE[key] = pipe
149
  return pipe
150
 
151
- def apply_loras(pipe, ids: List[str], scales: List[float]):
152
- for i, rid in enumerate([x for x in ids if x]):
153
  try:
154
  pipe.load_lora_weights(rid)
155
- try:
156
- sc = scales[i] if i < len(scales) else 0.7
157
- pipe.fuse_lora(lora_scale=float(sc))
158
- except Exception:
159
- pass
160
  except Exception as e:
161
  print(f"[LoRA] load failed {rid}: {e}")
162
 
163
- def to_png_info(meta: dict) -> str:
164
  return json.dumps(meta, ensure_ascii=False, indent=2)
165
 
166
- # --------------- Optional post-process ---------------
167
  def ensure_upscalers():
168
  global UPSCALE_PIPE, GFP, REALSR
169
  if UPSCALE_PIPE is None:
@@ -175,27 +162,25 @@ def ensure_upscalers():
175
  except Exception as e:
176
  print("[Upscaler] SD x4 not available:", e)
177
 
178
- # GFPGAN
179
  if _HAS_GFP and GFP is None and GFPGANer is not None:
180
  try:
181
  GFP = GFPGANer(model_path=None, upscale=1, arch="clean", channel_multiplier=2)
182
  except Exception as e:
183
  print("[GFPGAN] init failed:", e)
184
 
185
- # RealESRGAN (ต้องมี weights เองถึงใช้ได้)
186
  if _HAS_REALESRGAN and REALSR is None and device == "cuda":
187
  try:
188
- REALSR = RealESRGAN(torch.device("cuda"), scale=4) # ต้องมีไฟล์ weights ถึงจะทำงานจริง
189
- # REALSR.load_weights("weights/RealESRGAN_x4plus.pth")
190
  except Exception as e:
191
  REALSR = None
192
  print("[RealESRGAN] init failed:", e)
193
 
194
- def post_process(img: Image.Image, do_upscale: bool, do_face: bool, do_rembg: bool):
195
  ensure_upscalers()
196
  out = img
197
 
198
- if do_upscale:
 
199
  try:
200
  if REALSR is not None:
201
  out = Image.fromarray(REALSR.predict(np.array(out)))
@@ -215,7 +200,7 @@ def post_process(img: Image.Image, do_upscale: bool, do_face: bool, do_rembg: bo
215
  except Exception as e:
216
  print("[GFPGAN] skipped:", e)
217
 
218
- if do_rembg and rembg_remove is not None:
219
  try:
220
  out = Image.open(io.BytesIO(rembg_remove(np.array(out))))
221
  except Exception as e:
@@ -223,98 +208,97 @@ def post_process(img: Image.Image, do_upscale: bool, do_face: bool, do_rembg: bo
223
 
224
  return out
225
 
226
- # ---------------- Core generators ----------------
227
- def run_txt2img(model_id, custom_model, prompt, preset, negative,
228
- steps, cfg, width, height, scheduler_name, seed,
229
- lora_list, lora_custom_csv, lora_s1, lora_s2, lora_s3,
230
- ctrl_selected, ctrl_images, use_refiner, refine_strength,
231
- do_upscale, do_face, do_rembg):
232
-
233
- if not prompt.strip(): raise gr.Error("กรุณากรอก prompt")
234
- model = (custom_model.strip() or model_id).strip()
235
- if preset in PRESETS: prompt = prompt + PRESETS[preset]
236
- if not (negative or "").strip(): negative = NEG_DEFAULT
237
-
238
- # ControlNet images
239
- cond_imgs, ctrl_ids = [], []
240
- for (cid, label, note, key) in CONTROLNETS:
241
- if label in ctrl_selected and key in ctrl_images and ctrl_images[key] is not None:
242
- ctrl_ids.append(cid); cond_imgs.append(ctrl_images[key])
243
-
244
- pipe = prep_pipe(model, ctrl_ids)
 
 
 
 
 
 
 
245
  set_sched(pipe, scheduler_name)
246
 
247
- loras = []
248
- if lora_list: loras += lora_list
249
- if lora_custom_csv.strip():
250
- loras += [x.strip() for x in lora_custom_csv.split(",") if x.strip()]
251
- apply_loras(pipe, loras, [lora_s1, lora_s2, lora_s3])
252
 
253
- width = int(max(512, min(1024, width)))
254
- height = int(max(512, min(1024, height)))
255
  gen = seed_gen(seed)
256
 
257
  if device == "cuda":
258
  with torch.autocast("cuda"):
259
- if ctrl_ids:
260
- image = pipe(prompt=prompt, negative_prompt=negative,
261
- width=width, height=height,
262
- num_inference_steps=int(steps), guidance_scale=float(cfg),
263
- controlnet_conditioning_image=cond_imgs if len(cond_imgs)>1 else cond_imgs[0],
264
- generator=gen).images[0]
 
 
265
  else:
266
- image = pipe(prompt=prompt, negative_prompt=negative,
267
- width=width, height=height,
268
- num_inference_steps=int(steps), guidance_scale=float(cfg),
269
- generator=gen).images[0]
 
 
270
  else:
271
- if ctrl_ids:
272
- image = pipe(prompt=prompt, negative_prompt=negative,
273
- width=width, height=height,
274
- num_inference_steps=int(steps), guidance_scale=float(cfg),
275
- controlnet_conditioning_image=cond_imgs if len(cond_imgs)>1 else cond_imgs[0],
276
- generator=gen).images[0]
 
 
277
  else:
278
- image = pipe(prompt=prompt, negative_prompt=negative,
279
- width=width, height=height,
280
- num_inference_steps=int(steps), guidance_scale=float(cfg),
281
- generator=gen).images[0]
282
-
283
- # Refiner (GPU only)
284
- if use_refiner and device == "cuda":
285
- try:
286
- ref = StableDiffusionXLImg2ImgPipeline.from_pretrained(
287
- "stabilityai/stable-diffusion-xl-refiner-1.0",
288
- torch_dtype=torch.float16, use_safetensors=True
289
- ).to("cuda")
290
- set_sched(ref, scheduler_name)
291
- with torch.autocast("cuda"):
292
- image = ref(prompt=prompt, negative_prompt=negative, image=image,
293
- strength=float(refine_strength),
294
- num_inference_steps=max(10, int(steps)//2),
295
- guidance_scale=float(cfg), generator=gen).images[0]
296
- except Exception as e:
297
- print("[Refiner] skipped:", e)
298
-
299
- image = post_process(image, do_upscale, do_face, do_rembg)
300
  meta = {
301
- "mode":"txt2img","model":model,"loras":loras,"controlnets":ctrl_selected,
302
  "prompt":prompt,"negative":negative,"size":f"{width}x{height}",
303
  "steps":steps,"cfg":cfg,"scheduler":scheduler_name,"seed":seed,
304
- "post":{"upscale":do_upscale,"face_restore":do_face,"remove_bg":do_rembg}
305
  }
306
- return image, to_png_info(meta)
307
 
308
- def run_img2img(model_id, custom_model, init_image, strength,
309
- prompt, preset, negative, steps, cfg, width, height, scheduler_name, seed,
310
- do_upscale, do_face, do_rembg):
 
 
311
  if init_image is None: raise gr.Error("โปรดอัปโหลดภาพเริ่มต้น")
312
- model = (custom_model.strip() or model_id).strip()
313
- if preset in PRESETS: prompt = prompt + PRESETS[preset]
314
- if not (negative or "").strip(): negative = NEG_DEFAULT
315
 
316
- pipe = StableDiffusionXLImg2ImgPipeline.from_pretrained(model, torch_dtype=dtype, use_safetensors=True)
317
- pipe = pipe.to(device)
318
  try:
319
  if device=="cuda": pipe.enable_xformers_memory_efficient_attention()
320
  except Exception: pass
@@ -322,50 +306,48 @@ def run_img2img(model_id, custom_model, init_image, strength,
322
 
323
  if device=="cuda":
324
  with torch.autocast("cuda"):
325
- img = pipe(prompt=prompt, negative_prompt=negative, image=init_image,
326
- strength=float(strength), num_inference_steps=int(steps),
327
- guidance_scale=float(cfg), generator=gen).images[0]
 
328
  else:
329
- img = pipe(prompt=prompt, negative_prompt=negative, image=init_image,
330
- strength=float(strength), num_inference_steps=int(steps),
331
- guidance_scale=float(cfg), generator=gen).images[0]
 
332
 
333
- img = post_process(img, do_upscale, do_face, do_rembg)
334
  meta = {"mode":"img2img","model":model,"prompt":prompt,"neg":negative,
335
  "steps":steps,"cfg":cfg,"seed":seed,"strength":strength}
336
- return img, to_png_info(meta)
337
 
338
  def expand_canvas_for_outpaint(img: Image.Image, expand_px: int, direction: str) -> Tuple[Image.Image, Image.Image]:
339
  w, h = img.size
340
- new = Image.new("RGBA", (w, h), (0,0,0,0))
341
- mask = Image.new("L", (w, h), 0)
342
- draw = ImageDraw.Draw(mask)
343
-
344
  if direction == "left":
345
- new = Image.new("RGBA", (w+expand_px, h), (0,0,0,0)); new.paste(img, (expand_px,0))
346
- mask = Image.new("L", (w+expand_px, h), 0); draw = ImageDraw.Draw(mask); draw.rectangle([0,0,expand_px,h], fill=255)
347
  elif direction == "right":
348
- new = Image.new("RGBA", (w+expand_px, h), (0,0,0,0)); new.paste(img, (0,0))
349
- mask = Image.new("L", (w+expand_px, h), 0); draw = ImageDraw.Draw(mask); draw.rectangle([w,0,w+expand_px,h], fill=255)
350
  elif direction == "top":
351
- new = Image.new("RGBA", (w, h+expand_px), (0,0,0,0)); new.paste(img, (0,expand_px))
352
- mask = Image.new("L", (w, h+expand_px), 0); draw = ImageDraw.Draw(mask); draw.rectangle([0,0,w,expand_px], fill=255)
353
- else: # bottom
354
- new = Image.new("RGBA", (w, h+expand_px), (0,0,0,0)); new.paste(img, (0,0))
355
- mask = Image.new("L", (w, h+expand_px), 0); draw = ImageDraw.Draw(mask); draw.rectangle([0,h,w,h+expand_px], fill=255)
356
-
357
  return new.convert("RGB"), mask
358
 
359
- def run_inpaint_outpaint(model_id, custom_model, base_image, mask_image, mode, expand_px, expand_dir,
360
- prompt, preset, negative, steps, cfg, width, height, scheduler_name, seed,
361
- strength, do_upscale, do_face, do_rembg):
 
 
362
  if base_image is None: raise gr.Error("โปรดอัปโหลดภาพฐาน")
363
- model = (custom_model.strip() or model_id).strip()
364
- if preset in PRESETS: prompt = prompt + PRESETS[preset]
365
- if not (negative or "").strip(): negative = NEG_DEFAULT
366
 
367
- pipe = StableDiffusionXLInpaintPipeline.from_pretrained(model, torch_dtype=dtype, use_safetensors=True)
368
- pipe = pipe.to(device)
369
  try:
370
  if device=="cuda": pipe.enable_xformers_memory_efficient_attention()
371
  except Exception: pass
@@ -378,26 +360,29 @@ def run_inpaint_outpaint(model_id, custom_model, base_image, mask_image, mode, e
378
  with torch.autocast("cuda"):
379
  img = pipe(prompt=prompt, negative_prompt=negative,
380
  image=base_image, mask_image=mask_image,
381
- strength=float(strength), num_inference_steps=int(steps),
382
- guidance_scale=float(cfg), generator=gen).images[0]
 
383
  else:
384
  img = pipe(prompt=prompt, negative_prompt=negative,
385
  image=base_image, mask_image=mask_image,
386
- strength=float(strength), num_inference_steps=int(steps),
387
- guidance_scale=float(cfg), generator=gen).images[0]
 
388
 
389
- img = post_process(img, do_upscale, do_face, do_rembg)
390
  meta = {"mode":mode,"model":model,"prompt":prompt,"steps":steps,"cfg":cfg,"seed":seed}
391
- return img, to_png_info(meta)
392
 
393
  # ---------------- UI ----------------
394
  def build_ui():
395
  with gr.Blocks(theme=gr.themes.Soft(), title="Masterpiece SDXL Studio Pro") as demo:
396
  gr.Markdown("# 🖼️ Masterpiece SDXL Studio Pro")
397
- gr.Markdown("Text2Img • Img2Img • Inpaint/Outpaint • Multi-LoRA • Multi-ControlNet • Upscale/FaceRestore/RemoveBG")
398
 
399
- model_dd = gr.Dropdown(choices=[m[0] for m in MODELS], value=MODELS[0][0], label="Model (เลือก)")
400
- model_custom = gr.Textbox(label="Custom Model ID (เช่น username/my-model)", placeholder="(ไม่จำเป็น)")
 
401
 
402
  preset = gr.Dropdown(choices=list(PRESETS.keys()), value=None, label="Style Preset (optional)")
403
  negative = gr.Textbox(value=NEG_DEFAULT, label="Negative Prompt")
@@ -407,42 +392,23 @@ def build_ui():
407
  width = gr.Slider(512, 1024, 832, step=64, label="Width")
408
  height= gr.Slider(512, 1024, 832, step=64, label="Height")
409
  scheduler = gr.Dropdown(list(SCHEDULERS.keys()), value="DPM-Solver (Karras)", label="Scheduler")
410
- seed = gr.Number(value=-1, precision=0, label="Seed (-1=random)")
 
 
 
 
 
 
 
 
 
 
 
411
 
412
- # LoRA
413
- lora_group = gr.CheckboxGroup(
414
- choices=[f"{rid} — {lbl} ({note})" for rid,lbl,note in LORAS],
415
- label="LoRA (เลือกหลายตัวได้)"
416
- )
417
- lora_custom = gr.Textbox(label="Custom LoRA IDs (คั่นด้วย comma)")
418
- lora_s1 = gr.Slider(0.0, 1.2, 0.7, 0.05, label="LoRA scale #1")
419
- lora_s2 = gr.Slider(0.0, 1.2, 0.5, 0.05, label="LoRA scale #2")
420
- lora_s3 = gr.Slider(0.0, 1.2, 0.5, 0.05, label="LoRA scale #3")
421
-
422
- # ControlNet
423
- ctrl_group = gr.CheckboxGroup(
424
- choices=[c[1]+" ("+c[2]+")" for c in CONTROLNETS],
425
- label="ControlNet (เลือกชนิด)"
426
- )
427
- imgs = {
428
- "canny": gr.Image(type="pil", label="Canny"),
429
- "pose": gr.Image(type="pil", label="OpenPose"),
430
- "depth": gr.Image(type="pil", label="Depth"),
431
- "softedge": gr.Image(type="pil", label="SoftEdge"),
432
- "lineart": gr.Image(type="pil", label="Lineart"),
433
- "anime_lineart": gr.Image(type="pil", label="Anime Lineart"),
434
- "normal": gr.Image(type="pil", label="Normal"),
435
- "mlsd": gr.Image(type="pil", label="MLSD"),
436
- "scribble": gr.Image(type="pil", label="Scribble"),
437
- "seg": gr.Image(type="pil", label="Segmentation"),
438
- "tile": gr.Image(type="pil", label="Tile"),
439
- }
440
-
441
- # Post-process
442
  with gr.Row():
443
- do_upscale = gr.Checkbox(False, label="Upscale x4 (ถ้ามี)")
444
- do_face = gr.Checkbox(False, label="Face Restore (ถ้ามี)")
445
- do_rembg = gr.Checkbox(False, label="Remove Background (ถ้ามี)")
446
 
447
  with gr.Tab("Text → Image"):
448
  prompt_txt = gr.Textbox(lines=3, label="Prompt")
@@ -451,7 +417,7 @@ def build_ui():
451
  out_meta_txt = gr.Textbox(label="Metadata", lines=10)
452
 
453
  with gr.Tab("Image → Image"):
454
- init_img = gr.Image(type="pil", label="Init Image (img2img)")
455
  strength = gr.Slider(0.1, 1.0, 0.7, 0.05, label="Strength")
456
  prompt_i2i = gr.Textbox(lines=3, label="Prompt")
457
  btn_i2i = gr.Button("🚀 Img2Img")
@@ -469,20 +435,15 @@ def build_ui():
469
  out_img_io = gr.Image(type="pil", label="Result")
470
  out_meta_io = gr.Textbox(label="Metadata", lines=10)
471
 
472
- def parse_lora_list(selected: List[str]) -> List[str]:
473
- if not selected: return []
474
- return [s.split(" — ")[0].strip() for s in selected]
475
-
476
  btn_txt.click(
477
  fn=run_txt2img,
478
  inputs=[
479
  model_dd, model_custom, prompt_txt, preset, negative,
480
  steps, cfg, width, height, scheduler, seed,
481
- gr.Variable(parse_lora_list), lora_custom, lora_s1, lora_s2, lora_s3,
482
- ctrl_group,
483
- {k:v for k,v in imgs.items()}, # dict of images
484
- gr.Checkbox(False), gr.Slider(0.05,0.5,0.2,0.05), # use_refiner, refine_strength (placeholder; UI ไม่โชว์)
485
- do_upscale, do_face, do_rembg
486
  ],
487
  outputs=[out_img_txt, out_meta_txt],
488
  api_name="txt2img"
@@ -490,23 +451,27 @@ def build_ui():
490
 
491
  btn_i2i.click(
492
  fn=run_img2img,
493
- inputs=[model_dd, model_custom, init_img, strength,
494
- prompt_i2i, preset, negative, steps, cfg, width, height, scheduler, seed,
495
- do_upscale, do_face, do_rembg],
 
 
496
  outputs=[out_img_i2i, out_meta_i2i],
497
  api_name="img2img"
498
  )
499
 
500
  btn_io.click(
501
  fn=run_inpaint_outpaint,
502
- inputs=[model_dd, model_custom, base_img, mask_img, mode_io, expand_px, expand_dir,
503
- prompt_io, preset, negative, steps, cfg, width, height, scheduler, seed,
504
- strength, do_upscale, do_face, do_rembg],
 
 
505
  outputs=[out_img_io, out_meta_io],
506
  api_name="inpaint_outpaint"
507
  )
508
 
509
- gr.Markdown("ℹ️ **หมายเห���ุ**: ถ้า LoRA/ControlNet/โพสต์โปรเซสบางตัวไม่มีในสภาพแวดล้อม โปรแกรมจะข้ามอย่างปลอดภัย พร้อมพิมพ์คำเตือนใน Console")
510
 
511
  return demo
512
 
 
1
+ import io, json
2
  from typing import List, Dict, Optional, Tuple
3
 
4
  import gradio as gr
5
  import numpy as np
6
  from PIL import Image, ImageDraw
 
7
  import torch
8
  from diffusers import (
9
  StableDiffusionXLPipeline,
 
16
  EulerAncestralDiscreteScheduler, HeunDiscreteScheduler,
17
  )
18
 
19
+ # ---------------- Optional deps (safe imports: ไม่มีก็ข้าม) ----------------
 
20
  try:
21
  from rembg import remove as rembg_remove
22
  except Exception:
23
  rembg_remove = None
24
 
 
25
  _HAS_GFP = False
26
  GFPGANer = None
27
  GFP = None
 
33
  except Exception as e:
34
  print("[WARN] GFPGAN not available:", e)
35
 
 
36
  _HAS_REALESRGAN = False
37
  RealESRGAN = None
38
  REALSR = None
 
42
  except Exception as e:
43
  print("[WARN] RealESRGAN not available:", e)
44
 
45
+ # ---------------- Runtime setup ----------------
 
46
  device = "cuda" if torch.cuda.is_available() else "cpu"
47
  dtype = torch.float16 if device == "cuda" else torch.float32
48
 
49
+ # ---------------- Registries ----------------
50
  MODELS: List[Tuple[str,str,str]] = [
51
+ ("stabilityai/stable-diffusion-xl-base-1.0", "SDXL Base 1.0", "เอนกประสงค์"),
52
+ ("stabilityai/stable-diffusion-xl-refiner-1.0","SDXL Refiner", "เสริมรายละเอียด (pass 2)"),
53
  ("SG161222/RealVisXL_V4.0", "RealVis XL v4", "โฟโต้เรียล คน/สินค้า"),
54
+ ("Lykon/dreamshaper-xl-v2", "DreamShaper XL","แฟนตาซี-เรียลลิสติก"),
55
+ ("RunDiffusion/Juggernaut-XL", "Juggernaut XL", "คอนทราสต์แรง"),
56
  ("emilianJR/epiCRealismXL", "EpicRealism XL","แฟชั่น/พอร์เทรต"),
57
+ ("black-forest-labs/FLUX.1-dev", "FLUX.1-dev", "แนวสมัยใหม่ (ไม่ใช่ SDXL)"),
58
+ ("stabilityai/sd-turbo", "SD-Turbo", "เร็วมากสำหรับร่างไอเดีย"),
59
+ ("stabilityai/stable-diffusion-2-1", "SD 2.1", "แลนด์สเคปกว้าง"),
60
+ ("runwayml/stable-diffusion-v1-5", "SD 1.5", "คลาสสิก"),
61
+ ("timbrooks/instruct-pix2pix", "Instruct-Pix2Pix","แก้ภาพตามคำสั่ง"),
62
  ]
63
 
64
  LORAS: List[Tuple[str,str,str]] = [
65
+ ("ByteDance/SDXL-Lightning", "SDXL-Lightning", "สปีด"),
66
+ ("ostris/epicrealism-xl-lora", "EpicRealism XL", "โทนจริง"),
67
+ ("alpha-diffusion/sdxl-anime-lora", "Anime-Style XL", "อนิเม"),
68
+ ("alpha-diffusion/sdxl-cinematic-lora","Cinematic-Drama", "แสงหนัง"),
69
+ ("alpha-diffusion/sdxl-watercolor-lora","Watercolor", "สีน้ำ"),
70
+ ("alpha-diffusion/sdxl-fashion-lora", "Fashion", "แฟชั่น"),
71
+ ("alpha-diffusion/sdxl-product-lora", "Product-Studio", "สินค้า"),
72
+ ("alpha-diffusion/sdxl-interior-lora", "Interior-Archi", "สถาปัตย์"),
73
+ ("alpha-diffusion/sdxl-food-lora", "Food-Tasty", "อาหาร"),
74
+ ("alpha-diffusion/sdxl-logo-lora", "Logo-Clean", "โลโก้"),
 
75
  ]
76
 
77
+ # ใช้ 5 ชนิดหลักเพื่อ UI กระชับและเสถียร
78
  CONTROLNETS: List[Tuple[str,str,str,str]] = [
79
+ ("diffusers/controlnet-canny-sdxl-1.0", "Canny", "เส้นขอบ", "canny"),
80
+ ("diffusers/controlnet-openpose-sdxl-1.0", "OpenPose", "ท่าทางคน", "pose"),
81
+ ("diffusers/controlnet-depth-sdxl-1.0", "Depth", "ระยะลึก", "depth"),
82
+ ("diffusers/controlnet-softedge-sdxl-1.0", "SoftEdge", "เส้นนุ่ม", "softedge"),
83
+ ("diffusers/controlnet-lineart-sdxl-1.0", "Lineart", "เส้นร่าง", "lineart"),
 
 
 
 
 
 
84
  ]
85
 
86
  PRESETS = {
87
  "Cinematic": ", cinematic lighting, 50mm, bokeh, film grain, high dynamic range",
88
  "Studio": ", studio photo, softbox lighting, sharp focus, high detail",
 
89
  "Anime": ", anime style, clean lineart, vibrant colors, high quality",
90
+ "Product": ", product photography, seamless background, diffused light, reflections",
91
  }
92
  NEG_DEFAULT = "lowres, blurry, bad anatomy, extra fingers, watermark, jpeg artifacts, text"
93
 
 
98
  "Heun": HeunDiscreteScheduler,
99
  }
100
 
101
+ # ---------------- Caches ----------------
102
  PIPE_CACHE: Dict[str, object] = {}
103
  CONTROL_CACHE: Dict[str, ControlNetModel] = {}
104
  UPSCALE_PIPE: Optional[StableDiffusionUpscalePipeline] = None
 
110
 
111
  def seed_gen(sd: int):
112
  if sd is None or sd < 0: return None
113
+ g = torch.Generator(device=("cuda" if device=="cuda" else "cpu"))
114
  g.manual_seed(int(sd)); return g
115
 
116
  def prep_pipe(model_id: str, control_ids: List[str]):
 
118
  if key in PIPE_CACHE: return PIPE_CACHE[key]
119
 
120
  if control_ids:
121
+ cn_models = []
122
  for cid in control_ids:
123
  if cid not in CONTROL_CACHE:
124
  CONTROL_CACHE[cid] = ControlNetModel.from_pretrained(cid, torch_dtype=dtype, use_safetensors=True)
125
+ cn_models.append(CONTROL_CACHE[cid])
126
+ pipe = StableDiffusionXLControlNetPipeline.from_pretrained(model_id, controlnet=cn_models, torch_dtype=dtype, use_safetensors=True)
127
  else:
128
  pipe = StableDiffusionXLPipeline.from_pretrained(model_id, torch_dtype=dtype, use_safetensors=True)
129
 
130
+ pipe.to(device)
131
+ try:
132
+ if device == "cuda":
133
+ pipe.enable_vae_tiling(); pipe.enable_vae_slicing()
134
+ pipe.enable_xformers_memory_efficient_attention()
135
+ else:
136
+ pipe.enable_attention_slicing()
137
+ except Exception:
138
+ pass
139
 
140
  PIPE_CACHE[key] = pipe
141
  return pipe
142
 
143
+ def apply_loras(pipe, lora_ids: List[str]):
144
+ for rid in [x for x in lora_ids if x]:
145
  try:
146
  pipe.load_lora_weights(rid)
 
 
 
 
 
147
  except Exception as e:
148
  print(f"[LoRA] load failed {rid}: {e}")
149
 
150
+ def to_info(meta: dict) -> str:
151
  return json.dumps(meta, ensure_ascii=False, indent=2)
152
 
153
+ # ---------------- Post-process ----------------
154
  def ensure_upscalers():
155
  global UPSCALE_PIPE, GFP, REALSR
156
  if UPSCALE_PIPE is None:
 
162
  except Exception as e:
163
  print("[Upscaler] SD x4 not available:", e)
164
 
 
165
  if _HAS_GFP and GFP is None and GFPGANer is not None:
166
  try:
167
  GFP = GFPGANer(model_path=None, upscale=1, arch="clean", channel_multiplier=2)
168
  except Exception as e:
169
  print("[GFPGAN] init failed:", e)
170
 
 
171
  if _HAS_REALESRGAN and REALSR is None and device == "cuda":
172
  try:
173
+ REALSR = RealESRGAN(torch.device("cuda"), scale=4) # ต้องมี weights เองจึงจะทำงานจริง
 
174
  except Exception as e:
175
  REALSR = None
176
  print("[RealESRGAN] init failed:", e)
177
 
178
+ def post_process(img: Image.Image, do_up: bool, do_face: bool, do_bg: bool):
179
  ensure_upscalers()
180
  out = img
181
 
182
+ # Upscale: RealESRGAN (ถ้ามี) > SD x4 > skip
183
+ if do_up:
184
  try:
185
  if REALSR is not None:
186
  out = Image.fromarray(REALSR.predict(np.array(out)))
 
200
  except Exception as e:
201
  print("[GFPGAN] skipped:", e)
202
 
203
+ if do_bg and rembg_remove is not None:
204
  try:
205
  out = Image.open(io.BytesIO(rembg_remove(np.array(out))))
206
  except Exception as e:
 
208
 
209
  return out
210
 
211
+ # ---------------- Generators ----------------
212
+ def run_txt2img(
213
+ model_id, model_custom, prompt, preset, negative,
214
+ steps, cfg, width, height, scheduler_name, seed,
215
+ lora_selected, lora_custom,
216
+ ctrl_selected, img_canny, img_pose, img_depth, img_softedge, img_lineart,
217
+ do_up, do_face, do_bg
218
+ ):
219
+ if not prompt or not str(prompt).strip():
220
+ raise gr.Error("กรุณากรอก prompt")
221
+
222
+ model = (model_custom.strip() or model_id).strip()
223
+ if preset and preset in PRESETS: prompt = prompt + PRESETS[preset]
224
+ if not negative or not negative.strip(): negative = NEG_DEFAULT
225
+
226
+ # ControlNet mapping (เฉพาะภาพที่อัปโหลดจริง)
227
+ label_to_img = {
228
+ "Canny": img_canny, "OpenPose": img_pose, "Depth": img_depth,
229
+ "SoftEdge": img_softedge, "Lineart": img_lineart
230
+ }
231
+ control_ids, cond_images = [], []
232
+ for cid, label, note, key in CONTROLNETS:
233
+ if label in ctrl_selected and label_to_img.get(label) is not None:
234
+ control_ids.append(cid); cond_images.append(label_to_img[label])
235
+
236
+ pipe = prep_pipe(model, control_ids)
237
  set_sched(pipe, scheduler_name)
238
 
239
+ # LoRA
240
+ lora_ids = [s.split(" ")[0].strip() for s in (lora_selected or [])]
241
+ if lora_custom and lora_custom.strip():
242
+ lora_ids += [x.strip() for x in lora_custom.split(",") if x.strip()]
243
+ apply_loras(pipe, lora_ids)
244
 
245
+ width, height = int(width), int(height)
 
246
  gen = seed_gen(seed)
247
 
248
  if device == "cuda":
249
  with torch.autocast("cuda"):
250
+ if control_ids:
251
+ img = pipe(
252
+ prompt=prompt, negative_prompt=negative,
253
+ width=width, height=height,
254
+ num_inference_steps=int(steps), guidance_scale=float(cfg),
255
+ controlnet_conditioning_image=cond_images if len(cond_images)>1 else cond_images[0],
256
+ generator=gen
257
+ ).images[0]
258
  else:
259
+ img = pipe(
260
+ prompt=prompt, negative_prompt=negative,
261
+ width=width, height=height,
262
+ num_inference_steps=int(steps), guidance_scale=float(cfg),
263
+ generator=gen
264
+ ).images[0]
265
  else:
266
+ if control_ids:
267
+ img = pipe(
268
+ prompt=prompt, negative_prompt=negative,
269
+ width=width, height=height,
270
+ num_inference_steps=int(steps), guidance_scale=float(cfg),
271
+ controlnet_conditioning_image=cond_images if len(cond_images)>1 else cond_images[0],
272
+ generator=gen
273
+ ).images[0]
274
  else:
275
+ img = pipe(
276
+ prompt=prompt, negative_prompt=negative,
277
+ width=width, height=height,
278
+ num_inference_steps=int(steps), guidance_scale=float(cfg),
279
+ generator=gen
280
+ ).images[0]
281
+
282
+ img = post_process(img, do_up, do_face, do_bg)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  meta = {
284
+ "mode":"txt2img","model":model,"loras":lora_ids,"controlnets":ctrl_selected,
285
  "prompt":prompt,"negative":negative,"size":f"{width}x{height}",
286
  "steps":steps,"cfg":cfg,"scheduler":scheduler_name,"seed":seed,
287
+ "post":{"upscale":do_up,"face_restore":do_face,"remove_bg":do_bg}
288
  }
289
+ return img, to_info(meta)
290
 
291
+ def run_img2img(
292
+ model_id, model_custom, init_image, strength,
293
+ prompt, preset, negative, steps, cfg, width, height, scheduler_name, seed,
294
+ do_up, do_face, do_bg
295
+ ):
296
  if init_image is None: raise gr.Error("โปรดอัปโหลดภาพเริ่มต้น")
297
+ model = (model_custom.strip() or model_id).strip()
298
+ if preset and preset in PRESETS: prompt = prompt + PRESETS[preset]
299
+ if not negative or not negative.strip(): negative = NEG_DEFAULT
300
 
301
+ pipe = StableDiffusionXLImg2ImgPipeline.from_pretrained(model, torch_dtype=dtype, use_safetensors=True).to(device)
 
302
  try:
303
  if device=="cuda": pipe.enable_xformers_memory_efficient_attention()
304
  except Exception: pass
 
306
 
307
  if device=="cuda":
308
  with torch.autocast("cuda"):
309
+ img = pipe(prompt=prompt, negative_prompt=negative,
310
+ image=init_image, strength=float(strength),
311
+ num_inference_steps=int(steps), guidance_scale=float(cfg),
312
+ generator=gen).images[0]
313
  else:
314
+ img = pipe(prompt=prompt, negative_prompt=negative,
315
+ image=init_image, strength=float(strength),
316
+ num_inference_steps=int(steps), guidance_scale=float(cfg),
317
+ generator=gen).images[0]
318
 
319
+ img = post_process(img, do_up, do_face, do_bg)
320
  meta = {"mode":"img2img","model":model,"prompt":prompt,"neg":negative,
321
  "steps":steps,"cfg":cfg,"seed":seed,"strength":strength}
322
+ return img, to_info(meta)
323
 
324
  def expand_canvas_for_outpaint(img: Image.Image, expand_px: int, direction: str) -> Tuple[Image.Image, Image.Image]:
325
  w, h = img.size
 
 
 
 
326
  if direction == "left":
327
+ new = Image.new("RGBA",(w+expand_px,h),(0,0,0,0)); new.paste(img,(expand_px,0))
328
+ mask = Image.new("L",(w+expand_px,h),0); d=ImageDraw.Draw(mask); d.rectangle([0,0,expand_px,h], fill=255)
329
  elif direction == "right":
330
+ new = Image.new("RGBA",(w+expand_px,h),(0,0,0,0)); new.paste(img,(0,0))
331
+ mask = Image.new("L",(w+expand_px,h),0); d=ImageDraw.Draw(mask); d.rectangle([w,0,w+expand_px,h], fill=255)
332
  elif direction == "top":
333
+ new = Image.new("RGBA",(w,h+expand_px),(0,0,0,0)); new.paste(img,(0,expand_px))
334
+ mask = Image.new("L",(w,h+expand_px),0); d=ImageDraw.Draw(mask); d.rectangle([0,0,w,expand_px], fill=255)
335
+ else:
336
+ new = Image.new("RGBA",(w,h+expand_px),(0,0,0,0)); new.paste(img,(0,0))
337
+ mask = Image.new("L",(w,h+expand_px),0); d=ImageDraw.Draw(mask); d.rectangle([0,h,w,h+expand_px], fill=255)
 
338
  return new.convert("RGB"), mask
339
 
340
+ def run_inpaint_outpaint(
341
+ model_id, model_custom, base_image, mask_image, mode, expand_px, expand_dir,
342
+ prompt, preset, negative, steps, cfg, width, height, scheduler_name, seed,
343
+ strength, do_up, do_face, do_bg
344
+ ):
345
  if base_image is None: raise gr.Error("โปรดอัปโหลดภาพฐาน")
346
+ model = (model_custom.strip() or model_id).strip()
347
+ if preset and preset in PRESETS: prompt = prompt + PRESETS[preset]
348
+ if not negative or not negative.strip(): negative = NEG_DEFAULT
349
 
350
+ pipe = StableDiffusionXLInpaintPipeline.from_pretrained(model, torch_dtype=dtype, use_safetensors=True).to(device)
 
351
  try:
352
  if device=="cuda": pipe.enable_xformers_memory_efficient_attention()
353
  except Exception: pass
 
360
  with torch.autocast("cuda"):
361
  img = pipe(prompt=prompt, negative_prompt=negative,
362
  image=base_image, mask_image=mask_image,
363
+ strength=float(strength),
364
+ num_inference_steps=int(steps), guidance_scale=float(cfg),
365
+ generator=gen).images[0]
366
  else:
367
  img = pipe(prompt=prompt, negative_prompt=negative,
368
  image=base_image, mask_image=mask_image,
369
+ strength=float(strength),
370
+ num_inference_steps=int(steps), guidance_scale=float(cfg),
371
+ generator=gen).images[0]
372
 
373
+ img = post_process(img, do_up, do_face, do_bg)
374
  meta = {"mode":mode,"model":model,"prompt":prompt,"steps":steps,"cfg":cfg,"seed":seed}
375
+ return img, to_info(meta)
376
 
377
  # ---------------- UI ----------------
378
  def build_ui():
379
  with gr.Blocks(theme=gr.themes.Soft(), title="Masterpiece SDXL Studio Pro") as demo:
380
  gr.Markdown("# 🖼️ Masterpiece SDXL Studio Pro")
381
+ gr.Markdown("Text2Img • Img2Img • Inpaint/Outpaint • Multi-LoRA • ControlNet • Upscale/FaceRestore/RemoveBG (optional)")
382
 
383
+ # Common controls
384
+ model_dd = gr.Dropdown(choices=[m[0] for m in MODELS], value=MODELS[0][0], label="Model")
385
+ model_custom = gr.Textbox(label="Custom Model ID", placeholder="(ถ้าอยากใช้โมเดลของคุณเอง กรอกที่นี่)")
386
 
387
  preset = gr.Dropdown(choices=list(PRESETS.keys()), value=None, label="Style Preset (optional)")
388
  negative = gr.Textbox(value=NEG_DEFAULT, label="Negative Prompt")
 
392
  width = gr.Slider(512, 1024, 832, step=64, label="Width")
393
  height= gr.Slider(512, 1024, 832, step=64, label="Height")
394
  scheduler = gr.Dropdown(list(SCHEDULERS.keys()), value="DPM-Solver (Karras)", label="Scheduler")
395
+ seed = gr.Number(value=-1, precision=0, label="Seed (-1 = random)")
396
+
397
+ # LoRA & ControlNet
398
+ lora_sel = gr.CheckboxGroup(choices=[f"{rid} — {lbl} ({note})" for rid,lbl,note in LORAS], label="LoRA (เลือกได้หลายตัว)")
399
+ lora_custom = gr.Textbox(label="Custom LoRA IDs (comma separated)")
400
+
401
+ ctrl_sel = gr.CheckboxGroup(choices=[c[1] for c in CONTROLNETS], label="ControlNet ชนิดที่ใช้")
402
+ img_canny = gr.Image(type="pil", label="Canny")
403
+ img_pose = gr.Image(type="pil", label="OpenPose")
404
+ img_depth = gr.Image(type="pil", label="Depth")
405
+ img_softedge = gr.Image(type="pil", label="SoftEdge")
406
+ img_lineart = gr.Image(type="pil", label="Lineart")
407
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  with gr.Row():
409
+ do_up = gr.Checkbox(False, label="Upscale x4 (ถ้ามี)")
410
+ do_face = gr.Checkbox(False, label="Face Restore (ถ้ามี)")
411
+ do_bg = gr.Checkbox(False, label="Remove BG (ถ้ามี)")
412
 
413
  with gr.Tab("Text → Image"):
414
  prompt_txt = gr.Textbox(lines=3, label="Prompt")
 
417
  out_meta_txt = gr.Textbox(label="Metadata", lines=10)
418
 
419
  with gr.Tab("Image → Image"):
420
+ init_img = gr.Image(type="pil", label="Init Image")
421
  strength = gr.Slider(0.1, 1.0, 0.7, 0.05, label="Strength")
422
  prompt_i2i = gr.Textbox(lines=3, label="Prompt")
423
  btn_i2i = gr.Button("🚀 Img2Img")
 
435
  out_img_io = gr.Image(type="pil", label="Result")
436
  out_meta_io = gr.Textbox(label="Metadata", lines=10)
437
 
438
+ # Bindings
 
 
 
439
  btn_txt.click(
440
  fn=run_txt2img,
441
  inputs=[
442
  model_dd, model_custom, prompt_txt, preset, negative,
443
  steps, cfg, width, height, scheduler, seed,
444
+ lora_sel, lora_custom,
445
+ ctrl_sel, img_canny, img_pose, img_depth, img_softedge, img_lineart,
446
+ do_up, do_face, do_bg
 
 
447
  ],
448
  outputs=[out_img_txt, out_meta_txt],
449
  api_name="txt2img"
 
451
 
452
  btn_i2i.click(
453
  fn=run_img2img,
454
+ inputs=[
455
+ model_dd, model_custom, init_img, strength,
456
+ prompt_i2i, preset, negative, steps, cfg, width, height, scheduler, seed,
457
+ do_up, do_face, do_bg
458
+ ],
459
  outputs=[out_img_i2i, out_meta_i2i],
460
  api_name="img2img"
461
  )
462
 
463
  btn_io.click(
464
  fn=run_inpaint_outpaint,
465
+ inputs=[
466
+ model_dd, model_custom, base_img, mask_img, mode_io, expand_px, expand_dir,
467
+ prompt_io, preset, negative, steps, cfg, width, height, scheduler, seed,
468
+ strength, do_up, do_face, do_bg
469
+ ],
470
  outputs=[out_img_io, out_meta_io],
471
  api_name="inpaint_outpaint"
472
  )
473
 
474
+ gr.Markdown("ℹ️ ถ้าโมดูลเสริมหรือบางโมเดลไม่พร้อมใช้งาน ระบบจะข้ามอย่างปลอดภัยและแจ้งเตือนใน Console")
475
 
476
  return demo
477