Nad54 commited on
Commit
0da1dec
·
verified ·
1 Parent(s): 6ab138b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +51 -92
app.py CHANGED
@@ -1,12 +1,12 @@
1
- # app.py — InstantID SDXL + (optionnel) IP-Adapter Style pour rendu 2D
2
- # HF Space ready
3
 
4
  # 0) Environnement AVANT TOUT IMPORT
5
  import os, sys
6
- os.environ["OMP_NUM_THREADS"] = "4" # valeur safe pour libgomp
7
  os.environ.setdefault("HF_HUB_ENABLE_HF_TRANSFER", "1")
8
 
9
- # rendre importable ./instantid/ip_adapter
10
  sys.path.insert(0, os.path.abspath("./instantid"))
11
 
12
  # 1) Imports
@@ -20,21 +20,21 @@ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
20
  DTYPE = torch.float16 if DEVICE == "cuda" else torch.float32
21
 
22
  # 2) Chemins & Hub
23
- ASSETS_REPO = "InstantX/InstantID" # poids InstantID (ControlNet + ip-adapter instantid)
24
  CHECKPOINTS_DIR = "./checkpoints"
25
  CN_LOCAL_DIR = os.path.join(CHECKPOINTS_DIR, "ControlNetModel")
26
  IP_ADAPTER_LOCAL = os.path.join(CHECKPOINTS_DIR, "ip-adapter.bin")
27
 
28
- # --- IP-Adapter Style (SDXL) : h94/IP-Adapter
29
- IP_STYLE_REPO = "h94/IP-Adapter"
30
- IP_STYLE_SUBFOLDER= "sdxl_models"
31
- IP_STYLE_WEIGHT = "ip-adapter_sdxl.bin"
32
- IP_STYLE_LOCAL = os.path.join(CHECKPOINTS_DIR, "ip-adapter_sdxl.bin") # copie locale (utile hors cache HF)
33
 
34
  # 3) Téléchargements sûrs (détecte fichiers vides)
35
  def safe_download(repo, filename, local_dir, min_bytes, label, subfolder=None):
36
  os.makedirs(local_dir, exist_ok=True)
37
- local_path = os.path.join(local_dir, filename if subfolder is None else os.path.basename(filename))
38
  if os.path.exists(local_path) and os.path.getsize(local_path) < min_bytes:
39
  print(f"⚠️ {label} corrompu ({os.path.getsize(local_path)} bytes) → suppression")
40
  try: os.remove(local_path)
@@ -57,35 +57,34 @@ def safe_download(repo, filename, local_dir, min_bytes, label, subfolder=None):
57
  def ensure_assets_or_download():
58
  os.makedirs(CHECKPOINTS_DIR, exist_ok=True)
59
  os.makedirs(CN_LOCAL_DIR, exist_ok=True)
60
- # InstantID (IdentityNet + ip-adapter instantid)
61
  safe_download(ASSETS_REPO, "ControlNetModel/config.json", CHECKPOINTS_DIR, 1_000, "IdentityNet config")
62
  safe_download(ASSETS_REPO, "ControlNetModel/diffusion_pytorch_model.safetensors", CHECKPOINTS_DIR, 100_000_000, "IdentityNet weights")
63
- safe_download(ASSETS_REPO, "ip-adapter.bin", CHECKPOINTS_DIR, 100_000_000, "ip-adapter (InstantID)")
64
  # IP-Adapter Style (SDXL)
65
  p = safe_download(IP_STYLE_REPO, IP_STYLE_WEIGHT, CHECKPOINTS_DIR, 20_000_000, "IP-Adapter Style (SDXL)", subfolder=IP_STYLE_SUBFOLDER)
66
- # copieur simple pour avoir une voie locale directe si besoin
67
  try:
68
  if not os.path.exists(IP_STYLE_LOCAL):
69
  import shutil; shutil.copy2(p, IP_STYLE_LOCAL)
70
  except Exception as e:
71
  print(f"ℹ️ Copie locale IP-Adapter Style ignorée: {e}")
72
 
73
- # 4) Import dynamique de la pipeline InstantID (fichier texte local)
74
  def import_pipeline_or_fail():
75
  candidates = [
76
- "./instantid/pipeline_stable_diffusion_xl_instantid.py",
77
  "./instantid/pipeline_stable_diffusion_xl_instantid_full.py",
 
78
  ]
79
  pipeline_file = next((p for p in candidates if os.path.exists(p)), None)
80
  if pipeline_file is None:
81
- raise RuntimeError("❌ Fichier pipeline manquant : place l’un de ces fichiers dans ./instantid/")
82
-
83
  if os.path.getsize(pipeline_file) < 1024:
84
- raise RuntimeError("❌ Fichier pipeline trop petit (vide ?). Colle le contenu depuis GitHub InstantID.")
85
 
86
  spec = importlib.util.spec_from_file_location("instantid_pipeline", pipeline_file)
87
  mod = importlib.util.module_from_spec(spec)
88
  spec.loader.exec_module(mod)
 
89
  for name, obj in vars(mod).items():
90
  if isinstance(obj, type) and "InstantID" in name and hasattr(obj, "from_pretrained"):
91
  print(f"✅ Pipeline trouvée : {name}")
@@ -93,7 +92,7 @@ def import_pipeline_or_fail():
93
  avail = [n for n, o in vars(mod).items() if isinstance(o, type)]
94
  raise RuntimeError("❌ Aucune classe pipeline InstantID trouvée. Classes dispo: " + ", ".join(avail))
95
 
96
- # 5) draw_kps local (remplace la dépendance au draw_kps du fichier)
97
  def draw_kps_local(img_pil, kps):
98
  w, h = img_pil.size
99
  out = Image.new("RGB", (w, h), "white")
@@ -103,8 +102,9 @@ def draw_kps_local(img_pil, kps):
103
  d.ellipse((x - r, y - r, x + r, y + r), fill="black")
104
  return out
105
 
106
- # 6) Chargement pipeline (ControlNet en OBJET, pas liste)
107
  load_logs = []
 
108
  try:
109
  SDXLInstantID = import_pipeline_or_fail()
110
  ensure_assets_or_download()
@@ -116,19 +116,16 @@ try:
116
 
117
  pipe = SDXLInstantID.from_pretrained(
118
  BASE_MODEL,
119
- controlnet=controlnet_identitynet, # objet unique
120
  torch_dtype=DTYPE,
121
  safety_checker=None,
122
  feature_extractor=None,
123
  ).to(DEVICE)
124
 
125
- # 6.1) Charger l'IP-Adapter InstantID (pour l'identité)
126
- # (la pipeline InstantID fournit cette méthode utilitaire)
127
  pipe.load_ip_adapter_instantid(IP_ADAPTER_LOCAL)
128
 
129
- # 6.2) (Optionnel) Charger un IP-Adapter Style SDXL (h94/IP-Adapter)
130
- # On le nomme "style" pour l'adresser séparément dans set_ip_adapter_scale().
131
- # Suivant diffusers, adapter_name peut ne pas exister: gérer fallback.
132
  try:
133
  pipe.load_ip_adapter(
134
  IP_STYLE_REPO,
@@ -139,7 +136,7 @@ try:
139
  load_logs.append("✅ IP-Adapter Style (SDXL) chargé (adapter_name='style').")
140
  HAS_STYLE_ADAPTER = True
141
  except Exception as e:
142
- load_logs.append(f"ℹ️ Impossible de charger IP-Adapter Style via load_ip_adapter: {e}")
143
  HAS_STYLE_ADAPTER = False
144
 
145
  if DEVICE == "cuda":
@@ -150,12 +147,11 @@ try:
150
  except Exception:
151
  load_logs += ["❌ ERREUR au chargement:", traceback.format_exc()]
152
  pipe = None
153
- HAS_STYLE_ADAPTER = False
154
 
155
  if pipe is None:
156
  raise RuntimeError("Échec de chargement du pipeline.\n" + "\n".join(load_logs))
157
 
158
- # 7) InsightFace (robuste : essaie antelopev2 puis buffalo_l)
159
  from insightface.app import FaceAnalysis
160
  def load_face_analyser():
161
  errors = []
@@ -172,32 +168,23 @@ def load_face_analyser():
172
 
173
  fa = load_face_analyser()
174
 
175
- def extract_kps_image(pil_img):
 
176
  import numpy as np, cv2
177
  img_cv2 = cv2.cvtColor(np.array(pil_img.convert("RGB")), cv2.COLOR_RGB2BGR)
178
  faces = fa.get(img_cv2)
179
  if not faces:
180
  raise ValueError("Aucun visage détecté dans la photo.")
181
  face = faces[-1]
182
- return draw_kps_local(pil_img, face["kps"])
183
-
184
- # --- util: normaliser image_embeds quelle que soit la forme renvoyée
185
- def _normalize_image_embeds(image_embeds):
186
- import numpy as np
187
- if isinstance(image_embeds, dict):
188
- for k in ("image_embeds", "prompt_image_embeds", "pooled_prompt_embeds"):
189
- if k in image_embeds and image_embeds[k] is not None:
190
- image_embeds = image_embeds[k]; break
191
- if isinstance(image_embeds, (tuple, list)):
192
- if len(image_embeds) == 0: return None
193
- image_embeds = image_embeds[0] if image_embeds[0] is not None else image_embeds[-1]
194
- if isinstance(image_embeds, np.ndarray):
195
- image_embeds = torch.from_numpy(image_embeds)
196
- if isinstance(image_embeds, torch.Tensor):
197
- image_embeds = image_embeds.to(device=DEVICE, dtype=DTYPE if DEVICE == "cuda" else torch.float32)
198
- return image_embeds
199
-
200
- # 8) Génération
201
  def generate(face_image, style_image, prompt, negative_prompt,
202
  identity_strength, adapter_strength, style_strength,
203
  steps, cfg, width, height, seed):
@@ -207,51 +194,24 @@ def generate(face_image, style_image, prompt, negative_prompt,
207
 
208
  gen = None if seed is None or int(seed) < 0 else torch.Generator(device=DEVICE).manual_seed(int(seed))
209
 
210
- # 2) préparer visage (carré 512) + landmarks
211
  face = ImageOps.exif_transpose(face_image).convert("RGB")
212
  ms = min(face.size); x = (face.width - ms) // 2; y = (face.height - ms) // 2
213
  face_sq = face.crop((x, y, x + ms, y + ms)).resize((512, 512), Image.Resampling.LANCZOS)
214
 
215
- kps_img = extract_kps_image(face_sq) # -> image landmarks (noir sur blanc)
 
216
 
217
- # 3) règles IP-Adapter scales
218
- # - adapter_strength = détails anime d’InstantID (son IP-adapter interne)
219
- # - style_strength = force de l’IP-Adapter Style (si présent + image fournie)
220
- # On supporte dict (multi-adapters nommés) et fallback simple.
221
- set_scale_ok = False
222
  try:
223
  if HAS_STYLE_ADAPTER and style_image is not None:
224
  pipe.set_ip_adapter_scale({"instantid": float(adapter_strength), "style": float(style_strength)})
225
  else:
226
- # pas de style → ne régler que l’adapter InstantID
227
  pipe.set_ip_adapter_scale(float(adapter_strength))
228
- set_scale_ok = True
229
  except Exception as e:
230
- print(f"ℹ️ set_ip_adapter_scale fallback: {e}")
231
- try:
232
- pipe.set_ip_adapter_scale(float(adapter_strength))
233
- set_scale_ok = True
234
- except Exception as e2:
235
- print(f"⚠️ set_ip_adapter_scale impossible: {e2}")
236
-
237
- # 4) embeddings InstantID visage
238
- image_embeds = None
239
- for m in ("get_image_embeds", "prepare_ip_adapter_image_embeds", "encode_image", "encode_ip_image"):
240
- fn = getattr(pipe, m, None)
241
- if callable(fn):
242
- try:
243
- image_embeds = fn(face_sq); break
244
- except Exception as e:
245
- print(f"⚠️ {m} a échoué: {e}")
246
- image_embeds = _normalize_image_embeds(image_embeds)
247
- if image_embeds is None:
248
- return None, (
249
- "La pipeline InstantID SDXL requiert des `image_embeds`, mais aucune méthode compatible n'a été trouvée "
250
- "(get_image_embeds / prepare_ip_adapter_image_embeds / encode_image / encode_ip_image). "
251
- "Vérifie que le fichier `pipeline_stable_diffusion_xl_instantid.py` est bien la version SDXL officielle."
252
- ), "\n".join(load_logs)
253
-
254
- # 5) multi-ControlNet compat
255
  cn = getattr(pipe, "controlnet", None)
256
  if isinstance(cn, (list, tuple)):
257
  n_cn = len(cn)
@@ -263,12 +223,12 @@ def generate(face_image, style_image, prompt, negative_prompt,
263
  scale_val = float(identity_strength)
264
  scale_arg = [scale_val] * n_cn if n_cn > 1 else ([scale_val] if isinstance(cn, (list, tuple)) else scale_val)
265
 
266
- # 6) préparer kwargs IP-Adapter Style si utilisé
267
  gen_kwargs = dict(
268
  prompt=prompt.strip(),
269
  negative_prompt=(negative_prompt or "").strip(),
270
- image=image_arg,
271
- image_embeds=image_embeds,
272
  controlnet_conditioning_scale=scale_arg,
273
  num_inference_steps=int(steps),
274
  guidance_scale=float(cfg),
@@ -276,14 +236,14 @@ def generate(face_image, style_image, prompt, negative_prompt,
276
  height=int(height),
277
  generator=gen,
278
  )
279
- # diffusers modernes: ip_adapter_image=style_image
 
280
  if HAS_STYLE_ADAPTER and style_image is not None:
281
  try:
282
  gen_kwargs["ip_adapter_image"] = ImageOps.exif_transpose(style_image).convert("RGB")
283
  except Exception as e:
284
- print(f"ℹ️ ip_adapter_image conversion ignorée: {e}")
285
 
286
- # 7) appel pipeline
287
  images = pipe(**gen_kwargs).images
288
  return images[0], "", "\n".join(load_logs)
289
 
@@ -305,7 +265,7 @@ EX_NEG = (
305
  )
306
 
307
  with gr.Blocks(css="footer{display:none !important}") as demo:
308
- gr.Markdown("# 🏴‍☠️ One Piece – InstantID SDXL + IP-Adapter Style (2D total)")
309
 
310
  with gr.Row():
311
  with gr.Column():
@@ -318,7 +278,6 @@ with gr.Blocks(css="footer{display:none !important}") as demo:
318
  identity_strength = gr.Slider(0.2, 1.5, 0.95, 0.05, label="Fidélité visage (IdentityNet)")
319
  adapter_strength = gr.Slider(0.1, 1.5, 0.85, 0.05, label="Détails anime (InstantID IP-Adapter)")
320
 
321
- # Nouveau : force style (IP-Adapter Style)
322
  style_strength = gr.Slider(0.1, 1.5, 0.95, 0.05, label="Force style (IP-Adapter Style)")
323
 
324
  steps = gr.Slider(10, 60, 30, 1, label="Steps")
 
1
+ # app.py — InstantID SDXL + (optionnel) IP-Adapter Style (2D total)
2
+ # Hugging Face Space – prêt à déployer
3
 
4
  # 0) Environnement AVANT TOUT IMPORT
5
  import os, sys
6
+ os.environ["OMP_NUM_THREADS"] = "4" # safe pour libgomp
7
  os.environ.setdefault("HF_HUB_ENABLE_HF_TRANSFER", "1")
8
 
9
+ # rendre importable ./instantid (où se trouve pipeline_stable_diffusion_xl_instantid_full.py)
10
  sys.path.insert(0, os.path.abspath("./instantid"))
11
 
12
  # 1) Imports
 
20
  DTYPE = torch.float16 if DEVICE == "cuda" else torch.float32
21
 
22
  # 2) Chemins & Hub
23
+ ASSETS_REPO = "InstantX/InstantID" # poids InstantID: IdentityNet + ip-adapter instantid
24
  CHECKPOINTS_DIR = "./checkpoints"
25
  CN_LOCAL_DIR = os.path.join(CHECKPOINTS_DIR, "ControlNetModel")
26
  IP_ADAPTER_LOCAL = os.path.join(CHECKPOINTS_DIR, "ip-adapter.bin")
27
 
28
+ # IP-Adapter Style (SDXL) pour forcer le rendu 2D
29
+ IP_STYLE_REPO = "h94/IP-Adapter"
30
+ IP_STYLE_SUBFOLDER = "sdxl_models"
31
+ IP_STYLE_WEIGHT = "ip-adapter_sdxl.bin"
32
+ IP_STYLE_LOCAL = os.path.join(CHECKPOINTS_DIR, "ip-adapter_sdxl.bin")
33
 
34
  # 3) Téléchargements sûrs (détecte fichiers vides)
35
  def safe_download(repo, filename, local_dir, min_bytes, label, subfolder=None):
36
  os.makedirs(local_dir, exist_ok=True)
37
+ local_path = os.path.join(local_dir, os.path.basename(filename))
38
  if os.path.exists(local_path) and os.path.getsize(local_path) < min_bytes:
39
  print(f"⚠️ {label} corrompu ({os.path.getsize(local_path)} bytes) → suppression")
40
  try: os.remove(local_path)
 
57
  def ensure_assets_or_download():
58
  os.makedirs(CHECKPOINTS_DIR, exist_ok=True)
59
  os.makedirs(CN_LOCAL_DIR, exist_ok=True)
60
+ # IdentityNet (ControlNet) + ip-adapter (InstantID)
61
  safe_download(ASSETS_REPO, "ControlNetModel/config.json", CHECKPOINTS_DIR, 1_000, "IdentityNet config")
62
  safe_download(ASSETS_REPO, "ControlNetModel/diffusion_pytorch_model.safetensors", CHECKPOINTS_DIR, 100_000_000, "IdentityNet weights")
63
+ safe_download(ASSETS_REPO, "ip-adapter.bin", CHECKPOINTS_DIR, 100_000_000, "IP-Adapter InstantID")
64
  # IP-Adapter Style (SDXL)
65
  p = safe_download(IP_STYLE_REPO, IP_STYLE_WEIGHT, CHECKPOINTS_DIR, 20_000_000, "IP-Adapter Style (SDXL)", subfolder=IP_STYLE_SUBFOLDER)
 
66
  try:
67
  if not os.path.exists(IP_STYLE_LOCAL):
68
  import shutil; shutil.copy2(p, IP_STYLE_LOCAL)
69
  except Exception as e:
70
  print(f"ℹ️ Copie locale IP-Adapter Style ignorée: {e}")
71
 
72
+ # 4) Import dynamique de la pipeline InstantID SDXL (officielle)
73
  def import_pipeline_or_fail():
74
  candidates = [
 
75
  "./instantid/pipeline_stable_diffusion_xl_instantid_full.py",
76
+ "./instantid/pipeline_stable_diffusion_xl_instantid.py",
77
  ]
78
  pipeline_file = next((p for p in candidates if os.path.exists(p)), None)
79
  if pipeline_file is None:
80
+ raise RuntimeError("❌ Fichier pipeline manquant.\nPlace `pipeline_stable_diffusion_xl_instantid_full.py` dans ./instantid/")
 
81
  if os.path.getsize(pipeline_file) < 1024:
82
+ raise RuntimeError("❌ Fichier pipeline trop petit (vide ?). Colle la version SDXL officielle.")
83
 
84
  spec = importlib.util.spec_from_file_location("instantid_pipeline", pipeline_file)
85
  mod = importlib.util.module_from_spec(spec)
86
  spec.loader.exec_module(mod)
87
+ # Chercher la classe SDXL officielle
88
  for name, obj in vars(mod).items():
89
  if isinstance(obj, type) and "InstantID" in name and hasattr(obj, "from_pretrained"):
90
  print(f"✅ Pipeline trouvée : {name}")
 
92
  avail = [n for n, o in vars(mod).items() if isinstance(o, type)]
93
  raise RuntimeError("❌ Aucune classe pipeline InstantID trouvée. Classes dispo: " + ", ".join(avail))
94
 
95
+ # 5) draw_kps local (remplace la dépendance draw_kps du repo)
96
  def draw_kps_local(img_pil, kps):
97
  w, h = img_pil.size
98
  out = Image.new("RGB", (w, h), "white")
 
102
  d.ellipse((x - r, y - r, x + r, y + r), fill="black")
103
  return out
104
 
105
+ # 6) Chargement pipeline (ControlNet = objet unique)
106
  load_logs = []
107
+ HAS_STYLE_ADAPTER = False
108
  try:
109
  SDXLInstantID = import_pipeline_or_fail()
110
  ensure_assets_or_download()
 
116
 
117
  pipe = SDXLInstantID.from_pretrained(
118
  BASE_MODEL,
119
+ controlnet=controlnet_identitynet, # objet unique
120
  torch_dtype=DTYPE,
121
  safety_checker=None,
122
  feature_extractor=None,
123
  ).to(DEVICE)
124
 
125
+ # Charger lIP-Adapter InstantID (identité)
 
126
  pipe.load_ip_adapter_instantid(IP_ADAPTER_LOCAL)
127
 
128
+ # Charger (optionnel) un IP-Adapter Style SDXL, nommé "style"
 
 
129
  try:
130
  pipe.load_ip_adapter(
131
  IP_STYLE_REPO,
 
136
  load_logs.append("✅ IP-Adapter Style (SDXL) chargé (adapter_name='style').")
137
  HAS_STYLE_ADAPTER = True
138
  except Exception as e:
139
+ load_logs.append(f"ℹ️ IP-Adapter Style non chargé: {e}")
140
  HAS_STYLE_ADAPTER = False
141
 
142
  if DEVICE == "cuda":
 
147
  except Exception:
148
  load_logs += ["❌ ERREUR au chargement:", traceback.format_exc()]
149
  pipe = None
 
150
 
151
  if pipe is None:
152
  raise RuntimeError("Échec de chargement du pipeline.\n" + "\n".join(load_logs))
153
 
154
+ # 7) InsightFace (robuste : antelopev2 buffalo_l)
155
  from insightface.app import FaceAnalysis
156
  def load_face_analyser():
157
  errors = []
 
168
 
169
  fa = load_face_analyser()
170
 
171
+ # — util pour extraire embedding visage + landmarks (kps) depuis la photo
172
+ def extract_face_embed_and_kps(pil_img):
173
  import numpy as np, cv2
174
  img_cv2 = cv2.cvtColor(np.array(pil_img.convert("RGB")), cv2.COLOR_RGB2BGR)
175
  faces = fa.get(img_cv2)
176
  if not faces:
177
  raise ValueError("Aucun visage détecté dans la photo.")
178
  face = faces[-1]
179
+ face_emb = face["embedding"] # <— Embedding InsightFace attendu par la pipeline SDXL officielle
180
+ kps_img = draw_kps_local(pil_img, face["kps"])
181
+ # Convertir en torch tensor si besoin (la pipeline accepte souvent ndarray directement)
182
+ if isinstance(face_emb, (list, tuple)):
183
+ import numpy as np
184
+ face_emb = np.array(face_emb)
185
+ return face_emb, kps_img
186
+
187
+ # 8) Génération (Option A : on passe l’embedding InsightFace -> image_embeds)
 
 
 
 
 
 
 
 
 
 
188
  def generate(face_image, style_image, prompt, negative_prompt,
189
  identity_strength, adapter_strength, style_strength,
190
  steps, cfg, width, height, seed):
 
194
 
195
  gen = None if seed is None or int(seed) < 0 else torch.Generator(device=DEVICE).manual_seed(int(seed))
196
 
197
+ # Préparer visage carré (512) pour détection consistante
198
  face = ImageOps.exif_transpose(face_image).convert("RGB")
199
  ms = min(face.size); x = (face.width - ms) // 2; y = (face.height - ms) // 2
200
  face_sq = face.crop((x, y, x + ms, y + ms)).resize((512, 512), Image.Resampling.LANCZOS)
201
 
202
+ # Embedding InsightFace + landmarks (kps)
203
+ face_emb, kps_img = extract_face_embed_and_kps(face_sq)
204
 
205
+ # Régler l’échelle des IP-Adapters (identité & style)
 
 
 
 
206
  try:
207
  if HAS_STYLE_ADAPTER and style_image is not None:
208
  pipe.set_ip_adapter_scale({"instantid": float(adapter_strength), "style": float(style_strength)})
209
  else:
 
210
  pipe.set_ip_adapter_scale(float(adapter_strength))
 
211
  except Exception as e:
212
+ print(f"ℹ️ set_ip_adapter_scale ignoré: {e}")
213
+
214
+ # Compat multi-ControlNet
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  cn = getattr(pipe, "controlnet", None)
216
  if isinstance(cn, (list, tuple)):
217
  n_cn = len(cn)
 
223
  scale_val = float(identity_strength)
224
  scale_arg = [scale_val] * n_cn if n_cn > 1 else ([scale_val] if isinstance(cn, (list, tuple)) else scale_val)
225
 
226
+ # Préparer kwargs (NOTE: image_embeds = face_emb)
227
  gen_kwargs = dict(
228
  prompt=prompt.strip(),
229
  negative_prompt=(negative_prompt or "").strip(),
230
+ image=image_arg, # IdentityNet (landmarks)
231
+ image_embeds=face_emb, # <— embedding InsightFace
232
  controlnet_conditioning_scale=scale_arg,
233
  num_inference_steps=int(steps),
234
  guidance_scale=float(cfg),
 
236
  height=int(height),
237
  generator=gen,
238
  )
239
+
240
+ # Fournir l’image de style à l’IP-Adapter Style si dispo
241
  if HAS_STYLE_ADAPTER and style_image is not None:
242
  try:
243
  gen_kwargs["ip_adapter_image"] = ImageOps.exif_transpose(style_image).convert("RGB")
244
  except Exception as e:
245
+ print(f"ℹ️ ip_adapter_image ignoré: {e}")
246
 
 
247
  images = pipe(**gen_kwargs).images
248
  return images[0], "", "\n".join(load_logs)
249
 
 
265
  )
266
 
267
  with gr.Blocks(css="footer{display:none !important}") as demo:
268
+ gr.Markdown("# 🏴‍☠️ One Piece – InstantID SDXL + IP-Adapter Style (2D total) — Option A officielle")
269
 
270
  with gr.Row():
271
  with gr.Column():
 
278
  identity_strength = gr.Slider(0.2, 1.5, 0.95, 0.05, label="Fidélité visage (IdentityNet)")
279
  adapter_strength = gr.Slider(0.1, 1.5, 0.85, 0.05, label="Détails anime (InstantID IP-Adapter)")
280
 
 
281
  style_strength = gr.Slider(0.1, 1.5, 0.95, 0.05, label="Force style (IP-Adapter Style)")
282
 
283
  steps = gr.Slider(10, 60, 30, 1, label="Steps")