Spaces:
Sleeping
Sleeping
Upload 3 files
Browse files- README.md +3 -3
- app.py +398 -289
- requirements.txt +7 -65
README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
| 1 |
---
|
| 2 |
-
title: Bilder Builder
|
| 3 |
emoji: 🌄
|
| 4 |
colorFrom: pink
|
| 5 |
colorTo: yellow
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
app_file: app.py
|
| 9 |
pinned: true
|
| 10 |
header: mini
|
| 11 |
-
short_description: Erstelle Bilder mit Flux
|
| 12 |
thumbnail: https://i.imgur.com/oXiKuZK.png
|
| 13 |
---
|
| 14 |
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Bilder Builder (UPDATE, Gradio 5)
|
| 3 |
emoji: 🌄
|
| 4 |
colorFrom: pink
|
| 5 |
colorTo: yellow
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 5.33.0
|
| 8 |
app_file: app.py
|
| 9 |
pinned: true
|
| 10 |
header: mini
|
| 11 |
+
short_description: Erstelle Bilder mit Flux in HQ oder Schnell
|
| 12 |
thumbnail: https://i.imgur.com/oXiKuZK.png
|
| 13 |
---
|
| 14 |
|
app.py
CHANGED
|
@@ -4,12 +4,15 @@ import numpy as np
|
|
| 4 |
import requests
|
| 5 |
from requests import Session
|
| 6 |
from requests.adapters import HTTPAdapter
|
| 7 |
-
|
|
|
|
|
|
|
| 8 |
from datetime import datetime
|
| 9 |
import urllib.parse
|
|
|
|
| 10 |
from groq import Groq
|
| 11 |
from exif import Image
|
| 12 |
-
from PIL import Image as PILImage, ExifTags as PILExifTags
|
| 13 |
from io import BytesIO
|
| 14 |
import colorsys
|
| 15 |
import spaces
|
|
@@ -26,36 +29,39 @@ MAX_IMAGE_SIZE = 2048
|
|
| 26 |
GROQ_APIKEY_PROMPTENHANCE = os.getenv("GROQ_APIKEY_PROMPTENHANCE")
|
| 27 |
API1 = urllib.parse.unquote(os.getenv("API1"))
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
-
|
| 31 |
-
IMAGE_DIR = "cache/images/"
|
| 32 |
-
if not os.path.exists(CACHE_DIR):
|
| 33 |
-
os.makedirs(CACHE_DIR)
|
| 34 |
-
print(f"Created cache dir on path {CACHE_DIR}")
|
| 35 |
-
os.makedirs(os.path.join(CACHE_DIR, "images"))
|
| 36 |
-
print(f"Created images dir on path {IMAGE_DIR}")
|
| 37 |
-
|
| 38 |
-
RES = os.path.join(os.path.dirname(__file__), "_res")
|
| 39 |
|
| 40 |
-
|
|
|
|
|
|
|
| 41 |
|
| 42 |
-
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
custom_head = f"""
|
| 46 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css"/>
|
| 47 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/js/all.min.js"></script>
|
| 48 |
-
<link rel="apple-touch-icon" sizes="180x180" href="file=
|
| 49 |
-
<link rel="icon" type="image/png" sizes="32x32" href="file=
|
| 50 |
-
<link rel="icon" type="image/png" sizes="16x16" href="file=
|
| 51 |
-
<link rel="icon" type="image/x-icon" href="file=
|
| 52 |
-
<link rel="manifest" href="file=
|
|
|
|
| 53 |
<script src="https://unpkg.com/@dotlottie/player-component@latest/dist/dotlottie-player.mjs" type="module"></script>
|
| 54 |
<script src='https://storage.ko-fi.com/cdn/widget/Widget_2.js'></script>
|
| 55 |
"""
|
| 56 |
|
| 57 |
theme = gr.themes.Soft(
|
| 58 |
-
# primary_hue="orange",
|
| 59 |
radius_size="sm",
|
| 60 |
neutral_hue=gr.themes.Color(
|
| 61 |
c100="#a6adc8",
|
|
@@ -71,40 +77,62 @@ theme = gr.themes.Soft(
|
|
| 71 |
c950="#11111b",
|
| 72 |
),
|
| 73 |
)
|
| 74 |
-
|
| 75 |
title = "Bilder Builder"
|
| 76 |
|
| 77 |
|
| 78 |
# MARK: READ EXIF
|
| 79 |
def read_exif(image_path):
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
|
| 96 |
|
| 97 |
# MARK: GROQ PROMPT ENHANCE
|
| 98 |
def groq_enhance_process(Prompt=""):
|
| 99 |
client = Groq(api_key=GROQ_APIKEY_PROMPTENHANCE)
|
| 100 |
Prompt = "random prompt" if Prompt == "" else Prompt
|
| 101 |
-
SYSTEMPROMPT = os.path.join(RES, "groq_systemmessage_prompt_enhance.json")
|
| 102 |
-
with open(SYSTEMPROMPT, "r") as f:
|
| 103 |
-
SYSTEMPROMPT = json.load(f)
|
| 104 |
-
|
| 105 |
completion = client.chat.completions.create(
|
| 106 |
model="meta-llama/llama-4-scout-17b-16e-instruct",
|
| 107 |
-
messages=[
|
| 108 |
temperature=1,
|
| 109 |
max_tokens=512,
|
| 110 |
top_p=0.9,
|
|
@@ -112,17 +140,14 @@ def groq_enhance_process(Prompt=""):
|
|
| 112 |
seed=random.randint(0, MAX_SEED),
|
| 113 |
stop=None,
|
| 114 |
)
|
| 115 |
-
|
| 116 |
if completion.choices[0].message.content != "":
|
| 117 |
enhanced_prompt = completion.choices[0].message.content
|
| 118 |
enhanced_prompt = re.sub(r"[\.\"]+", "", enhanced_prompt)
|
| 119 |
-
|
| 120 |
return enhanced_prompt
|
| 121 |
|
| 122 |
|
| 123 |
def image_get_size(image_path):
|
| 124 |
img = PILImage.open(image_path)
|
| 125 |
-
# print("Image size:", img.size)
|
| 126 |
width, height = img.size
|
| 127 |
return width, height
|
| 128 |
|
|
@@ -131,54 +156,47 @@ def image_get_size(image_path):
|
|
| 131 |
def image_get_dominant_color(image_path):
|
| 132 |
img = PILImage.open(image_path)
|
| 133 |
img = img.convert("RGB")
|
| 134 |
-
img = img.resize((100, 100), resample=
|
| 135 |
pixels = list(img.getdata())
|
| 136 |
-
|
| 137 |
-
# Erzeuge eine Liste mit den Häufigkeiten der Farben
|
| 138 |
colors = []
|
| 139 |
for pixel in pixels:
|
| 140 |
r, g, b = pixel
|
| 141 |
h, s, v = colorsys.rgb_to_hsv(r / 255, g / 255, b / 255)
|
| 142 |
-
if v > 0.5:
|
| 143 |
continue
|
| 144 |
-
if v > 0.99:
|
| 145 |
continue
|
| 146 |
colors.append((h, s, v))
|
| 147 |
-
|
| 148 |
-
|
| 149 |
dominant_color = max(colors, key=lambda x: x[2])
|
| 150 |
dominant_color_rgb = colorsys.hsv_to_rgb(dominant_color[0], dominant_color[1], dominant_color[2])
|
| 151 |
dominant_color_rgb = [int(c * 255) for c in dominant_color_rgb]
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
return dominant_color_rgb
|
| 156 |
|
| 157 |
|
| 158 |
# MARK: CLEAR COMPONENTS
|
| 159 |
def clear_components():
|
| 160 |
-
return None
|
| 161 |
|
| 162 |
|
| 163 |
-
def process(Prompt, used_model, image_width, image_height,
|
| 164 |
if Prompt == "":
|
| 165 |
gr.Info("Kein Prompt angegeben, es wird ein zufälliger Prompt generiert.", duration=12)
|
| 166 |
Prompt = groq_enhance_process("random prompt")
|
| 167 |
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
used_model = "turbo" if "schnell" in used_model.lower() else "flux" # turbo, flux
|
| 171 |
|
| 172 |
timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
|
| 173 |
filename_prompt = re.sub(r"[^\w\s-]", "", Prompt).strip().replace(" ", "_")
|
| 174 |
filename = timestamp + "_" + filename_prompt[:100] + ".png"
|
| 175 |
-
|
|
|
|
| 176 |
|
| 177 |
-
# Retry-Logik mit requests und Retry
|
| 178 |
session = Session()
|
| 179 |
-
retries = Retry(
|
| 180 |
-
total=3, status_forcelist=[429, 500, 502, 503, 504], backoff_factor=0.3, respect_retry_after_header=True
|
| 181 |
-
) # Max 3 Versuche # Codes, die wiederholt werden # Exponential Backoff # Retry-Header beachten
|
| 182 |
adapter = HTTPAdapter(max_retries=retries)
|
| 183 |
session.mount("https://", adapter)
|
| 184 |
session.mount("http://", adapter)
|
|
@@ -191,41 +209,67 @@ def process(Prompt, used_model, image_width, image_height, image_ratio, image_se
|
|
| 191 |
response = session.get(REQUEST_URL, timeout=60)
|
| 192 |
if response.status_code == 200:
|
| 193 |
print("REQUEST URL:\n" + REQUEST_URL + "\n\nImagine API Request solved")
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
print("Save image to: ")
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
|
| 207 |
return (
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
|
|
|
| 216 |
used_seed,
|
| 217 |
-
|
| 218 |
img_dominant_color,
|
| 219 |
used_seed,
|
|
|
|
| 220 |
)
|
| 221 |
else:
|
| 222 |
-
print("Imagine API Request ERROR")
|
| 223 |
-
raise gr.Error("Imagine API-Aufruf fehlgeschlagen 💥!", duration=15)
|
| 224 |
except requests.exceptions.Timeout:
|
| 225 |
raise gr.Error("⏰ Zeitüberschreitung beim API-Aufruf", duration=15)
|
| 226 |
except requests.exceptions.RequestException as e:
|
| 227 |
print(f"Unbekannter Fehler beim API-Aufruf: {e}")
|
| 228 |
-
raise gr.Error("Unbekannter Fehler beim API-Aufruf! 🤷♂️", duration=15)
|
| 229 |
|
| 230 |
|
| 231 |
def check_api(url):
|
|
@@ -235,25 +279,22 @@ def check_api(url):
|
|
| 235 |
return response.status_code
|
| 236 |
except requests.exceptions.RequestException as e:
|
| 237 |
print(f"An error occurred: {e}")
|
| 238 |
-
return
|
| 239 |
|
| 240 |
|
| 241 |
def get_inference_models():
|
| 242 |
-
|
| 243 |
status_api_1 = check_api(API1)
|
| 244 |
-
|
| 245 |
info_api_1 = "🟢API1👍" if status_api_1 == 200 else "🔴API1👎🔌"
|
| 246 |
-
|
| 247 |
api_models_1 = ["FLUX Dev", "Flux Schnell (Low-Res)"]
|
| 248 |
-
|
| 249 |
models = [model for model in api_models_1 if status_api_1 == 200]
|
| 250 |
info_api_status = f"Status: {info_api_1}"
|
| 251 |
-
|
| 252 |
-
return gr.update(choices=models, value=
|
| 253 |
|
| 254 |
|
| 255 |
-
# MARK: Gradio BLOCKS UI
|
| 256 |
-
with gr.Blocks(
|
|
|
|
| 257 |
with gr.Row(elem_classes="row-header"):
|
| 258 |
gr.Markdown(
|
| 259 |
f"""
|
|
@@ -279,207 +320,272 @@ with gr.Blocks(theme=theme, head=custom_head, css=custom_css, js=custom_js, titl
|
|
| 279 |
elem_classes="md-header",
|
| 280 |
)
|
| 281 |
|
| 282 |
-
with gr.
|
| 283 |
-
with gr.
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
)
|
| 306 |
-
with gr.Row():
|
| 307 |
-
random_prompt_button = gr.Button("", variant="secondary", elem_id="random_prompt_btn", elem_classes="random-prompt-btn", icon="_res/assets/star_light_48.png")
|
| 308 |
-
enhance_prompt_button = gr.Button(
|
| 309 |
-
"", variant="secondary", elem_id="enhance_prompt_btn", elem_classes="enhance-prompt-btn", icon="_res/assets/star_light_48.png"
|
| 310 |
-
)
|
| 311 |
-
run_button = gr.Button("Erstellen", variant="primary", elem_id="run_btn", elem_classes="run-btn", interactive=False)
|
| 312 |
-
with gr.Row(elem_classes="image_size_selctor_wrapper"):
|
| 313 |
-
with gr.Column(scale=1):
|
| 314 |
-
with gr.Row():
|
| 315 |
-
with gr.Column():
|
| 316 |
-
# inference_models, selected_model = get_inference_models()
|
| 317 |
-
# select_model = gr.Dropdown(choices=inference_models, value=selected_model, label="Model", elem_id="select_model", elem_classes="select-model")
|
| 318 |
-
select_model = gr.Dropdown(label="Model", elem_id="select_model", elem_classes="select-model")
|
| 319 |
-
# with gr.Row():
|
| 320 |
-
image_width = gr.Number(
|
| 321 |
-
label="Breite",
|
| 322 |
-
minimum=256,
|
| 323 |
-
maximum=MAX_IMAGE_SIZE,
|
| 324 |
-
value=576,
|
| 325 |
-
step=32,
|
| 326 |
-
elem_id="image_width_selector",
|
| 327 |
-
elem_classes="image-width-selector",
|
| 328 |
-
scale=1,
|
| 329 |
-
visible=False,
|
| 330 |
-
)
|
| 331 |
-
image_height = gr.Number(
|
| 332 |
-
label="Höhe",
|
| 333 |
-
minimum=256,
|
| 334 |
-
maximum=MAX_IMAGE_SIZE,
|
| 335 |
-
value=1024,
|
| 336 |
-
step=32,
|
| 337 |
-
elem_id="image_height_selector",
|
| 338 |
-
elem_classes="image-height-selector",
|
| 339 |
-
scale=1,
|
| 340 |
-
visible=False,
|
| 341 |
-
)
|
| 342 |
-
with gr.Row():
|
| 343 |
-
image_ratio_buttons = gr.Radio(
|
| 344 |
-
["9:16", "3:4", "2:3", "1:1"],
|
| 345 |
-
value="9:16",
|
| 346 |
-
label="Hochformat",
|
| 347 |
-
show_label=True,
|
| 348 |
-
info="Seitenverhältniss drehen",
|
| 349 |
-
interactive=True,
|
| 350 |
-
elem_id="image_ratio_buttons",
|
| 351 |
-
elem_classes="image-ratio-buttons",
|
| 352 |
-
container=True,
|
| 353 |
-
scale=2,
|
| 354 |
-
)
|
| 355 |
-
switch_width_height = gr.Button("", size="sm", elem_id="switch_width_height", elem_classes="switch-ratio-btn", variant="primary", scale=1)
|
| 356 |
with gr.Column():
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
label="
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
)
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 410 |
demo.load(
|
| 411 |
-
lambda
|
| 412 |
outputs=[run_button, enhance_prompt_button, random_prompt_button],
|
| 413 |
).then(get_inference_models, outputs=[select_model]).then(
|
| 414 |
-
lambda
|
| 415 |
outputs=[run_button, enhance_prompt_button, random_prompt_button],
|
| 416 |
)
|
| 417 |
|
| 418 |
-
def
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 465 |
image_ratio_buttons.input(
|
| 466 |
-
fn=calculate_ratio_values, inputs=[image_ratio_buttons], outputs=[image_ratio_buttons, image_width, image_height], show_progress="hidden",
|
| 467 |
)
|
| 468 |
|
| 469 |
run_button.click(
|
| 470 |
-
fn=lambda: (
|
| 471 |
outputs=[run_button, enhance_prompt_button, random_prompt_button],
|
| 472 |
-
|
| 473 |
-
js="() => document.querySelector('dotlottie-player').play()",
|
| 474 |
-
).then(
|
| 475 |
fn=process,
|
| 476 |
-
inputs=[text_prompt, select_model, image_width, image_height,
|
| 477 |
outputs=[
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
gallery,
|
|
|
|
|
|
|
| 481 |
output_url,
|
| 482 |
-
image_informations,
|
| 483 |
image_info_tb_prompt,
|
| 484 |
image_info_tb_width,
|
| 485 |
image_info_tb_height,
|
|
@@ -487,18 +593,21 @@ with gr.Blocks(theme=theme, head=custom_head, css=custom_css, js=custom_js, titl
|
|
| 487 |
image_download_button,
|
| 488 |
output_dominant_image_color,
|
| 489 |
image_seed,
|
|
|
|
| 490 |
],
|
|
|
|
| 491 |
).then(
|
| 492 |
-
fn=lambda: (
|
| 493 |
outputs=[run_button, enhance_prompt_button, random_prompt_button],
|
| 494 |
-
|
| 495 |
-
js="() => document.querySelector('dotlottie-player').stop()",
|
| 496 |
)
|
| 497 |
|
| 498 |
-
|
| 499 |
|
| 500 |
-
|
| 501 |
-
|
|
|
|
| 502 |
|
| 503 |
# MARK: Gradio LAUNCH
|
| 504 |
-
demo.launch(show_api=False)
|
|
|
|
| 4 |
import requests
|
| 5 |
from requests import Session
|
| 6 |
from requests.adapters import HTTPAdapter
|
| 7 |
+
|
| 8 |
+
# from requests.packages.urllib3.util.retry import Retry # Already imported via requests.utils
|
| 9 |
+
from urllib3.util.retry import Retry # More direct import
|
| 10 |
from datetime import datetime
|
| 11 |
import urllib.parse
|
| 12 |
+
from pathlib import Path
|
| 13 |
from groq import Groq
|
| 14 |
from exif import Image
|
| 15 |
+
from PIL import Image as PILImage, ExifTags as PILExifTags, PngImagePlugin
|
| 16 |
from io import BytesIO
|
| 17 |
import colorsys
|
| 18 |
import spaces
|
|
|
|
| 29 |
GROQ_APIKEY_PROMPTENHANCE = os.getenv("GROQ_APIKEY_PROMPTENHANCE")
|
| 30 |
API1 = urllib.parse.unquote(os.getenv("API1"))
|
| 31 |
|
| 32 |
+
BASE_DIR = Path(__file__).resolve().parent
|
| 33 |
+
RES = BASE_DIR / "_res"
|
| 34 |
+
ASSETS = RES / "assets"
|
| 35 |
+
IMAGE_DIR = BASE_DIR / "cache" / "images"
|
| 36 |
+
IMAGE_DIR.mkdir(parents=True, exist_ok=True)
|
| 37 |
|
| 38 |
+
gr.set_static_paths(paths=[str(RES), str("cache/images"), str(ASSETS)])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
+
enhance_systemmessage_json = RES / "groq_systemmessage_prompt_enhance_new.json"
|
| 41 |
+
with open(enhance_systemmessage_json, "r") as f:
|
| 42 |
+
enhance_systemmessage = json.load(f)
|
| 43 |
|
| 44 |
+
custom_css_path = RES / "_custom.css"
|
| 45 |
+
custom_js_path = RES / "_custom.js"
|
| 46 |
+
with open(custom_css_path, "r") as f:
|
| 47 |
+
custom_css = f.read()
|
| 48 |
+
with open(custom_js_path, "r") as f:
|
| 49 |
+
custom_js = f.read()
|
| 50 |
|
| 51 |
custom_head = f"""
|
| 52 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css"/>
|
| 53 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/js/all.min.js"></script>
|
| 54 |
+
<link rel="apple-touch-icon" sizes="180x180" href="file={ASSETS / 'favicons/apple-touch-icon.png'}">
|
| 55 |
+
<link rel="icon" type="image/png" sizes="32x32" href="file={ASSETS / 'favicons/favicon-32x32.png'}">
|
| 56 |
+
<link rel="icon" type="image/png" sizes="16x16" href="file={ASSETS / 'favicons/favicon-16x16.png'}">
|
| 57 |
+
<link rel="icon" type="image/x-icon" href="file={ASSETS / 'favicons/favicon.ico'}">
|
| 58 |
+
<link rel="manifest" href="file={ASSETS / 'favicons/site.webmanifest'}">
|
| 59 |
+
<style>{custom_css}</style>
|
| 60 |
<script src="https://unpkg.com/@dotlottie/player-component@latest/dist/dotlottie-player.mjs" type="module"></script>
|
| 61 |
<script src='https://storage.ko-fi.com/cdn/widget/Widget_2.js'></script>
|
| 62 |
"""
|
| 63 |
|
| 64 |
theme = gr.themes.Soft(
|
|
|
|
| 65 |
radius_size="sm",
|
| 66 |
neutral_hue=gr.themes.Color(
|
| 67 |
c100="#a6adc8",
|
|
|
|
| 77 |
c950="#11111b",
|
| 78 |
),
|
| 79 |
)
|
|
|
|
| 80 |
title = "Bilder Builder"
|
| 81 |
|
| 82 |
|
| 83 |
# MARK: READ EXIF
|
| 84 |
def read_exif(image_path):
|
| 85 |
+
try:
|
| 86 |
+
with open(image_path, "rb") as src:
|
| 87 |
+
img = Image(src) # This is where TiffByteOrder error can happen from exif library
|
| 88 |
+
if not hasattr(img, "user_comment") or not img.user_comment:
|
| 89 |
+
print(f"Warning: No user_comment found in EXIF for {image_path}")
|
| 90 |
+
return {} # Return empty dict if no user_comment
|
| 91 |
+
try:
|
| 92 |
+
img_comment = json.loads(img.user_comment)
|
| 93 |
+
except json.JSONDecodeError:
|
| 94 |
+
print(f"Warning: Could not decode user_comment JSON from EXIF for {image_path}")
|
| 95 |
+
return {} # Return empty dict if JSON is invalid
|
| 96 |
+
|
| 97 |
+
if "concept" in img_comment: # checking if the key exists before removing
|
| 98 |
+
img_comment.pop("concept")
|
| 99 |
+
return img_comment
|
| 100 |
+
except ValueError as ve: # Catch specific errors like TiffByteOrder from the exif library
|
| 101 |
+
print(f"EXIF parsing ValueError for {image_path}: {ve}")
|
| 102 |
+
return {} # Return empty dict on this error
|
| 103 |
+
except Exception as e: # Catch other potential errors during EXIF processing
|
| 104 |
+
print(f"Unexpected error reading EXIF for {image_path}: {e}")
|
| 105 |
+
return {}
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def read_png_metadata(image_path):
|
| 109 |
+
try:
|
| 110 |
+
img_pil = PILImage.open(image_path)
|
| 111 |
+
if "user_comment" in img_pil.info:
|
| 112 |
+
metadata_json_string = img_pil.info["user_comment"]
|
| 113 |
+
try:
|
| 114 |
+
img_comment = json.loads(metadata_json_string)
|
| 115 |
+
if "concept" in img_comment:
|
| 116 |
+
img_comment.pop("concept")
|
| 117 |
+
return img_comment
|
| 118 |
+
except json.JSONDecodeError:
|
| 119 |
+
print(f"Warning: Could not decode user_comment JSON from PNG info for {image_path}")
|
| 120 |
+
return {}
|
| 121 |
+
else:
|
| 122 |
+
print(f"Warning: No 'user_comment' found in PNG info for {image_path}")
|
| 123 |
+
return {}
|
| 124 |
+
except Exception as e:
|
| 125 |
+
print(f"Error reading PNG metadata for {image_path}: {e}")
|
| 126 |
+
return {}
|
| 127 |
|
| 128 |
|
| 129 |
# MARK: GROQ PROMPT ENHANCE
|
| 130 |
def groq_enhance_process(Prompt=""):
|
| 131 |
client = Groq(api_key=GROQ_APIKEY_PROMPTENHANCE)
|
| 132 |
Prompt = "random prompt" if Prompt == "" else Prompt
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
completion = client.chat.completions.create(
|
| 134 |
model="meta-llama/llama-4-scout-17b-16e-instruct",
|
| 135 |
+
messages=[enhance_systemmessage, {"role": "user", "content": Prompt}],
|
| 136 |
temperature=1,
|
| 137 |
max_tokens=512,
|
| 138 |
top_p=0.9,
|
|
|
|
| 140 |
seed=random.randint(0, MAX_SEED),
|
| 141 |
stop=None,
|
| 142 |
)
|
|
|
|
| 143 |
if completion.choices[0].message.content != "":
|
| 144 |
enhanced_prompt = completion.choices[0].message.content
|
| 145 |
enhanced_prompt = re.sub(r"[\.\"]+", "", enhanced_prompt)
|
|
|
|
| 146 |
return enhanced_prompt
|
| 147 |
|
| 148 |
|
| 149 |
def image_get_size(image_path):
|
| 150 |
img = PILImage.open(image_path)
|
|
|
|
| 151 |
width, height = img.size
|
| 152 |
return width, height
|
| 153 |
|
|
|
|
| 156 |
def image_get_dominant_color(image_path):
|
| 157 |
img = PILImage.open(image_path)
|
| 158 |
img = img.convert("RGB")
|
| 159 |
+
img = img.resize((100, 100), resample=PILImage.Resampling.NEAREST) # Use Resampling enum
|
| 160 |
pixels = list(img.getdata())
|
|
|
|
|
|
|
| 161 |
colors = []
|
| 162 |
for pixel in pixels:
|
| 163 |
r, g, b = pixel
|
| 164 |
h, s, v = colorsys.rgb_to_hsv(r / 255, g / 255, b / 255)
|
| 165 |
+
if v > 0.5:
|
| 166 |
continue
|
| 167 |
+
if v > 0.99:
|
| 168 |
continue
|
| 169 |
colors.append((h, s, v))
|
| 170 |
+
if not colors: # Handle case where all pixels are filtered out
|
| 171 |
+
return "rgb(0,0,0)" # Default to black or handle as error
|
| 172 |
dominant_color = max(colors, key=lambda x: x[2])
|
| 173 |
dominant_color_rgb = colorsys.hsv_to_rgb(dominant_color[0], dominant_color[1], dominant_color[2])
|
| 174 |
dominant_color_rgb = [int(c * 255) for c in dominant_color_rgb]
|
| 175 |
+
dominant_color_rgb_str = f"rgb({dominant_color_rgb[0]}, {dominant_color_rgb[1]}, {dominant_color_rgb[2]})"
|
| 176 |
+
return dominant_color_rgb_str
|
|
|
|
|
|
|
| 177 |
|
| 178 |
|
| 179 |
# MARK: CLEAR COMPONENTS
|
| 180 |
def clear_components():
|
| 181 |
+
return gr.update(value=None, visible=False), gr.update(visible=False)
|
| 182 |
|
| 183 |
|
| 184 |
+
def process(Prompt, used_model, image_width, image_height, image_seed, p_randomize_seed, current_gallery_items):
|
| 185 |
if Prompt == "":
|
| 186 |
gr.Info("Kein Prompt angegeben, es wird ein zufälliger Prompt generiert.", duration=12)
|
| 187 |
Prompt = groq_enhance_process("random prompt")
|
| 188 |
|
| 189 |
+
used_seed = random.randint(0, MAX_SEED) if image_seed == 0 or p_randomize_seed else image_seed
|
| 190 |
+
used_model = "turbo" if "schnell" in used_model.lower() else "flux"
|
|
|
|
| 191 |
|
| 192 |
timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
|
| 193 |
filename_prompt = re.sub(r"[^\w\s-]", "", Prompt).strip().replace(" ", "_")
|
| 194 |
filename = timestamp + "_" + filename_prompt[:100] + ".png"
|
| 195 |
+
absolute_file_path = IMAGE_DIR / filename
|
| 196 |
+
relative_image_path = f"cache/images/{filename}"
|
| 197 |
|
|
|
|
| 198 |
session = Session()
|
| 199 |
+
retries = Retry(total=3, status_forcelist=[429, 500, 502, 503, 504], backoff_factor=0.3, respect_retry_after_header=True)
|
|
|
|
|
|
|
| 200 |
adapter = HTTPAdapter(max_retries=retries)
|
| 201 |
session.mount("https://", adapter)
|
| 202 |
session.mount("http://", adapter)
|
|
|
|
| 209 |
response = session.get(REQUEST_URL, timeout=60)
|
| 210 |
if response.status_code == 200:
|
| 211 |
print("REQUEST URL:\n" + REQUEST_URL + "\n\nImagine API Request solved")
|
| 212 |
+
img_pil = PILImage.open(BytesIO(response.content))
|
| 213 |
+
img_pil.save(absolute_file_path, "png")
|
| 214 |
+
print("Save image to: ", absolute_file_path)
|
| 215 |
+
|
| 216 |
+
img_dominant_color = image_get_dominant_color(absolute_file_path)
|
| 217 |
+
img_width_actual, img_height_actual = image_get_size(absolute_file_path) # Use actual size
|
| 218 |
+
|
| 219 |
+
image_metadata = {
|
| 220 |
+
"prompt": Prompt,
|
| 221 |
+
"seed": used_seed,
|
| 222 |
+
"model": used_model,
|
| 223 |
+
"dominant_color": img_dominant_color,
|
| 224 |
+
"width": img_width_actual,
|
| 225 |
+
"height": img_height_actual,
|
| 226 |
+
"timestamp": timestamp,
|
| 227 |
+
}
|
| 228 |
+
metadata_json_string = json.dumps(image_metadata)
|
| 229 |
+
|
| 230 |
+
png_info_to_add = PngImagePlugin.PngInfo()
|
| 231 |
+
png_info_to_add.add_text("user_comment", metadata_json_string)
|
| 232 |
+
|
| 233 |
+
try:
|
| 234 |
+
# Re-save the image, this time embedding the PngInfo metadata
|
| 235 |
+
img_pil.save(absolute_file_path, "png", pnginfo=png_info_to_add)
|
| 236 |
+
print(f"Image re-saved with PNG metadata: {absolute_file_path}")
|
| 237 |
+
except Exception as e:
|
| 238 |
+
# This error would be if saving with PngInfo fails for some reason
|
| 239 |
+
print(f"Warning: Could not save PNG with embedded metadata for {absolute_file_path}. Error: {e}")
|
| 240 |
+
# The file 'absolute_file_path' would still exist from the temporary save, but without our metadata.
|
| 241 |
+
|
| 242 |
+
new_gallery_item_state_data = (str(absolute_file_path), Prompt, used_seed)
|
| 243 |
+
updated_gallery_state_list = [item for item in current_gallery_items] if current_gallery_items else []
|
| 244 |
+
updated_gallery_state_list.append(new_gallery_item_state_data)
|
| 245 |
+
|
| 246 |
+
all_image_filepaths_for_gallery_files = [item[0] for item in updated_gallery_state_list][::-1]
|
| 247 |
+
gallery_display_list = [(item[0], item[1]) for item in updated_gallery_state_list][::-1]
|
| 248 |
|
| 249 |
return (
|
| 250 |
+
updated_gallery_state_list, # 1 generated_image
|
| 251 |
+
all_image_filepaths_for_gallery_files, # 2 gallery_files
|
| 252 |
+
gr.update(value=gallery_display_list, selected_index=0), # 3 gallery
|
| 253 |
+
gr.update(value=Prompt), # 4 Text Prompt
|
| 254 |
+
gr.update(value=str(absolute_file_path), visible=False), # 5 output_image
|
| 255 |
+
gr.update(value=relative_image_path, visible=True),
|
| 256 |
+
gr.update(value=Prompt, visible=True),
|
| 257 |
+
img_width_actual, # Use actual width
|
| 258 |
+
img_height_actual, # Use actual height
|
| 259 |
used_seed,
|
| 260 |
+
gr.update(value=str(absolute_file_path), visible=True),
|
| 261 |
img_dominant_color,
|
| 262 |
used_seed,
|
| 263 |
+
gr.update(visible=True),
|
| 264 |
)
|
| 265 |
else:
|
| 266 |
+
print(f"Imagine API Request ERROR, Status: {response.status_code}, Response: {response.text}")
|
| 267 |
+
raise gr.Error(f"Imagine API-Aufruf fehlgeschlagen (Code: {response.status_code}) 💥!", duration=15)
|
| 268 |
except requests.exceptions.Timeout:
|
| 269 |
raise gr.Error("⏰ Zeitüberschreitung beim API-Aufruf", duration=15)
|
| 270 |
except requests.exceptions.RequestException as e:
|
| 271 |
print(f"Unbekannter Fehler beim API-Aufruf: {e}")
|
| 272 |
+
raise gr.Error(f"Unbekannter Fehler beim API-Aufruf! 🤷♂️ {e}", duration=15)
|
| 273 |
|
| 274 |
|
| 275 |
def check_api(url):
|
|
|
|
| 279 |
return response.status_code
|
| 280 |
except requests.exceptions.RequestException as e:
|
| 281 |
print(f"An error occurred: {e}")
|
| 282 |
+
return 999
|
| 283 |
|
| 284 |
|
| 285 |
def get_inference_models():
|
|
|
|
| 286 |
status_api_1 = check_api(API1)
|
|
|
|
| 287 |
info_api_1 = "🟢API1👍" if status_api_1 == 200 else "🔴API1👎🔌"
|
|
|
|
| 288 |
api_models_1 = ["FLUX Dev", "Flux Schnell (Low-Res)"]
|
|
|
|
| 289 |
models = [model for model in api_models_1 if status_api_1 == 200]
|
| 290 |
info_api_status = f"Status: {info_api_1}"
|
| 291 |
+
default_model = models[0] if models else None
|
| 292 |
+
return gr.update(choices=models, value=default_model, interactive=bool(models), info=info_api_status)
|
| 293 |
|
| 294 |
|
| 295 |
+
# MARK: Gradio BLOCKS UI css=custom_css
|
| 296 |
+
with gr.Blocks(js=custom_js, head=custom_head, title=title, theme=theme) as demo:
|
| 297 |
+
# ... (Your existing UI header markdown) ...
|
| 298 |
with gr.Row(elem_classes="row-header"):
|
| 299 |
gr.Markdown(
|
| 300 |
f"""
|
|
|
|
| 320 |
elem_classes="md-header",
|
| 321 |
)
|
| 322 |
|
| 323 |
+
with gr.Row(elem_classes="row-main"):
|
| 324 |
+
with gr.Column(scale=2):
|
| 325 |
+
generated_images = gr.State([])
|
| 326 |
+
# generated_images_count = gr.State(0) # This state was unused
|
| 327 |
+
with gr.Row():
|
| 328 |
+
placeholder_text = "[???] Generiert dir einen zufälligen Prompt.\n[STERN] optimiert deinen eignen Prompt.\n[RUN] generiert dein Bild."
|
| 329 |
+
text_prompt = gr.Textbox(
|
| 330 |
+
label="Prompt",
|
| 331 |
+
show_label=False,
|
| 332 |
+
lines=12,
|
| 333 |
+
max_lines=18,
|
| 334 |
+
placeholder=placeholder_text,
|
| 335 |
+
elem_id="prompt_input",
|
| 336 |
+
elem_classes="prompt-input hide-progress",
|
| 337 |
+
autofocus=True,
|
| 338 |
+
)
|
| 339 |
+
with gr.Row():
|
| 340 |
+
random_prompt_button = gr.Button("", variant="secondary", elem_id="random_prompt_btn", elem_classes="random-prompt-btn", icon=str(ASSETS / "random.png"))
|
| 341 |
+
enhance_prompt_button = gr.Button("", variant="secondary", elem_id="enhance_prompt_btn", elem_classes="enhance-prompt-btn", icon=str(ASSETS / "star_light_48.png"))
|
| 342 |
+
run_button = gr.Button("Erstellen", variant="primary", elem_id="run_btn", elem_classes="run-btn", interactive=False)
|
| 343 |
+
with gr.Row(elem_classes="image_size_selctor_wrapper"):
|
| 344 |
+
with gr.Column(scale=1):
|
| 345 |
+
with gr.Row():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 346 |
with gr.Column():
|
| 347 |
+
select_model = gr.Dropdown(label="Model", elem_id="select_model", elem_classes="select-model")
|
| 348 |
+
image_width = gr.Number(
|
| 349 |
+
label="Breite",
|
| 350 |
+
minimum=256,
|
| 351 |
+
maximum=MAX_IMAGE_SIZE,
|
| 352 |
+
value=576,
|
| 353 |
+
step=32,
|
| 354 |
+
elem_id="image_width_selector",
|
| 355 |
+
elem_classes="image-width-selector",
|
| 356 |
+
scale=1,
|
| 357 |
+
visible=False,
|
| 358 |
)
|
| 359 |
+
image_height = gr.Number(
|
| 360 |
+
label="Höhe",
|
| 361 |
+
minimum=256,
|
| 362 |
+
maximum=MAX_IMAGE_SIZE,
|
| 363 |
+
value=1024,
|
| 364 |
+
step=32,
|
| 365 |
+
elem_id="image_height_selector",
|
| 366 |
+
elem_classes="image-height-selector",
|
| 367 |
+
scale=1,
|
| 368 |
+
visible=False,
|
| 369 |
+
)
|
| 370 |
+
with gr.Row():
|
| 371 |
+
image_ratio_buttons = gr.Radio(
|
| 372 |
+
["9:16", "3:4", "2:3", "1:1"],
|
| 373 |
+
value="9:16",
|
| 374 |
+
label="Hochformat",
|
| 375 |
+
show_label=True,
|
| 376 |
+
info="Seitenverhältniss drehen",
|
| 377 |
+
interactive=True,
|
| 378 |
+
elem_id="image_ratio_buttons",
|
| 379 |
+
elem_classes="image-ratio-buttons",
|
| 380 |
+
container=True,
|
| 381 |
+
scale=2,
|
| 382 |
+
)
|
| 383 |
+
switch_width_height = gr.Button("", size="sm", elem_id="switch_width_height", elem_classes="switch-ratio-btn", variant="primary", scale=1)
|
| 384 |
+
with gr.Column():
|
| 385 |
+
randomize_seed = gr.Checkbox(label="Randomize seed", value=True, elem_classes="random-seed-cb toggle-btn")
|
| 386 |
+
image_seed = gr.Slider(
|
| 387 |
+
label="Seed",
|
| 388 |
+
info="Jeder Seed generiert ein anderes Bild mit dem selben Prompt",
|
| 389 |
+
minimum=0,
|
| 390 |
+
step=1,
|
| 391 |
+
value=42,
|
| 392 |
+
maximum=MAX_SEED,
|
| 393 |
+
elem_id="image_seed",
|
| 394 |
+
elem_classes="image-seed hide-progress",
|
| 395 |
+
interactive=False,
|
| 396 |
+
)
|
| 397 |
+
|
| 398 |
+
with gr.Column(scale=4):
|
| 399 |
+
with gr.Row():
|
| 400 |
+
with gr.Column(scale=3): # Changed scale for better layout with info panel
|
| 401 |
+
with gr.Row():
|
| 402 |
+
output_image = gr.Image(
|
| 403 |
+
show_label=False, min_width=320, elem_id="output_image", elem_classes="output-image", type="filepath", format="webp", visible=False
|
| 404 |
+
) # format="png" might be better if webp causes issues with EXIF
|
| 405 |
+
|
| 406 |
+
gallery = gr.Gallery(
|
| 407 |
+
label="Bisher erstellte Bilder",
|
| 408 |
+
show_label=False,
|
| 409 |
+
format="png",
|
| 410 |
+
elem_id="gallery",
|
| 411 |
+
columns=[4],
|
| 412 |
+
rows=[4],
|
| 413 |
+
object_fit="contain",
|
| 414 |
+
height="auto",
|
| 415 |
+
type="filepath",
|
| 416 |
+
preview=True,
|
| 417 |
+
allow_preview=True,
|
| 418 |
+
)
|
| 419 |
+
|
| 420 |
+
with gr.Row():
|
| 421 |
+
gallery_files = gr.Files(visible=False, elem_id="gallery_files", elem_classes="gallery-files", interactive=False, label="Generierte Bilddateien")
|
| 422 |
+
|
| 423 |
+
with gr.Column(scale=1, visible=False, elem_classes="image-info-wrapper") as image_info_wrapper:
|
| 424 |
+
with gr.Accordion("Bild Informationen", open=False):
|
| 425 |
+
# with gr.Group():
|
| 426 |
+
# gr.Markdown(f"""## Bildinformationen""") # Removed visible=False as parent controls it
|
| 427 |
+
image_info_tb_prompt = gr.Textbox(label="Bild Prompt", lines=18, interactive=False, elem_classes="hide-progress", show_copy_button=True, visible=True)
|
| 428 |
+
with gr.Row(elem_classes="img-size-wrapper"):
|
| 429 |
+
image_info_tb_width = gr.Textbox(label="Breite", lines=1, max_lines=1, interactive=False, show_copy_button=True, elem_classes="image-info-tb-width")
|
| 430 |
+
image_info_tb_height = gr.Textbox(label="Höhe", lines=1, max_lines=1, interactive=False, show_copy_button=True, elem_classes="image-info-tb-height")
|
| 431 |
+
with gr.Row(elem_classes="img-seed-wrapper"):
|
| 432 |
+
image_info_tb_seed = gr.Textbox(label="Seed", lines=1, max_lines=1, interactive=False, show_copy_button=True, elem_classes="image-info-tb-seed")
|
| 433 |
+
# Made visible as parent controls wrapper
|
| 434 |
+
image_download_button = gr.DownloadButton("Bild herunterladen", value=None, elem_classes="download-button", variant="primary", visible=True) # Made visible
|
| 435 |
+
|
| 436 |
+
output_url = gr.Textbox(label="Output URL", show_label=True, interactive=False, visible=False)
|
| 437 |
+
# outpu_image_comment = gr.Json(visible=False) # This was unused
|
| 438 |
+
output_dominant_image_color = gr.Textbox(show_label=False, elem_id="dominant_image_color", visible=True, elem_classes="output-dominant-image-color")
|
| 439 |
+
|
| 440 |
+
# MARK: Functionen after
|
| 441 |
demo.load(
|
| 442 |
+
lambda: (gr.update(interactive=False), gr.update(interactive=False), gr.update(interactive=False)),
|
| 443 |
outputs=[run_button, enhance_prompt_button, random_prompt_button],
|
| 444 |
).then(get_inference_models, outputs=[select_model]).then(
|
| 445 |
+
lambda: (gr.update(interactive=True), gr.update(interactive=True), gr.update(interactive=True)),
|
| 446 |
outputs=[run_button, enhance_prompt_button, random_prompt_button],
|
| 447 |
)
|
| 448 |
|
| 449 |
+
def gallery_select_handler(evt: gr.SelectData, gallery_files):
|
| 450 |
+
# For debugging, to understand what evt.value is:
|
| 451 |
+
# print(f"DEBUG: gallery_select_handler called. evt: {evt}")
|
| 452 |
+
# print(f"DEBUG: evt.value: {evt.value}, type(evt.value): {type(evt.value)}")
|
| 453 |
+
# print(f"DEBUG: evt.index: {evt.index}, evt.selected: {evt.selected}")
|
| 454 |
+
|
| 455 |
+
selected_absolute_path = None
|
| 456 |
+
# Safely try to get the filepath from evt.value
|
| 457 |
+
# evt.value is expected to be a tuple: (filepath, caption)
|
| 458 |
+
if isinstance(gallery_files[evt.index], str) and gallery_files[evt.index]:
|
| 459 |
+
selected_absolute_path = gallery_files[evt.index]
|
| 460 |
+
|
| 461 |
+
# If no valid path was extracted, or if evt.value was not as expected (e.g. None, empty dict, etc.)
|
| 462 |
+
if not selected_absolute_path:
|
| 463 |
+
gr.Info("Kein Bild aus der Galerie ausgewählt, Pfad ist leer oder ungültiger Wert.")
|
| 464 |
+
return (
|
| 465 |
+
gr.update(value=None, visible=False), # 1. output_image
|
| 466 |
+
gr.update(value="", visible=False), # 2. output_url
|
| 467 |
+
gr.update(value="", visible=True), # 3. image_info_tb_prompt (clear value)
|
| 468 |
+
"", # 4. image_info_tb_width
|
| 469 |
+
"", # 5. image_info_tb_height
|
| 470 |
+
"", # 6. image_info_tb_seed
|
| 471 |
+
gr.update(value=None, visible=False), # 7. image_download_button
|
| 472 |
+
gr.update(visible=False), # 8. image_info_wrapper (hide the whole section)
|
| 473 |
+
)
|
| 474 |
+
|
| 475 |
+
# print(f"Gallery selected image path: {selected_absolute_path}") # Keep for your debugging
|
| 476 |
+
|
| 477 |
+
if not os.path.exists(selected_absolute_path):
|
| 478 |
+
gr.Warning(f"Ausgewählte Bilddatei nicht gefunden: {selected_absolute_path}")
|
| 479 |
+
return (
|
| 480 |
+
gr.update(value=None, visible=False),
|
| 481 |
+
gr.update(value="", visible=False),
|
| 482 |
+
gr.update(value="Datei nicht gefunden", visible=True),
|
| 483 |
+
"N/A",
|
| 484 |
+
"N/A",
|
| 485 |
+
"N/A",
|
| 486 |
+
gr.update(value=None, visible=False),
|
| 487 |
+
gr.update(visible=True), # Show wrapper with error
|
| 488 |
+
)
|
| 489 |
+
|
| 490 |
+
relative_path_for_url_tb = "cache/images/" + Path(selected_absolute_path).name
|
| 491 |
+
|
| 492 |
+
try:
|
| 493 |
+
exif_data = read_png_metadata(selected_absolute_path)
|
| 494 |
+
|
| 495 |
+
selected_prompt = exif_data.get("prompt", "N/A (EXIF Fehler)")
|
| 496 |
+
img_width = exif_data.get("width", "N/A")
|
| 497 |
+
img_height = exif_data.get("height", "N/A")
|
| 498 |
+
selected_seed = exif_data.get("seed", "N/A")
|
| 499 |
+
|
| 500 |
+
return (
|
| 501 |
+
gr.update(value=selected_absolute_path, visible=False),
|
| 502 |
+
gr.update(value=relative_path_for_url_tb, visible=False), # output_url can be visible
|
| 503 |
+
gr.update(value=selected_prompt, visible=True),
|
| 504 |
+
str(img_width),
|
| 505 |
+
str(img_height),
|
| 506 |
+
str(selected_seed),
|
| 507 |
+
gr.update(value=selected_absolute_path, visible=True),
|
| 508 |
+
gr.update(visible=True),
|
| 509 |
+
)
|
| 510 |
+
except Exception as e: # Catch any other unexpected error during this block
|
| 511 |
+
print(f"Error in gallery_select_handler after path validation: {e}")
|
| 512 |
+
gr.Warning(f"Fehler beim Verarbeiten der Bilddetails: {e}")
|
| 513 |
+
# Fallback if try block fails
|
| 514 |
+
return (
|
| 515 |
+
gr.update(value=selected_absolute_path, visible=False), # Still show image if path is valid
|
| 516 |
+
gr.update(value=relative_path_for_url_tb, visible=True),
|
| 517 |
+
gr.update(value="Fehler beim Lesen der Details", visible=True),
|
| 518 |
+
"Fehler",
|
| 519 |
+
"Fehler",
|
| 520 |
+
"Fehler",
|
| 521 |
+
gr.update(value=selected_absolute_path, visible=True), # Download button for the image
|
| 522 |
+
gr.update(visible=True), # Show info wrapper
|
| 523 |
+
)
|
| 524 |
+
|
| 525 |
+
gallery.select(
|
| 526 |
+
fn=gallery_select_handler,
|
| 527 |
+
inputs=[gallery_files], # evt is passed automatically by gr.SelectData
|
| 528 |
+
# outputs=[debug_text],
|
| 529 |
+
outputs=[
|
| 530 |
+
output_image,
|
| 531 |
+
output_url,
|
| 532 |
+
image_info_tb_prompt,
|
| 533 |
+
image_info_tb_width,
|
| 534 |
+
image_info_tb_height,
|
| 535 |
+
image_info_tb_seed,
|
| 536 |
+
image_download_button,
|
| 537 |
+
output_dominant_image_color, # Add if you want to update this too
|
| 538 |
+
],
|
| 539 |
+
show_progress="hidden",
|
| 540 |
+
)
|
| 541 |
|
| 542 |
+
# gallery_files.change(fn=lambda state_data: [item[0] for item in state_data], inputs=[gallery_files], outputs=[gallery_files], show_progress="hidden", api_name=False)
|
| 543 |
+
|
| 544 |
+
def switch_image_size_values(w, h):
|
| 545 |
+
return h, w
|
| 546 |
+
|
| 547 |
+
def switch_image_ratio_buttons(ratio_val):
|
| 548 |
+
parts = ratio_val.split(":")
|
| 549 |
+
new_ratio = f"{parts[1]}:{parts[0]}"
|
| 550 |
+
is_landscape = int(parts[1]) > int(parts[0])
|
| 551 |
+
is_portrait = int(parts[1]) < int(parts[0])
|
| 552 |
+
choices = ["16:9", "4:3", "3:2", "1:1"] if is_landscape else ["9:16", "3:4", "2:3", "1:1"]
|
| 553 |
+
label = "Querformat" if is_landscape else "Hochformat" if is_portrait else "Quadratisch"
|
| 554 |
+
return gr.update(choices=choices, value=new_ratio, label=label)
|
| 555 |
+
|
| 556 |
+
def calculate_ratio_values(ratio_str):
|
| 557 |
+
w_s, h_s = map(int, ratio_str.split(":"))
|
| 558 |
+
base_dim = 1024
|
| 559 |
+
if w_s > h_s: # Landscape or square
|
| 560 |
+
new_w = base_dim
|
| 561 |
+
new_h = round(base_dim * h_s / w_s / 8) * 8
|
| 562 |
+
else: # Portrait
|
| 563 |
+
new_h = base_dim
|
| 564 |
+
new_w = round(base_dim * w_s / h_s / 8) * 8
|
| 565 |
+
label = "Querformat" if w_s > h_s else "Hochformat" if w_s < h_s else "Quadratisch"
|
| 566 |
+
return gr.update(label=label), new_w, new_h
|
| 567 |
+
|
| 568 |
+
switch_width_height.click(fn=switch_image_size_values, inputs=[image_width, image_height], outputs=[image_width, image_height], show_progress="hidden", api_name=False)
|
| 569 |
+
switch_width_height.click(fn=switch_image_ratio_buttons, inputs=[image_ratio_buttons], outputs=[image_ratio_buttons], show_progress="hidden", api_name=False)
|
| 570 |
image_ratio_buttons.input(
|
| 571 |
+
fn=calculate_ratio_values, inputs=[image_ratio_buttons], outputs=[image_ratio_buttons, image_width, image_height], show_progress="hidden", api_name=False
|
| 572 |
)
|
| 573 |
|
| 574 |
run_button.click(
|
| 575 |
+
fn=lambda: (gr.update(interactive=False), gr.update(interactive=False), gr.update(interactive=False)),
|
| 576 |
outputs=[run_button, enhance_prompt_button, random_prompt_button],
|
| 577 |
+
api_name=False,
|
| 578 |
+
js="() => { const player = document.querySelector('dotlottie-player'); if (player) player.play(); }",
|
| 579 |
+
).then(
|
| 580 |
fn=process,
|
| 581 |
+
inputs=[text_prompt, select_model, image_width, image_height, image_seed, randomize_seed, generated_images],
|
| 582 |
outputs=[
|
| 583 |
+
generated_images, # 1 generated_image
|
| 584 |
+
gallery_files, # 2 gallery_files
|
| 585 |
+
gallery, # 3 gallery
|
| 586 |
+
text_prompt, # 4 Text Prompt
|
| 587 |
+
output_image, # 5 output_image
|
| 588 |
output_url,
|
|
|
|
| 589 |
image_info_tb_prompt,
|
| 590 |
image_info_tb_width,
|
| 591 |
image_info_tb_height,
|
|
|
|
| 593 |
image_download_button,
|
| 594 |
output_dominant_image_color,
|
| 595 |
image_seed,
|
| 596 |
+
image_info_wrapper,
|
| 597 |
],
|
| 598 |
+
show_progress="hidden",
|
| 599 |
).then(
|
| 600 |
+
fn=lambda: (gr.update(interactive=True), gr.update(interactive=True), gr.update(interactive=True)),
|
| 601 |
outputs=[run_button, enhance_prompt_button, random_prompt_button],
|
| 602 |
+
api_name=False,
|
| 603 |
+
js="() => { const player = document.querySelector('dotlottie-player'); if (player) player.stop(); }",
|
| 604 |
)
|
| 605 |
|
| 606 |
+
generated_images.change(fn=lambda state_data: [item[0] for item in state_data] if state_data else [], inputs=[generated_images], outputs=[gallery_files], api_name=False)
|
| 607 |
|
| 608 |
+
randomize_seed.input(lambda x: gr.update(interactive=not x), inputs=[randomize_seed], outputs=[image_seed], api_name=False)
|
| 609 |
+
enhance_prompt_button.click(fn=groq_enhance_process, inputs=[text_prompt], outputs=[text_prompt], api_name=False)
|
| 610 |
+
random_prompt_button.click(fn=groq_enhance_process, inputs=None, outputs=[text_prompt], api_name=False)
|
| 611 |
|
| 612 |
# MARK: Gradio LAUNCH
|
| 613 |
+
demo.launch(show_api=False, debug=True)
|
requirements.txt
CHANGED
|
@@ -1,65 +1,7 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
contourpy==1.3.1
|
| 9 |
-
cycler==0.12.1
|
| 10 |
-
distro==1.9.0
|
| 11 |
-
exif==1.6.1
|
| 12 |
-
fastapi==0.115.6
|
| 13 |
-
ffmpy==0.5.0
|
| 14 |
-
filelock==3.16.1
|
| 15 |
-
fonttools==4.55.3
|
| 16 |
-
fsspec==2024.12.0
|
| 17 |
-
gradio==4.44.1
|
| 18 |
-
gradio_client==1.3.0
|
| 19 |
-
groq==0.15.0
|
| 20 |
-
h11==0.14.0
|
| 21 |
-
httpcore==1.0.7
|
| 22 |
-
httpx==0.28.1
|
| 23 |
-
huggingface-hub==0.27.1
|
| 24 |
-
idna==3.10
|
| 25 |
-
importlib_resources==6.5.2
|
| 26 |
-
Jinja2==3.1.5
|
| 27 |
-
kiwisolver==1.4.8
|
| 28 |
-
markdown-it-py==3.0.0
|
| 29 |
-
MarkupSafe==2.1.5
|
| 30 |
-
matplotlib==3.10.0
|
| 31 |
-
mdurl==0.1.2
|
| 32 |
-
numpy==2.2.1
|
| 33 |
-
orjson==3.10.14
|
| 34 |
-
packaging==24.2
|
| 35 |
-
pandas==2.2.3
|
| 36 |
-
pillow==10.4.0
|
| 37 |
-
plum-py==0.8.7
|
| 38 |
-
psutil==5.9.8
|
| 39 |
-
pydantic==2.10.5
|
| 40 |
-
pydantic_core==2.27.2
|
| 41 |
-
pydub==0.25.1
|
| 42 |
-
Pygments==2.19.1
|
| 43 |
-
pyparsing==3.2.1
|
| 44 |
-
python-dateutil==2.9.0.post0
|
| 45 |
-
python-dotenv==1.0.1
|
| 46 |
-
python-multipart==0.0.20
|
| 47 |
-
pytz==2024.2
|
| 48 |
-
PyYAML==6.0.2
|
| 49 |
-
requests==2.32.3
|
| 50 |
-
rich==13.9.4
|
| 51 |
-
ruff==0.9.1
|
| 52 |
-
semantic-version==2.10.0
|
| 53 |
-
shellingham==1.5.4
|
| 54 |
-
six==1.17.0
|
| 55 |
-
sniffio==1.3.1
|
| 56 |
-
spaces==0.32.0
|
| 57 |
-
starlette==0.41.3
|
| 58 |
-
tomlkit==0.12.0
|
| 59 |
-
tqdm==4.67.1
|
| 60 |
-
typer==0.15.1
|
| 61 |
-
typing_extensions==4.12.2
|
| 62 |
-
tzdata==2024.2
|
| 63 |
-
urllib3==2.3.0
|
| 64 |
-
uvicorn==0.34.0
|
| 65 |
-
websockets==12.0
|
|
|
|
| 1 |
+
gradio
|
| 2 |
+
numpy
|
| 3 |
+
groq
|
| 4 |
+
exif
|
| 5 |
+
spaces
|
| 6 |
+
huggingface-hub
|
| 7 |
+
python-dotenv
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|