Sebastiankay commited on
Commit
e913d2d
·
verified ·
1 Parent(s): 1a5ad79

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +3 -3
  2. app.py +398 -289
  3. 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: 4.43.0
8
  app_file: app.py
9
  pinned: true
10
  header: mini
11
- short_description: Erstelle Bilder mit Flux und div. Flux Loras
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
- from requests.packages.urllib3.util.retry import Retry
 
 
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
- CACHE_DIR = os.path.join(os.path.dirname(__file__), "cache")
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
- gr.set_static_paths(paths=["_res/", "_res/assets/", "_res/assets/emojis/", "_res/assets/favicons/", IMAGE_DIR])
 
 
41
 
42
- custom_css = RES + "/_custom.css"
43
- custom_js = RES + "/_custom.js"
 
 
 
 
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=_res/assets/favicons/apple-touch-icon.png">
49
- <link rel="icon" type="image/png" sizes="32x32" href="file=_res/assets/favicons/favicon-32x32.png">
50
- <link rel="icon" type="image/png" sizes="16x16" href="file=_res/assets/favicons/favicon-16x16.png">
51
- <link rel="icon" type="image/x-icon" href="file=_res/assets/favicons/favicon.ico">
52
- <link rel="manifest" href="file=_res/assets/favicons/site.webmanifest">
 
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
- with open(image_path, "rb") as src:
81
- img = Image(src)
82
- img_comment = json.loads(img.user_comment)
83
-
84
- # checking if the key exists before removing
85
- if "concept" in img_comment:
86
- img_comment.pop("concept")
87
-
88
- return img_comment
89
-
90
-
91
- def read_image_exfi_data(image_path):
92
- print("Imagepath:", image_path)
93
- img_exif_make, img_exif_comment = read_exif(image_path)
94
- return None, image_path, img_exif_comment
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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=[SYSTEMPROMPT, {"role": "user", "content": Prompt}],
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=0)
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: # Filteriere hellere Farben aus
143
  continue
144
- if v > 0.99: # Filteriere Weiß aus
145
  continue
146
  colors.append((h, s, v))
147
-
148
- # Ermittle die dominante Farbe
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
- dominant_color_rgb = f"rgb({dominant_color_rgb[0]}, {dominant_color_rgb[1]}, {dominant_color_rgb[2]})"
153
- # print(dominant_color_rgb)
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, image_ratio, image_seed, randomize_seed, gallery):
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
- image_ratio = "9:16" if image_ratio == "" else image_ratio
169
- used_seed = random.randint(0, MAX_SEED) if image_seed == 0 or randomize_seed else image_seed
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
- file_path = IMAGE_DIR + filename
 
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
- img = PILImage.open(BytesIO(response.content))
195
- img.save(file_path, "png")
196
- print("Save image to: ")
197
- print(file_path)
198
-
199
- # img_exif_comment = "" # read_exif(file_path)
200
- img_dominant_color = image_get_dominant_color(file_path)
201
- img_width, img_height = image_get_size(file_path)
202
-
203
- new_gallery = [i for i in gallery] if gallery else []
204
- new_gallery.append(file_path)
205
- gallery = new_gallery
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
  return (
208
- {"value": Prompt, "__type__": "update"},
209
- {"value": file_path, "__type__": "update"},
210
- {"value": gallery, "selected_index": len(gallery) - 1, "__type__": "update"},
211
- {"value": None, "visible": False, "__type__": "update"},
212
- {"visible": True, "__type__": "update"},
213
- {"value": Prompt, "visible": True, "__type__": "update"},
214
- img_width,
215
- img_height,
 
216
  used_seed,
217
- {"value": file_path, "visible": True, "__type__": "update"},
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 int(999)
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=models[0], interactive=True, info=info_api_status)
253
 
254
 
255
- # MARK: Gradio BLOCKS UI
256
- with gr.Blocks(theme=theme, head=custom_head, css=custom_css, js=custom_js, title=title) as demo:
 
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.Tab("Bilder Builder"):
283
- with gr.Row():
284
- with gr.Column(elem_classes="spacesNavi"):
285
- spacesNaviHTML = gr.HTML("""
286
- <a style="--link-bg-color: var(--primary-500);" href="#"></a>
287
- <a style="--link-bg-color: var(--cat-sky);" href="#"></a>
288
- <a style="--link-bg-color: var(--cat-teal);" href="#"></a>
289
- <a style="--link-bg-color: rgb(94, 12, 21);" href="#"></a>
290
-
291
- """)
292
-
293
- with gr.Column(scale=2): # min_width=420,
294
- with gr.Row():
295
- placeholder_text = "[???] Generiert dir einen zufälligen Prompt.\n[STERN] optimiert deinen eignen Prompt.\n[RUN] generiert dein Bild."
296
- text_prompt = gr.Textbox(
297
- label="Prompt",
298
- show_label=False,
299
- lines=12,
300
- max_lines=18,
301
- placeholder=placeholder_text,
302
- elem_id="prompt_input",
303
- elem_classes="prompt-input hide-progress",
304
- autofocus=True,
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
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True, elem_classes="random-seed-cb toggle-btn")
358
- image_seed = gr.Slider(
359
- label="Seed",
360
- info="Jeder Seed generiert ein anderes Bild mit dem selben Prompt",
361
- minimum=0,
362
- step=1,
363
- value=42,
364
- maximum=MAX_SEED,
365
- elem_id="image_seed",
366
- elem_classes="image-seed hide-progress",
367
- interactive=False,
368
  )
369
-
370
- with gr.Column(scale=4): # min_width=600,
371
- with gr.Row():
372
- with gr.Column(scale=1):
373
- with gr.Row():
374
- with gr.Column():
375
- output_image = gr.Image(
376
- show_label=False, min_width=320, elem_id="output_image", elem_classes="output-image", type="filepath", format="webp", visible=False
377
- )
378
- gallery = gr.Gallery(
379
- label="Bisher erstellte Bilder",
380
- show_label=True,
381
- elem_id="gallery",
382
- columns=[4],
383
- object_fit="cover",
384
- height="auto",
385
- interactive=False,
386
- type="filepath",
387
- format="webp",
388
- )
389
- with gr.Column(scale=1, visible=False, elem_classes="image-info-wrapper") as image_info_wrapper:
390
- with gr.Group():
391
- image_informations = gr.Markdown("""## Bildinformationen""", visible=True)
392
- with gr.Row(elem_classes="img-size-wrapper"):
393
- image_info_tb_width = gr.Textbox(
394
- label="Breite", lines=1, max_lines=1, interactive=False, show_copy_button=True, elem_classes="image-info-tb-width"
395
- )
396
- image_info_tb_height = gr.Textbox(
397
- label="Höhe", lines=1, max_lines=1, interactive=False, show_copy_button=True, elem_classes="image-info-tb-height"
398
- )
399
- with gr.Row(elem_classes="img-seed-wrapper"):
400
- 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")
401
- image_info_tb_prompt = gr.Textbox(
402
- "Bild Prompt", lines=4, max_lines=8, interactive=False, elem_classes="hide-progress", show_copy_button=True, visible=False
403
- )
404
- image_download_button = gr.DownloadButton("Bild herunterladen", value=None, elem_classes="download-button", variant="primary", visible=False)
405
-
406
- output_url = gr.Textbox(label="Output URL", show_label=True, interactive=False, visible=False)
407
- outpu_image_comment = gr.Json(visible=False)
408
- output_dominant_image_color = gr.Textbox(show_label=False, elem_id="dominant_image_color", visible=True, elem_classes="output-dominant-image-color")
409
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  demo.load(
411
- lambda x: ({"interactive": False, "__type__": "update"}, {"interactive": False, "__type__": "update"}, {"interactive": False, "__type__": "update"}),
412
  outputs=[run_button, enhance_prompt_button, random_prompt_button],
413
  ).then(get_inference_models, outputs=[select_model]).then(
414
- lambda x: ({"interactive": True, "__type__": "update"}, {"interactive": True, "__type__": "update"}, {"interactive": True, "__type__": "update"}),
415
  outputs=[run_button, enhance_prompt_button, random_prompt_button],
416
  )
417
 
418
- def switch_image_size_values(image_width, image_height):
419
- return image_height, image_width
420
-
421
- def switch_image_ratio_buttons(ratio_value):
422
- ratio_value = ratio_value.split(":")
423
- ratio_value_new = f"{int(ratio_value[1])}:{int(ratio_value[0])}"
424
-
425
- if int(ratio_value[1]) > int(ratio_value[0]):
426
- # Querformat
427
- new_choises = ["16:9", "4:3", "3:2", "1:1"]
428
- new_label = "Querformat"
429
- elif int(ratio_value[1]) < int(ratio_value[0]):
430
- # Hochformat
431
- new_choises = ["9:16", "3:4", "2:3", "1:1"]
432
- new_label = "Hochformat"
433
- elif int(ratio_value[1]) == int(ratio_value[0]):
434
- new_choises = image_ratio_buttons.choices
435
- new_label = "Quadratisch"
436
-
437
- return {"choices": new_choises, "value": ratio_value_new, "label": new_label, "__type__": "update"}
438
-
439
- def calculate_ratio_values(image_ratio_buttons):
440
- ratio_value = image_ratio_buttons.split(":")
441
- if int(ratio_value[0]) > int(ratio_value[1]):
442
- a = 1024
443
- b = int(a * int(ratio_value[1]) / int(ratio_value[0]))
444
- b = round(b / 8) * 8
445
- new_width = a
446
- new_height = b
447
- new_label = "Querformat"
448
- elif int(ratio_value[0]) < int(ratio_value[1]):
449
- b = 1024
450
- a = int(b * int(ratio_value[0]) / int(ratio_value[1]))
451
- a = round(a / 8) * 8
452
- new_width = a
453
- new_height = b
454
- new_label = "Hochformat"
455
- elif int(ratio_value[0]) == int(ratio_value[1]):
456
- new_width = 1024
457
- new_height = 1024
458
- new_label = "Quadratisch"
459
-
460
- return {"label": new_label, "__type__": "update"}, new_width, new_height
461
-
462
- switch_width_height.click(fn=switch_image_size_values, inputs=[image_width, image_height], outputs=[image_width, image_height], show_progress="hidden", show_api=False)
463
- switch_width_height.click(fn=switch_image_ratio_buttons, inputs=[image_ratio_buttons], outputs=[image_ratio_buttons], show_progress="hidden", show_api=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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", show_api=False
467
  )
468
 
469
  run_button.click(
470
- fn=lambda: ({"interactive": False, "__type__": "update"}, {"interactive": False, "__type__": "update"}, {"interactive": False, "__type__": "update"}),
471
  outputs=[run_button, enhance_prompt_button, random_prompt_button],
472
- show_api=False,
473
- js="() => document.querySelector('dotlottie-player').play()",
474
- ).then(fn=clear_components, outputs=[output_image], show_api=False).then(
475
  fn=process,
476
- inputs=[text_prompt, select_model, image_width, image_height, image_ratio_buttons, image_seed, randomize_seed, gallery],
477
  outputs=[
478
- text_prompt,
479
- output_image,
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: ({"interactive": True, "__type__": "update"}, {"interactive": True, "__type__": "update"}, {"interactive": True, "__type__": "update"}),
493
  outputs=[run_button, enhance_prompt_button, random_prompt_button],
494
- show_api=False,
495
- js="() => document.querySelector('dotlottie-player').stop()",
496
  )
497
 
498
- randomize_seed.input(lambda x: {"interactive": False if x == True else True, "__type__": "update"}, inputs=[randomize_seed], outputs=[image_seed], show_api=False)
499
 
500
- enhance_prompt_button.click(fn=groq_enhance_process, inputs=[text_prompt], outputs=[text_prompt], show_api=False)
501
- random_prompt_button.click(fn=groq_enhance_process, inputs=None, outputs=[text_prompt], show_api=False)
 
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
- aiofiles==23.2.1
2
- annotated-types==0.7.0
3
- anyio==4.8.0
4
- certifi==2024.12.14
5
- charset-normalizer==3.4.1
6
- click==8.1.8
7
- colorama==0.4.6
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