Astridkraft commited on
Commit
1fe4554
·
verified ·
1 Parent(s): 0c4f4cc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +351 -277
app.py CHANGED
@@ -4,41 +4,12 @@ from diffusers import StableDiffusionInpaintPipeline, AutoencoderKL
4
  from diffusers import DPMSolverMultistepScheduler, PNDMScheduler
5
  from controlnet_module import controlnet_processor
6
  import torch
7
- from PIL import Image, ImageDraw, ImageFont
8
  import time
9
  import os
10
  import tempfile
11
  import random
12
- import re
13
-
14
-
15
- # === FACE-FIX IMPORT - MIT DETAILLIERTEM DEBUGGING ===
16
- try:
17
- print("Versuch 1: Importiere controlnet_facefix...")
18
- from controlnet_facefix import apply_facefix
19
- FACEFIX_AVAILABLE = True
20
- print("✅ Face-Fix erfolgreich geladen")
21
- except ImportError as e1:
22
- print(f"❌ ImportError: {e1}")
23
- try:
24
- print("Versuch 2: Import mit sys.path...")
25
- import sys
26
- sys.path.append(".")
27
- from controlnet_facefix import apply_facefix
28
- FACEFIX_AVAILABLE = True
29
- print("✅ Face-Fix erfolgreich geladen (mit sys.path)")
30
- except Exception as e2:
31
- print(f"❌ Endgültiger Fehler: {e2}")
32
- FACEFIX_AVAILABLE = False
33
- import traceback
34
- traceback.print_exc()
35
- except Exception as e:
36
- print(f"❌ Anderer Fehler: {e}")
37
- FACEFIX_AVAILABLE = False
38
- import traceback
39
- traceback.print_exc()
40
-
41
-
42
 
43
  # === OPTIMIERTE EINSTELLUNGEN ===
44
  device = "cuda" if torch.cuda.is_available() else "cpu"
@@ -50,7 +21,7 @@ print(f"Running on: {device}")
50
  # === MODELLKONFIGURATION (NUR 2 MODELLE) ===
51
  MODEL_CONFIGS = {
52
  "runwayml/stable-diffusion-v1-5": {
53
- "name": "Stable Diffusion 1.5 (Universal)",
54
  "description": "Universal model, good all-rounder, reliable results",
55
  "requires_vae": False,
56
  "recommended_steps": 35,
@@ -58,7 +29,7 @@ MODEL_CONFIGS = {
58
  "supports_fp16": True
59
  },
60
  "SG161222/Realistic_Vision_V6.0_B1_noVAE": {
61
- "name": "Realistic Vision V6.0 (Portraits)",
62
  "description": "Best for photorealistic faces, skin details, human portraits",
63
  "requires_vae": True,
64
  "vae_model": "stabilityai/sd-vae-ft-mse",
@@ -68,88 +39,106 @@ MODEL_CONFIGS = {
68
  }
69
  }
70
 
 
71
  SAFETENSORS_MODELS = ["runwayml/stable-diffusion-v1-5"]
 
 
72
  current_model_id = "runwayml/stable-diffusion-v1-5"
73
 
74
  # === AUTOMATISCHE NEGATIVE PROMPT GENERIERUNG ===
75
  def auto_negative_prompt(positive_prompt):
 
76
  p = positive_prompt.lower()
77
  negatives = []
78
 
 
79
  if any(w in p for w in [
80
- "person", "man", "woman", "face", "portrait", "team", "employee",
81
- "people", "crowd", "character", "figure", "human", "child", "baby",
82
- "girl", "boy", "lady", "gentleman", "fairy", "elf", "dwarf", "santa claus",
83
- "mermaid", "angel", "demon", "witch", "wizard", "creature", "being",
84
- "model", "actor", "actress", "celebrity", "avatar", "group"
85
- ]):
86
  negatives.append(
87
  "blurry face, lowres face, deformed pupils, bad anatomy, malformed hands, extra fingers, uneven eyes, distorted face, "
88
  "unrealistic skin, mutated, ugly, disfigured, poorly drawn face, "
89
  "missing limbs, extra limbs, fused fingers, too many fingers, bad teeth, "
90
- "mutated hands, long neck, extra wings, multiple wings, grainy face, noisy face, "
91
  "compression artifacts, rendering artifacts, digital artifacts, overprocessed face, oversmoothed face "
92
  )
93
-
 
94
  if any(w in p for w in ["office", "business", "team", "meeting", "corporate", "company", "workplace"]):
95
- negatives.append("overexposed, oversaturated, harsh lighting, watermark, text, logo, brand")
 
 
96
 
 
97
  if any(w in p for w in ["product", "packshot", "mockup", "render", "3d", "cgi", "packaging"]):
98
- negatives.append("plastic texture, noisy, overly reflective surfaces, watermark, text, low poly")
 
 
99
 
 
100
  if any(w in p for w in ["landscape", "nature", "mountain", "forest", "outdoor", "beach", "sky"]):
101
- negatives.append("blurry, oversaturated, unnatural colors, distorted horizon, floating objects")
 
 
102
 
 
103
  if any(w in p for w in ["logo", "symbol", "icon", "typography", "badge", "emblem"]):
104
- negatives.append("watermark, signature, username, text, writing, scribble, messy")
 
 
105
 
 
106
  if any(w in p for w in ["building", "architecture", "house", "interior", "room", "facade"]):
107
- negatives.append("deformed, distorted perspective, floating objects, collapsing structure")
 
 
108
 
 
109
  base_negatives = "low quality, worst quality, blurry, jpeg artifacts, ugly, deformed"
110
 
111
- return base_negatives + ", " + ", ".join(negatives) if negatives else base_negatives
112
-
113
-
114
- def is_person_prompt(prompt: str) -> bool:
115
- p = prompt.lower()
116
- print(f"DEBUG: Prüfe '{p}' auf Personen...")
117
-
118
- # EINFACHE Version die garantiert funktioniert:
119
- keywords = ["fairy", "person", "man", "woman", "face", "portrait"]
120
-
121
- for keyword in keywords:
122
- if keyword in p: # Einfach 'in' ohne Leerzeichen
123
- print(f"✅ Person erkannt durch '{keyword}'")
124
- return True
125
-
126
- print(f"❌ Keine Person erkannt")
127
- return False
128
-
129
-
130
 
131
  # === GESICHTSMASKEN-FUNKTIONEN ===
132
  def create_face_mask(image, bbox_coords, face_preserve):
133
- mask = Image.new("L", image.size, 0)
 
 
134
  if bbox_coords and all(coord is not None for coord in bbox_coords):
135
  x1, y1, x2, y2 = bbox_coords
136
  draw = ImageDraw.Draw(mask)
 
137
  if face_preserve:
138
- draw.rectangle([0, 0, image.size[0], image.size[1]], fill=255)
139
- draw.rectangle([x1, y1, x2, y2], fill=0)
 
 
140
  else:
141
- draw.rectangle([x1, y1, x2, y2], fill=255)
 
 
 
142
  return mask
143
 
144
  def auto_detect_face_area(image):
 
145
  width, height = image.size
 
146
  face_size = min(width, height) * 0.4
 
147
  x1 = (width - face_size) / 2
148
- y1 = (height - face_size) / 4
149
  x2 = x1 + face_size
150
- y2 = y1 + face_size * 1.2
 
151
  x1, y1 = max(0, int(x1)), max(0, int(y1))
152
  x2, y2 = min(width, int(x2)), min(height, int(y2))
 
153
  return [x1, y1, x2, y2]
154
 
155
  # === PIPELINES ===
@@ -158,277 +147,375 @@ current_pipe_model_id = None
158
  pipe_img2img = None
159
 
160
  def load_txt2img(model_id):
 
161
  global pipe_txt2img, current_pipe_model_id
 
 
162
  if pipe_txt2img is not None and current_pipe_model_id == model_id:
 
163
  return pipe_txt2img
164
 
165
- print(f"Lade Modell: {model_id}")
 
166
  config = MODEL_CONFIGS.get(model_id, MODEL_CONFIGS["runwayml/stable-diffusion-v1-5"])
 
 
167
 
168
  try:
 
169
  vae = None
170
  if config.get("requires_vae", False):
171
- vae = AutoencoderKL.from_pretrained(config["vae_model"], torch_dtype=torch_dtype).to(device)
 
 
 
 
 
 
 
 
 
 
172
 
 
173
  model_params = {
174
  "torch_dtype": torch_dtype,
175
  "safety_checker": None,
176
  "requires_safety_checker": False,
 
 
177
  }
178
 
 
179
  if model_id in SAFETENSORS_MODELS:
180
  model_params["use_safetensors"] = True
 
 
 
 
181
 
 
182
  if config.get("supports_fp16", False) and torch_dtype == torch.float16:
183
  model_params["variant"] = "fp16"
 
 
 
184
 
 
185
  if vae is not None:
186
  model_params["vae"] = vae
187
 
188
- pipe_txt2img = StableDiffusionPipeline.from_pretrained(model_id, **model_params).to(device)
189
- pipe_txt2img.enable_attention_slicing()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
 
 
191
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  pipe_txt2img.scheduler = DPMSolverMultistepScheduler.from_config(
193
- pipe_txt2img.scheduler.config,
194
  use_karras_sigmas=True,
195
  algorithm_type="sde-dpmsolver++"
196
  )
197
- except:
198
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
  current_pipe_model_id = model_id
 
 
 
 
 
201
  return pipe_txt2img
202
 
203
  except Exception as e:
204
- print(f"Fehler beim Laden, Fallback auf SD 1.5: {e}")
205
- pipe_txt2img = StableDiffusionPipeline.from_pretrained(
206
- "runwayml/stable-diffusion-v1-5", torch_dtype=torch_dtype, use_safetensors=True
207
- ).to(device)
208
- pipe_txt2img.enable_attention_slicing()
209
- current_pipe_model_id = "runwayml/stable-diffusion-v1-5"
210
- return pipe_txt2img
 
 
 
 
 
 
 
 
 
 
 
 
 
211
 
212
  def load_img2img():
213
  global pipe_img2img
214
  if pipe_img2img is None:
215
- pipe_img2img = StableDiffusionInpaintPipeline.from_pretrained(
216
- "runwayml/stable-diffusion-inpainting", torch_dtype=torch_dtype, safety_checker=None
217
- ).to(device)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  pipe_img2img.enable_attention_slicing()
219
  pipe_img2img.enable_vae_tiling()
 
 
 
220
  return pipe_img2img
221
 
222
- # === CALLBACKS ===
223
  class TextToImageProgressCallback:
224
  def __init__(self, progress, total_steps):
225
  self.progress = progress
226
  self.total_steps = total_steps
 
 
227
  def __call__(self, pipe, step, timestep, callback_kwargs):
228
- self.progress(step / self.total_steps, desc="Generierung läuft...")
 
 
229
  return callback_kwargs
230
 
231
  class ImageToImageProgressCallback:
232
  def __init__(self, progress, total_steps, strength):
233
  self.progress = progress
234
  self.total_steps = total_steps
 
235
  self.strength = strength
236
- self.actual_steps = None
 
237
  def __call__(self, pipe, step, timestep, callback_kwargs):
238
- if self.actual_steps is None:
239
- self.actual_steps = int(self.total_steps * self.strength)
240
- progress_val = step / self.actual_steps
241
- self.progress(progress_val, desc="Generierung läuft...")
 
 
 
 
 
 
 
 
242
  return callback_kwargs
243
 
244
- # === BILDVERARBEITUNGS-FUNKTIONEN FÜR BILD-TO-BILD TAB ===
245
- def process_image_upload(image):
246
- """Verarbeitet den Bild-Upload und aktualisiert die Koordinaten und Vorschau"""
247
  if image is None:
248
- return None, 100, 100, 300, 300
 
 
 
249
 
250
- # Automatische Gesichtserkennung
251
- bbox = auto_detect_face_area(image)
 
 
 
 
252
 
253
- # Live-Vorschau erstellen
254
- preview = update_live_preview(image, bbox[0], bbox[1], bbox[2], bbox[3], True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
 
256
- return preview, bbox[0], bbox[1], bbox[2], bbox[3]
257
 
258
- def update_live_preview(image, x1, y1, x2, y2, face_preserve):
259
- """Erstellt eine Live-Vorschau mit farbigem Rahmen basierend auf dem Modus"""
260
  if image is None:
261
  return None
262
 
263
- # Kopie des Bildes für Vorschau
264
- preview = image.copy()
265
-
266
- # Zeichne Rahmen basierend auf Modus
267
- draw = ImageDraw.Draw(preview)
268
 
269
- # Rahmenfarbe basierend auf Modus
270
- if face_preserve:
271
- # 🟢 GRÜN: Alles außerhalb des Rahmens wird verändert
272
- outline_color = "green"
273
- else:
274
- # 🔴 ROT: Nur innerhalb des Rahmens wird verändert
275
- outline_color = "red"
 
 
 
276
 
277
- # Rahmen zeichnen
278
- draw.rectangle([x1, y1, x2, y2], outline=outline_color, width=3)
279
 
280
- # Hinweis-Text hinzufügen
281
- try:
282
- # Versuche, eine Schriftart zu laden (falls verfügbar)
283
- try:
284
- font = ImageFont.truetype("arial.ttf", 16)
285
- except:
286
- font = ImageFont.load_default()
287
-
288
- text = f"{'🟢 Schutzmodus AN' if face_preserve else '🔴 Schutzmodus AUS'}"
289
- # Text-Hintergrund
290
- text_bbox = draw.textbbox((x1, y1 - 25), text, font=font)
291
- draw.rectangle(text_bbox, fill="white")
292
- # Text
293
- draw.text((x1, y1 - 25), text, fill=outline_color, font=font)
294
- except:
295
- pass # Falls Schrift nicht geladen werden kann
296
 
297
- return preview
298
 
299
-
300
- # === HAUPTFUNKTION: TEXT ZU BILD MIT AUTOMATISCHEM FACE-FIX - KORRIGIERT ===
301
  def text_to_image(prompt, model_id, steps, guidance_scale, progress=gr.Progress()):
302
  try:
303
  if not prompt or not prompt.strip():
304
  return None, "Bitte einen Prompt eingeben"
305
 
306
- print(f"\n" + "="*60)
307
- print(f"🔧 START: Text zu Bild Generierung")
308
- print(f"🔧 Prompt: {prompt}")
309
- print(f"🔧 Modell: {model_id}")
310
-
311
- # Personenerkennung ZUERST auf dem ORIGINAL Prompt!
312
- is_person = is_person_prompt(prompt)
313
- print(f"🔧 Person erkannt? {is_person}")
314
 
 
315
  auto_negatives = auto_negative_prompt(prompt)
316
- print(f"🔧 Auto Negative Prompt: {auto_negatives[:100]}...")
317
 
318
  start_time = time.time()
 
319
 
320
- # Qualitäts-Boost nur wenn nicht vorhanden
321
- quality_keywords = ['masterpiece', 'best quality', 'raw', 'highly detailed', 'ultra realistic']
322
- has_quality = any(kw in prompt.lower() for kw in quality_keywords)
323
- has_weights = bool(re.search(r':\d+\.\d+|\([^)]+:\d', prompt))
324
-
325
- enhanced_prompt = f"masterpiece, raw, best quality, highly detailed, {prompt}" if not (has_quality or has_weights) else prompt
326
- print(f"🔧 Enhanced Prompt: {enhanced_prompt[:100]}...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
 
 
328
  progress(0, desc="Lade Modell...")
329
  pipe = load_txt2img(model_id)
330
 
331
  seed = random.randint(0, 2**32 - 1)
332
  generator = torch.Generator(device=device).manual_seed(seed)
333
- print(f"🔧 Seed: {seed}")
334
-
335
- print(f"🔧 Starte Bildgenerierung...")
 
 
 
336
  image = pipe(
337
  prompt=enhanced_prompt,
338
  negative_prompt=auto_negatives,
339
- height=512, width=512,
 
340
  num_inference_steps=int(steps),
341
  guidance_scale=guidance_scale,
342
  generator=generator,
343
- callback_on_step_end=TextToImageProgressCallback(progress, steps),
344
  callback_on_step_end_tensor_inputs=[],
345
  ).images[0]
346
-
347
- print(f"✅ Bildgenerierung abgeschlossen")
348
-
349
- # AUTOMATISCHER FACE-FIX NUR BEI PERSONEN
350
- if FACEFIX_AVAILABLE and is_person:
351
- print("\n" + "🎭"*30)
352
- print("🎭 PERSON ERKANNT → Starte Face-Fix für perfekte Gesichter...")
353
- print("🎭"*30)
354
-
355
- progress(0.9, desc="Perfektioniere Gesicht & Hände...")
356
- try:
357
- # Originalbild speichern für Vergleich
358
- original_image = image.copy()
359
- print("🎭 Originalbild gespeichert, starte Face-Fix...")
360
-
361
- # Face-Fix anwenden
362
- fixed_image = apply_facefix(
363
- image=image,
364
- prompt=enhanced_prompt,
365
- negative_prompt=auto_negatives,
366
- seed=seed,
367
- model_id=model_id
368
- )
369
-
370
- image = fixed_image
371
- print("✅✅✅ Face-Fix ABGESCHLOSSEN! ✅✅✅")
372
-
373
- # Optional: Vergleichsbild erstellen
374
- try:
375
- width, height = image.size
376
- comparison = Image.new('RGB', (width * 2, height))
377
- comparison.paste(original_image, (0, 0))
378
- comparison.paste(image, (width, 0))
379
-
380
- # Trennlinie
381
- draw = ImageDraw.Draw(comparison)
382
- draw.line([(width, 0), (width, height)], fill="white", width=2)
383
-
384
- # Beschriftung hinzufügen
385
- try:
386
- font = ImageFont.truetype("arial.ttf", 20)
387
- except:
388
- font = ImageFont.load_default()
389
-
390
- draw.text((10, 10), "Vor Face-Fix", fill="white", font=font)
391
- draw.text((width + 10, 10), "Nach Face-Fix", fill="white", font=font)
392
-
393
- # Vergleichsbild als Option zurückgeben
394
- image = comparison
395
- print("✅ Vergleichsbild erstellt")
396
-
397
- except Exception as e:
398
- print(f"⚠️ Vergleichsbild konnte nicht erstellt werden: {e}")
399
-
400
- except Exception as e:
401
- print(f"❌❌❌ Face-Fix FEHLGESCHLAGEN: {e} ❌❌❌")
402
- import traceback
403
- traceback.print_exc()
404
- else:
405
- if not FACEFIX_AVAILABLE:
406
- print("ℹ️ Face-Fix nicht verfügbar")
407
- if not is_person:
408
- print("ℹ️ Keine Person im Prompt erkannt")
409
-
410
- duration = time.time() - start_time
411
- config = MODEL_CONFIGS.get(model_id, {"name": model_id})
412
 
413
- # Status-Nachricht mit Face-Fix Info
414
- if FACEFIX_AVAILABLE and is_person:
415
- status_msg = f"✅ Generiert mit {config.get('name', model_id)} + Face-Fix in {duration:.1f}s"
416
- else:
417
- status_msg = f"Generiert mit {config.get('name', model_id)} in {duration:.1f}s"
418
 
419
- print(f"\n" + "="*60)
420
- print(f"✅ FERTIG: {status_msg}")
421
- print(f"="*60 + "\n")
422
 
423
  return image, status_msg
424
 
425
  except Exception as e:
 
426
  print(f"❌ Fehler in text_to_image: {e}")
427
  import traceback
428
  traceback.print_exc()
429
- return None, f"Fehler: {str(e)}"
430
-
431
-
432
 
433
  def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
434
  face_preserve, bbox_x1, bbox_y1, bbox_x2, bbox_y2,
@@ -445,11 +532,12 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
445
  print(f"Negativ-Prompt: {neg_prompt}")
446
  print(f"Gesicht beibehalten: {face_preserve}")
447
 
448
- # Automatischen Negativ-Prompt generieren
 
449
  auto_negatives = auto_negative_prompt(prompt)
450
  print(f"🤖 Automatisch generierter Negativ-Prompt: {auto_negatives}")
451
 
452
- # Kombiniere manuellen und automatischen Prompt
453
  combined_negative_prompt = ""
454
 
455
  if neg_prompt and neg_prompt.strip():
@@ -458,6 +546,7 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
458
  print(f"👤 Benutzer Negativ-Prompt: {user_neg}")
459
 
460
  # Entferne Duplikate zwischen automatischen und manuellen Prompts
 
461
  user_words = [word.strip().lower() for word in user_neg.split(",")]
462
  auto_words = [word.strip().lower() for word in auto_negatives.split(",")]
463
 
@@ -469,7 +558,7 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
469
  if auto_word and auto_word not in user_words:
470
  combined_words.append(auto_word)
471
 
472
- # Zusammenfügen und Duplikate entfernen
473
  unique_words = []
474
  seen_words = set()
475
  for word in combined_words:
@@ -484,6 +573,8 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
484
  print(f"ℹ️ Kein manueller Negativ-Prompt, verwende nur automatischen: {combined_negative_prompt}")
485
 
486
  print(f"✅ Finaler kombinierter Negativ-Prompt: {combined_negative_prompt}")
 
 
487
 
488
  progress(0, desc="Starte Generierung mit ControlNet...")
489
 
@@ -567,22 +658,6 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
567
  print(f"🕒 Dauer: {end_time - start_time:.2f} Sekunden")
568
 
569
  generated_image = result.images[0]
570
-
571
- # Optional: Face-Fix auch auf das transformierte Bild anwenden
572
- if FACEFIX_AVAILABLE and is_person_prompt(prompt):
573
- print("Transformiertes Bild → Wende Face-Fix an...")
574
- try:
575
- generated_image = apply_facefix(
576
- image=generated_image,
577
- prompt=prompt,
578
- negative_prompt=combined_negative_prompt,
579
- seed=seed,
580
- model_id="runwayml/stable-diffusion-v1-5"
581
- )
582
- print("Face-Fix auf transformiertem Bild abgeschlossen!")
583
- except Exception as e:
584
- print(f"Face-Fix auf transformiertem Bild fehlgeschlagen: {e}")
585
-
586
  return generated_image
587
 
588
  except Exception as e:
@@ -591,6 +666,24 @@ def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
591
  traceback.print_exc()
592
  return None
593
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
594
  def main_ui():
595
  with gr.Blocks(
596
  title="AI Image Generator",
@@ -685,15 +778,6 @@ def main_ui():
685
  color: #721c24;
686
  border: 1px solid #f5c6cb;
687
  }
688
- .face-fix-badge {
689
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
690
- color: white;
691
- padding: 4px 8px;
692
- border-radius: 12px;
693
- font-size: 12px;
694
- margin-left: 10px;
695
- display: inline-block;
696
- }
697
  """
698
  ) as demo:
699
 
@@ -701,16 +785,6 @@ def main_ui():
701
  with gr.Tab("Text zu Bild"):
702
  gr.Markdown("## 🎨 Text zu Bild Generator")
703
 
704
- # Face-Fix Info Badge
705
- if FACEFIX_AVAILABLE:
706
- gr.Markdown(
707
- f"""
708
- <div style="background: #e3f2fd; padding: 10px; border-radius: 8px; margin-bottom: 20px; border-left: 4px solid #2196f3;">
709
- 🎭 <strong>Face-Fix aktiviert!</strong> Gesichter werden automatisch verbessert.
710
- </div>
711
- """
712
- )
713
-
714
  with gr.Row():
715
  with gr.Column(scale=2):
716
  # Modellauswahl Dropdown (NUR 2 MODELLE)
@@ -736,7 +810,7 @@ def main_ui():
736
 
737
  with gr.Column(scale=3):
738
  txt_input = gr.Textbox(
739
- placeholder="z.B. ultra realistic portrait of a beautiful woman with detailed skin, perfect eyes, sharp focus, cinematic lighting",
740
  lines=3,
741
  label="🎯 Prompt (Englisch)",
742
  info="Beschreibe detailliert, was du sehen möchtest. Negative Prompts werden automatisch generiert."
@@ -953,4 +1027,4 @@ if __name__ == "__main__":
953
  show_error=True,
954
  share=False,
955
  ssr_mode=False # SSR deaktivieren für Stabilität
956
- )
 
4
  from diffusers import DPMSolverMultistepScheduler, PNDMScheduler
5
  from controlnet_module import controlnet_processor
6
  import torch
7
+ from PIL import Image, ImageDraw
8
  import time
9
  import os
10
  import tempfile
11
  import random
12
+ Import re
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  # === OPTIMIERTE EINSTELLUNGEN ===
15
  device = "cuda" if torch.cuda.is_available() else "cpu"
 
21
  # === MODELLKONFIGURATION (NUR 2 MODELLE) ===
22
  MODEL_CONFIGS = {
23
  "runwayml/stable-diffusion-v1-5": {
24
+ "name": "🏠 Stable Diffusion 1.5 (Universal)",
25
  "description": "Universal model, good all-rounder, reliable results",
26
  "requires_vae": False,
27
  "recommended_steps": 35,
 
29
  "supports_fp16": True
30
  },
31
  "SG161222/Realistic_Vision_V6.0_B1_noVAE": {
32
+ "name": "👤 Realistic Vision V6.0 (Portraits)",
33
  "description": "Best for photorealistic faces, skin details, human portraits",
34
  "requires_vae": True,
35
  "vae_model": "stabilityai/sd-vae-ft-mse",
 
39
  }
40
  }
41
 
42
+ # === SAFETENSORS KONFIGURATION ===
43
  SAFETENSORS_MODELS = ["runwayml/stable-diffusion-v1-5"]
44
+
45
+ # Aktuell ausgewähltes Modell (wird vom User gesetzt)
46
  current_model_id = "runwayml/stable-diffusion-v1-5"
47
 
48
  # === AUTOMATISCHE NEGATIVE PROMPT GENERIERUNG ===
49
  def auto_negative_prompt(positive_prompt):
50
+ """Generiert automatisch negative Prompts basierend auf dem positiven Prompt"""
51
  p = positive_prompt.lower()
52
  negatives = []
53
 
54
+ # Personen / Portraits
55
  if any(w in p for w in [
56
+ "person", "man", "woman", "face", "portrait", "team", "employee",
57
+ "people", "crowd", "character", "figure", "human", "child", "baby",
58
+ "girl", "boy", "lady", "gentleman", "fairy", "elf", "dwarf", "santa claus"
59
+ "mermaid", "angel", "demon", "witch", "wizard", "creature", "being",
60
+ "model", "actor", "actress", "celebrity", "avatar", "group"]):
 
61
  negatives.append(
62
  "blurry face, lowres face, deformed pupils, bad anatomy, malformed hands, extra fingers, uneven eyes, distorted face, "
63
  "unrealistic skin, mutated, ugly, disfigured, poorly drawn face, "
64
  "missing limbs, extra limbs, fused fingers, too many fingers, bad teeth, "
65
+ "mutated hands, long neck, extra wings, multiple wings,grainy face, noisy face, "
66
  "compression artifacts, rendering artifacts, digital artifacts, overprocessed face, oversmoothed face "
67
  )
68
+
69
+ # Business / Corporate
70
  if any(w in p for w in ["office", "business", "team", "meeting", "corporate", "company", "workplace"]):
71
+ negatives.append(
72
+ "overexposed, oversaturated, harsh lighting, watermark, text, logo, brand"
73
+ )
74
 
75
+ # Produkt / CGI
76
  if any(w in p for w in ["product", "packshot", "mockup", "render", "3d", "cgi", "packaging"]):
77
+ negatives.append(
78
+ "plastic texture, noisy, overly reflective surfaces, watermark, text, low poly"
79
+ )
80
 
81
+ # Landschaft / Umgebung
82
  if any(w in p for w in ["landscape", "nature", "mountain", "forest", "outdoor", "beach", "sky"]):
83
+ negatives.append(
84
+ "blurry, oversaturated, unnatural colors, distorted horizon, floating objects"
85
+ )
86
 
87
+ # Logos / Symbole
88
  if any(w in p for w in ["logo", "symbol", "icon", "typography", "badge", "emblem"]):
89
+ negatives.append(
90
+ "watermark, signature, username, text, writing, scribble, messy"
91
+ )
92
 
93
+ # Architektur / Gebäude
94
  if any(w in p for w in ["building", "architecture", "house", "interior", "room", "facade"]):
95
+ negatives.append(
96
+ "deformed, distorted perspective, floating objects, collapsing structure"
97
+ )
98
 
99
+ # Basis negative Prompts für alle Fälle
100
  base_negatives = "low quality, worst quality, blurry, jpeg artifacts, ugly, deformed"
101
 
102
+ if negatives:
103
+ return base_negatives + ", " + ", ".join(negatives)
104
+ else:
105
+ return base_negatives
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
  # === GESICHTSMASKEN-FUNKTIONEN ===
108
  def create_face_mask(image, bbox_coords, face_preserve):
109
+ """Erzeugt eine Gesichtsmaske - WEIßE Bereiche werden VERÄNDERT, SCHWARZE BLEIBEN"""
110
+ mask = Image.new("L", image.size, 0) # Start mit komplett schwarzer Maske (alles geschützt)
111
+
112
  if bbox_coords and all(coord is not None for coord in bbox_coords):
113
  x1, y1, x2, y2 = bbox_coords
114
  draw = ImageDraw.Draw(mask)
115
+
116
  if face_preserve:
117
+ # GESICHTSERHALTUNG: Maske um das Gesicht herum zeichnen
118
+ draw.rectangle([0, 0, image.size[0], image.size[1]], fill=255) # Alles weiß = verändern
119
+ draw.rectangle([x1, y1, x2, y2], fill=0) # Gesicht schwarz = geschützt (rechteckig)
120
+ print("Gesicht wird GESCHÜTZT - Umgebung wird verändert (rechteckige Maske)")
121
  else:
122
+ # NUR GESICHT VERÄNDERN: Nur das Gesicht wird weiß (verändert)
123
+ draw.rectangle([x1, y1, x2, y2], fill=255) # Gesicht weiß = verändern (rechteckig)
124
+ print("Nur Gesicht wird verändert - Umgebung bleibt erhalten (rechteckige Maske)")
125
+
126
  return mask
127
 
128
  def auto_detect_face_area(image):
129
+ """Optimierten Vorschlag für Gesichtsbereich ohne externe Bibliotheken"""
130
  width, height = image.size
131
+ # Größere Bounding Box für bessere Abdeckung (50% statt 40%)
132
  face_size = min(width, height) * 0.4
133
+ # Verschiebe y1 nach oben, um Stirn und Kinn besser abzudecken
134
  x1 = (width - face_size) / 2
135
+ y1 = (height - face_size) / 4 # Höher positioniert (25% statt 33%)
136
  x2 = x1 + face_size
137
+ y2 = y1 + face_size * 1.2 # Leicht länglicher für ovale Gesichter
138
+ # Stelle sicher, dass Koordinaten innerhalb des Bildes liegen
139
  x1, y1 = max(0, int(x1)), max(0, int(y1))
140
  x2, y2 = min(width, int(x2)), min(height, int(y2))
141
+ print(f"Geschätzte Gesichtskoordinaten: [{x1}, {y1}, {x2}, {y2}]")
142
  return [x1, y1, x2, y2]
143
 
144
  # === PIPELINES ===
 
147
  pipe_img2img = None
148
 
149
  def load_txt2img(model_id):
150
+ """Lädt das Text-to-Image Modell basierend auf der Auswahl"""
151
  global pipe_txt2img, current_pipe_model_id
152
+
153
+ # Wenn bereits das richtige Modell geladen ist, nichts tun
154
  if pipe_txt2img is not None and current_pipe_model_id == model_id:
155
+ print(f"✅ Modell {model_id} bereits geladen")
156
  return pipe_txt2img
157
 
158
+ print(f"🔄 Lade Modell: {model_id}")
159
+
160
  config = MODEL_CONFIGS.get(model_id, MODEL_CONFIGS["runwayml/stable-diffusion-v1-5"])
161
+ print(f"📋 Modell-Konfiguration: {config['name']}")
162
+ print(f"📝 Beschreibung: {config['description']}")
163
 
164
  try:
165
+ # VAE-Handling basierend auf Modellkonfiguration
166
  vae = None
167
  if config.get("requires_vae", False):
168
+ print(f"🔧 Lade externe VAE: {config['vae_model']}")
169
+ try:
170
+ vae = AutoencoderKL.from_pretrained(
171
+ config["vae_model"],
172
+ torch_dtype=torch_dtype
173
+ ).to(device)
174
+ print("✅ VAE erfolgreich geladen")
175
+ except Exception as vae_error:
176
+ print(f"⚠️ Fehler beim Laden der VAE: {vae_error}")
177
+ print("ℹ️ Versuche ohne VAE weiter...")
178
+ vae = None
179
 
180
+ # Modellparameter basierend auf Modelltyp
181
  model_params = {
182
  "torch_dtype": torch_dtype,
183
  "safety_checker": None,
184
  "requires_safety_checker": False,
185
+ "add_watermarker": False,
186
+ "allow_pickle": True, # Für .bin Modelle wichtig
187
  }
188
 
189
+ # SAFETENSORS LOGIK
190
  if model_id in SAFETENSORS_MODELS:
191
  model_params["use_safetensors"] = True
192
+ print(f"ℹ️ Verwende safetensors für {model_id}")
193
+ else:
194
+ model_params["use_safetensors"] = False
195
+ print(f"ℹ️ Verwende .bin weights für {model_id}")
196
 
197
+ # FP16 Variante nur wenn Modell sie unterstützt UND wir auf GPU sind
198
  if config.get("supports_fp16", False) and torch_dtype == torch.float16:
199
  model_params["variant"] = "fp16"
200
+ print("ℹ️ Verwende FP16 Variante")
201
+ else:
202
+ print("ℹ️ Verwende Standard Variante (kein FP16)")
203
 
204
+ # VAE nur wenn nicht None
205
  if vae is not None:
206
  model_params["vae"] = vae
207
 
208
+ print(f"📥 Lade Hauptmodell von Hugging Face...")
209
+ pipe_txt2img = StableDiffusionPipeline.from_pretrained(
210
+ model_id,
211
+ **model_params
212
+ ).to(device)
213
+
214
+ # SICHERER SCHEDULER-HANDLING
215
+ print("⚙️ Konfiguriere Scheduler...")
216
+
217
+ # Prüfe ob Scheduler existiert
218
+ if pipe_txt2img.scheduler is None:
219
+ print("⚠️ Scheduler ist None, setze Standard-Scheduler")
220
+ pipe_txt2img.scheduler = PNDMScheduler.from_pretrained(
221
+ model_id,
222
+ subfolder="scheduler"
223
+ )
224
 
225
+ # Versuche DPM-Solver zu verwenden (bessere Ergebnisse)
226
  try:
227
+ # Hole die Scheduler-Konfiguration
228
+ if hasattr(pipe_txt2img.scheduler, 'config'):
229
+ scheduler_config = pipe_txt2img.scheduler.config
230
+ else:
231
+ # Fallback-Konfiguration für Scheduler
232
+ scheduler_config = {
233
+ "beta_start": 0.00085,
234
+ "beta_end": 0.012,
235
+ "beta_schedule": "scaled_linear",
236
+ "num_train_timesteps": 1000,
237
+ "prediction_type": "epsilon",
238
+ "steps_offset": 1
239
+ }
240
+ print("⚠️ Keine Scheduler-Konfig gefunden, verwende Standard")
241
+
242
+ # Setze DPM-Solver Scheduler
243
  pipe_txt2img.scheduler = DPMSolverMultistepScheduler.from_config(
244
+ scheduler_config,
245
  use_karras_sigmas=True,
246
  algorithm_type="sde-dpmsolver++"
247
  )
248
+ print("✅ DPM-Solver Multistep Scheduler konfiguriert")
249
+
250
+ except Exception as scheduler_error:
251
+ print(f"⚠️ Konnte DPM-Scheduler nicht setzen: {scheduler_error}")
252
+ print("ℹ️ Verwende Standard-Scheduler weiter")
253
+
254
+ # Optimierungen
255
+ pipe_txt2img.enable_attention_slicing()
256
+ print("✅ Attention Slicing aktiviert")
257
+
258
+ # VAE Slicing nur wenn VAE existiert
259
+ if hasattr(pipe_txt2img, 'vae') and pipe_txt2img.vae is not None:
260
+ try:
261
+ pipe_txt2img.enable_vae_slicing()
262
+ if hasattr(pipe_txt2img.vae, 'enable_slicing'):
263
+ pipe_txt2img.vae.enable_slicing()
264
+ print("✅ VAE Slicing aktiviert")
265
+ except Exception as vae_slice_error:
266
+ print(f"⚠️ VAE Slicing nicht möglich: {vae_slice_error}")
267
 
268
  current_pipe_model_id = model_id
269
+ print(f"✅ {config['name']} erfolgreich geladen")
270
+ print(f"📊 Modell-Dtype: {pipe_txt2img.dtype}")
271
+ print(f"📊 Scheduler: {type(pipe_txt2img.scheduler).__name__}")
272
+ print(f"⚙️ Empfohlene Einstellungen: Steps={config['recommended_steps']}, CFG={config['recommended_cfg']}")
273
+
274
  return pipe_txt2img
275
 
276
  except Exception as e:
277
+ print(f"Fehler beim Laden von {model_id}: {str(e)[:200]}...")
278
+ import traceback
279
+ traceback.print_exc()
280
+ print("🔄 Fallback auf SD 1.5...")
281
+
282
+ # Fallback auf Standard SD 1.5
283
+ try:
284
+ pipe_txt2img = StableDiffusionPipeline.from_pretrained(
285
+ "runwayml/stable-diffusion-v1-5",
286
+ torch_dtype=torch_dtype,
287
+ use_safetensors=True,
288
+ ).to(device)
289
+ pipe_txt2img.enable_attention_slicing()
290
+ current_pipe_model_id = "runwayml/stable-diffusion-v1-5"
291
+ print("✅ Fallback auf SD 1.5 erfolgreich")
292
+
293
+ return pipe_txt2img
294
+ except Exception as fallback_error:
295
+ print(f"❌ Auch Fallback fehlgeschlagen: {fallback_error}")
296
+ raise
297
 
298
  def load_img2img():
299
  global pipe_img2img
300
  if pipe_img2img is None:
301
+ print("Loading Inpainting model...")
302
+ try:
303
+ pipe_img2img = StableDiffusionInpaintPipeline.from_pretrained(
304
+ "runwayml/stable-diffusion-inpainting",
305
+ torch_dtype=torch_dtype,
306
+ allow_pickle=False,
307
+ safety_checker=None,
308
+ ).to(device)
309
+ except Exception as e:
310
+ print(f"Fehler beim Laden des Inpainting-Modells: {e}")
311
+ raise
312
+
313
+ from diffusers import DPMSolverMultistepScheduler
314
+ pipe_img2img.scheduler = DPMSolverMultistepScheduler.from_config(
315
+ pipe_img2img.scheduler.config,
316
+ algorithm_type="sde-dpmsolver++",
317
+ use_karras_sigmas=True,
318
+ timestep_spacing="trailing"
319
+ )
320
+
321
  pipe_img2img.enable_attention_slicing()
322
  pipe_img2img.enable_vae_tiling()
323
+ if hasattr(pipe_img2img, 'vae_slicing'):
324
+ pipe_img2img.vae_slicing = True
325
+
326
  return pipe_img2img
327
 
328
+ # === NEUE CALLBACK-FUNKTIONEN FÜR FORTSCHRITT ===
329
  class TextToImageProgressCallback:
330
  def __init__(self, progress, total_steps):
331
  self.progress = progress
332
  self.total_steps = total_steps
333
+ self.current_step = 0
334
+
335
  def __call__(self, pipe, step, timestep, callback_kwargs):
336
+ self.current_step = step + 1
337
+ progress_percent = (step / self.total_steps) * 100
338
+ self.progress(progress_percent / 100, desc="Generierung läuft...")
339
  return callback_kwargs
340
 
341
  class ImageToImageProgressCallback:
342
  def __init__(self, progress, total_steps, strength):
343
  self.progress = progress
344
  self.total_steps = total_steps
345
+ self.current_step = 0
346
  self.strength = strength
347
+ self.actual_total_steps = None
348
+
349
  def __call__(self, pipe, step, timestep, callback_kwargs):
350
+ self.current_step = step + 1
351
+
352
+ if self.actual_total_steps is None:
353
+ if self.strength < 1.0:
354
+ self.actual_total_steps = int(self.total_steps * self.strength)
355
+ else:
356
+ self.actual_total_steps = self.total_steps
357
+
358
+ print(f"🎯 INTERNE STEP-AUSGABE: Strength {self.strength} → {self.actual_total_steps} tatsächliche Denoising-Schritte")
359
+
360
+ progress_percent = (step / self.actual_total_steps) * 100
361
+ self.progress(progress_percent / 100, desc="Generierung läuft...")
362
  return callback_kwargs
363
 
364
+ # === NEUE FUNKTIONEN FÜR DIE FEATURES ===
365
+ def create_preview_image(image, bbox_coords, face_preserve, mode_color):
366
+ """Erstellt eine Vorschau mit farbigem Rahmen basierend auf dem Modus"""
367
  if image is None:
368
+ return None
369
+
370
+ preview = image.copy()
371
+ draw = ImageDraw.Draw(preview)
372
 
373
+ if mode_color == "red":
374
+ border_color = (255, 0, 0, 180)
375
+ mode_text = "NUR BILDELEMENT VERÄNDERN"
376
+ else:
377
+ border_color = (0, 255, 0, 180)
378
+ mode_text = "BILDELEMENT BEIBEHALTEN"
379
 
380
+ border_width = 8
381
+ draw.rectangle([0, 0, preview.width-1, preview.height-1],
382
+ outline=border_color, width=border_width)
383
+
384
+ if bbox_coords and all(coord is not None for coord in bbox_coords):
385
+ x1, y1, x2, y2 = bbox_coords
386
+
387
+ box_color = (255, 255, 0, 200)
388
+ draw.rectangle([x1, y1, x2, y2], outline=box_color, width=3)
389
+
390
+ text_color = (255, 255, 255)
391
+ bg_color = (0, 0, 0, 160)
392
+
393
+ text_bbox = draw.textbbox((x1, y1 - 25), mode_text)
394
+ draw.rectangle([text_bbox[0]-5, text_bbox[1]-2, text_bbox[2]+5, text_bbox[3]+2],
395
+ fill=bg_color)
396
+
397
+ draw.text((x1, y1 - 25), mode_text, fill=text_color)
398
 
399
+ return preview
400
 
401
+ def update_live_preview(image, bbox_x1, bbox_y1, bbox_x2, bbox_y2, face_preserve):
402
+ """Aktualisiert die Live-Vorschau bei Koordinaten-Änderungen"""
403
  if image is None:
404
  return None
405
 
406
+ bbox_coords = [bbox_x1, bbox_y1, bbox_x2, bbox_y2]
407
+ mode_color = "green" if face_preserve else "red"
 
 
 
408
 
409
+ return create_preview_image(image, bbox_coords, face_preserve, mode_color)
410
+
411
+ def process_image_upload(image):
412
+ """Verarbeitet Bild-Upload und gibt Bild + Koordinaten zurück"""
413
+ if image is None:
414
+ return None, None, None, None, None
415
+
416
+ if image.size != (512, 512):
417
+ image = image.resize((512, 512), Image.LANCZOS)
418
+ print(f"Bild auf 512x512 skaliert")
419
 
420
+ bbox = auto_detect_face_area(image)
421
+ bbox_x1, bbox_y1, bbox_x2, bbox_y2 = bbox
422
 
423
+ preview = create_preview_image(image, bbox, True, "green")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
 
425
+ return preview, bbox_x1, bbox_y1, bbox_x2, bbox_y2
426
 
427
+ # === HAUPTFUNKTIONEN ===
 
428
  def text_to_image(prompt, model_id, steps, guidance_scale, progress=gr.Progress()):
429
  try:
430
  if not prompt or not prompt.strip():
431
  return None, "Bitte einen Prompt eingeben"
432
 
433
+ print(f"🚀 Starte Generierung mit Modell: {model_id}")
434
+ print(f"📝 Prompt: {prompt}")
 
 
 
 
 
 
435
 
436
+ # Automatische negative Prompts generieren
437
  auto_negatives = auto_negative_prompt(prompt)
438
+ print(f"🤖 Automatisch generierte Negative Prompts: {auto_negatives}")
439
 
440
  start_time = time.time()
441
+
442
 
443
+ # Liste von Qualitätswörtern/Gewichten, die auf Benutzereingaben prüfen
444
+ quality_keywords = ['masterpiece', 'best quality', 'high quality', 'highly detailed',
445
+ 'exquisite', 'detailed', 'ultra detailed', 'professional',
446
+ 'perfect', 'excellent', 'amazing', 'stunning', 'beautiful']
447
+
448
+ # Prüfe, ob der Benutzer bereits Qualitätswörter/Gewichte verwendet hat
449
+ user_has_quality_words = False
450
+
451
+ # Konvertiere Prompt zu Kleinbuchstaben für die Prüfung
452
+ prompt_lower = prompt.lower()
453
+
454
+ # Prüfe auf einfache Qualitätswörter
455
+ for keyword in quality_keywords:
456
+ if keyword in prompt_lower:
457
+ user_has_quality_words = True
458
+ print(f"✓ Benutzer verwendet bereits Qualitätswort: {keyword}")
459
+ break
460
+
461
+ # Prüfe auf Gewichte (z.B. (word:1.5), [word], etc.)
462
+ weight_patterns = [r'\([^)]+:\d+(\.\d+)?\)', r'\[[^\]]+\]']
463
+ for pattern in weight_patterns:
464
+ if re.search(pattern, prompt):
465
+ user_has_quality_words = True
466
+ print("✓ Benutzer verwendet bereits Gewichte im Prompt")
467
+ break
468
+
469
+ # Prompt basierend auf Prüfung anpassen
470
+ if not user_has_quality_words:
471
+ enhanced_prompt = f"masterpiece, raw, best quality, highly detailed, {prompt}"
472
+ print(f"🔄 Verbesserter Prompt: {enhanced_prompt}")
473
+ else:
474
+ enhanced_prompt = prompt
475
+ print("✓ Benutzerprompt wird unverändert verwendet")
476
+
477
+ print(f"Finaler Prompt für Generation: {enhanced_prompt}")
478
 
479
+
480
+
481
  progress(0, desc="Lade Modell...")
482
  pipe = load_txt2img(model_id)
483
 
484
  seed = random.randint(0, 2**32 - 1)
485
  generator = torch.Generator(device=device).manual_seed(seed)
486
+ print(f"🌱 Seed: {seed}")
487
+
488
+ callback = TextToImageProgressCallback(progress, steps)
489
+
490
+ print(f"⚙️ Einstellungen: Steps={steps}, CFG={guidance_scale}")
491
+
492
  image = pipe(
493
  prompt=enhanced_prompt,
494
  negative_prompt=auto_negatives,
495
+ height=512,
496
+ width=512,
497
  num_inference_steps=int(steps),
498
  guidance_scale=guidance_scale,
499
  generator=generator,
500
+ callback_on_step_end=callback,
501
  callback_on_step_end_tensor_inputs=[],
502
  ).images[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503
 
504
+ end_time = time.time()
505
+ duration = end_time - start_time
506
+ print(f"✅ Bild generiert in {duration:.2f} Sekunden")
 
 
507
 
508
+ config = MODEL_CONFIGS.get(model_id, MODEL_CONFIGS["runwayml/stable-diffusion-v1-5"])
509
+ status_msg = f"✅ Generiert mit {config['name']} in {duration:.1f}s"
 
510
 
511
  return image, status_msg
512
 
513
  except Exception as e:
514
+ error_msg = f"❌ Fehler: {str(e)}"
515
  print(f"❌ Fehler in text_to_image: {e}")
516
  import traceback
517
  traceback.print_exc()
518
+ return None, error_msg
 
 
519
 
520
  def img_to_image(image, prompt, neg_prompt, strength, steps, guidance_scale,
521
  face_preserve, bbox_x1, bbox_y1, bbox_x2, bbox_y2,
 
532
  print(f"Negativ-Prompt: {neg_prompt}")
533
  print(f"Gesicht beibehalten: {face_preserve}")
534
 
535
+
536
+ # ===== NEU: AUTOMATISCHEN NEGATIV-PROMPT GENERIEREN =====
537
  auto_negatives = auto_negative_prompt(prompt)
538
  print(f"🤖 Automatisch generierter Negativ-Prompt: {auto_negatives}")
539
 
540
+ # ===== NEU: KOMBINIERE MANUELLEN UND AUTOMATISCHEN PROMPT =====
541
  combined_negative_prompt = ""
542
 
543
  if neg_prompt and neg_prompt.strip():
 
546
  print(f"👤 Benutzer Negativ-Prompt: {user_neg}")
547
 
548
  # Entferne Duplikate zwischen automatischen und manuellen Prompts
549
+ # Konvertiere beide in Sets für einfachen Duplikatvergleich
550
  user_words = [word.strip().lower() for word in user_neg.split(",")]
551
  auto_words = [word.strip().lower() for word in auto_negatives.split(",")]
552
 
 
558
  if auto_word and auto_word not in user_words:
559
  combined_words.append(auto_word)
560
 
561
+ # Zusammenfügen und Duplikate entfernen (für den Fall von Duplikaten innerhalb des gleichen Prompts)
562
  unique_words = []
563
  seen_words = set()
564
  for word in combined_words:
 
573
  print(f"ℹ️ Kein manueller Negativ-Prompt, verwende nur automatischen: {combined_negative_prompt}")
574
 
575
  print(f"✅ Finaler kombinierter Negativ-Prompt: {combined_negative_prompt}")
576
+ # ===== ENDE DER NEUEN LOGIK =====
577
+
578
 
579
  progress(0, desc="Starte Generierung mit ControlNet...")
580
 
 
658
  print(f"🕒 Dauer: {end_time - start_time:.2f} Sekunden")
659
 
660
  generated_image = result.images[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
661
  return generated_image
662
 
663
  except Exception as e:
 
666
  traceback.print_exc()
667
  return None
668
 
669
+ def update_bbox_from_image(image):
670
+ """Aktualisiert die Bounding-Box-Koordinaten wenn ein Bild hochgeladen wird"""
671
+ if image is None:
672
+ return None, None, None, None
673
+
674
+ bbox = auto_detect_face_area(image)
675
+ return bbox[0], bbox[1], bbox[2], bbox[3]
676
+
677
+ def update_model_settings(model_id):
678
+ """Aktualisiert die empfohlenen Einstellungen basierend auf Modellauswahl"""
679
+ config = MODEL_CONFIGS.get(model_id, MODEL_CONFIGS["runwayml/stable-diffusion-v1-5"])
680
+
681
+ return (
682
+ config["recommended_steps"], # steps
683
+ config["recommended_cfg"], # guidance_scale
684
+ f"📊 Empfohlene Einstellungen: {config['steps']} Steps, CFG {config['cfg']}"
685
+ )
686
+
687
  def main_ui():
688
  with gr.Blocks(
689
  title="AI Image Generator",
 
778
  color: #721c24;
779
  border: 1px solid #f5c6cb;
780
  }
 
 
 
 
 
 
 
 
 
781
  """
782
  ) as demo:
783
 
 
785
  with gr.Tab("Text zu Bild"):
786
  gr.Markdown("## 🎨 Text zu Bild Generator")
787
 
 
 
 
 
 
 
 
 
 
 
788
  with gr.Row():
789
  with gr.Column(scale=2):
790
  # Modellauswahl Dropdown (NUR 2 MODELLE)
 
810
 
811
  with gr.Column(scale=3):
812
  txt_input = gr.Textbox(
813
+ placeholder="z.B. ultra realistic mountain landscape at sunrise, soft mist over the valley, detailed foliage, crisp textures, depth of field, sunlight rays through clouds, shot on medium format camera, 8k, HDR, hyper-detailed, natural lighting, masterpiece",
814
  lines=3,
815
  label="🎯 Prompt (Englisch)",
816
  info="Beschreibe detailliert, was du sehen möchtest. Negative Prompts werden automatisch generiert."
 
1027
  show_error=True,
1028
  share=False,
1029
  ssr_mode=False # SSR deaktivieren für Stabilität
1030
+ )