Spaces:
Running
Running
| import os, sys, json, re, time, base64, random, shutil | |
| import gradio as gr | |
| import numpy as np | |
| import requests | |
| from requests import Session | |
| from requests.adapters import HTTPAdapter | |
| from urllib3.util.retry import Retry # More direct import | |
| from datetime import datetime | |
| import urllib.parse | |
| from pathlib import Path | |
| from groq import Groq | |
| from exif import Image | |
| from PIL import Image as PILImage, ExifTags as PILExifTags, PngImagePlugin | |
| from io import BytesIO | |
| import colorsys | |
| import spaces | |
| if not "SPACE" in os.environ: | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| # MARK: INIT | |
| MAX_SEED = np.iinfo(np.int32).max | |
| MAX_IMAGE_SIZE = 2048 | |
| GROQ_APIKEY_PROMPTENHANCE = os.getenv("GROQ_APIKEY_PROMPTENHANCE") | |
| API1 = urllib.parse.unquote(os.getenv("API1")) | |
| BASE_DIR = Path(__file__).resolve().parent | |
| RES = BASE_DIR / "_res" | |
| ASSETS = RES / "assets" | |
| IMAGE_DIR = BASE_DIR / "cache" / "images" | |
| IMAGE_DIR.mkdir(parents=True, exist_ok=True) | |
| gr.set_static_paths(paths=[str(RES), str("cache/images"), str(ASSETS)]) | |
| enhance_systemmessage_json = RES / "groq_systemmessage_prompt_enhance_new.json" | |
| with open(enhance_systemmessage_json, "r") as f: | |
| enhance_systemmessage = json.load(f) | |
| custom_css_path = RES / "_custom.css" | |
| custom_js_path = RES / "_custom.js" | |
| with open(custom_css_path, "r") as f: | |
| custom_css = f.read() | |
| with open(custom_js_path, "r") as f: | |
| custom_js = f.read() | |
| custom_head = f""" | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css"/> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/js/all.min.js"></script> | |
| <link rel="apple-touch-icon" sizes="180x180" href="file={ASSETS / 'favicons/apple-touch-icon.png'}"> | |
| <link rel="icon" type="image/png" sizes="32x32" href="file={ASSETS / 'favicons/favicon-32x32.png'}"> | |
| <link rel="icon" type="image/png" sizes="16x16" href="file={ASSETS / 'favicons/favicon-16x16.png'}"> | |
| <link rel="icon" type="image/x-icon" href="file={ASSETS / 'favicons/favicon.ico'}"> | |
| <link rel="manifest" href="file={ASSETS / 'favicons/site.webmanifest'}"> | |
| <script src="https://unpkg.com/@dotlottie/player-component@latest/dist/dotlottie-player.mjs" type="module"></script> | |
| <script src='https://storage.ko-fi.com/cdn/widget/Widget_2.js'></script> | |
| """ | |
| theme = gr.themes.Soft( | |
| radius_size="sm", | |
| neutral_hue=gr.themes.Color( | |
| c100="#a6adc8", | |
| c200="#9399b2", | |
| c300="#7f849c", | |
| c400="#6c7086", | |
| c50="#cdd6f4", | |
| c500="#585b70", | |
| c600="#45475a", | |
| c700="#313244", | |
| c800="#1e1e2e", | |
| c900="#181825", | |
| c950="#11111b", | |
| ), | |
| ) | |
| title = "Bilder Builder" | |
| # MARK: READ EXIF | |
| def read_exif(image_path): | |
| try: | |
| with open(image_path, "rb") as src: | |
| img = Image(src) # This is where TiffByteOrder error can happen from exif library | |
| if not hasattr(img, "user_comment") or not img.user_comment: | |
| print(f"Warning: No user_comment found in EXIF for {image_path}") | |
| return {} # Return empty dict if no user_comment | |
| try: | |
| img_comment = json.loads(img.user_comment) | |
| except json.JSONDecodeError: | |
| print(f"Warning: Could not decode user_comment JSON from EXIF for {image_path}") | |
| return {} # Return empty dict if JSON is invalid | |
| if "concept" in img_comment: # checking if the key exists before removing | |
| img_comment.pop("concept") | |
| return img_comment | |
| except ValueError as ve: # Catch specific errors like TiffByteOrder from the exif library | |
| print(f"EXIF parsing ValueError for {image_path}: {ve}") | |
| return {} # Return empty dict on this error | |
| except Exception as e: # Catch other potential errors during EXIF processing | |
| print(f"Unexpected error reading EXIF for {image_path}: {e}") | |
| return {} | |
| def read_png_metadata(image_path): | |
| try: | |
| img_pil = PILImage.open(image_path) | |
| if "user_comment" in img_pil.info: | |
| metadata_json_string = img_pil.info["user_comment"] | |
| try: | |
| img_comment = json.loads(metadata_json_string) | |
| if "concept" in img_comment: | |
| img_comment.pop("concept") | |
| return img_comment | |
| except json.JSONDecodeError: | |
| print(f"Warning: Could not decode user_comment JSON from PNG info for {image_path}") | |
| return {} | |
| else: | |
| print(f"Warning: No 'user_comment' found in PNG info for {image_path}") | |
| return {} | |
| except Exception as e: | |
| print(f"Error reading PNG metadata for {image_path}: {e}") | |
| return {} | |
| # MARK: GROQ PROMPT ENHANCE | |
| def groq_enhance_process(Prompt=""): | |
| client = Groq(api_key=GROQ_APIKEY_PROMPTENHANCE) | |
| Prompt = "random prompt" if Prompt == "" else Prompt | |
| completion = client.chat.completions.create( | |
| model="meta-llama/llama-4-scout-17b-16e-instruct", | |
| messages=[enhance_systemmessage, {"role": "user", "content": Prompt}], | |
| temperature=1, | |
| max_tokens=512, | |
| top_p=0.9, | |
| stream=False, | |
| seed=random.randint(0, MAX_SEED), | |
| stop=None, | |
| ) | |
| if completion.choices[0].message.content != "": | |
| enhanced_prompt = completion.choices[0].message.content | |
| enhanced_prompt = re.sub(r"[\.\"]+", "", enhanced_prompt) | |
| return enhanced_prompt | |
| def image_get_size(image_path): | |
| img = PILImage.open(image_path) | |
| width, height = img.size | |
| return width, height | |
| # MARK: DOMINANT COLOR | |
| def image_get_dominant_color(image_path): | |
| img = PILImage.open(image_path) | |
| img = img.convert("RGB") | |
| img = img.resize((100, 100), resample=PILImage.Resampling.NEAREST) # Use Resampling enum | |
| pixels = list(img.getdata()) | |
| colors = [] | |
| for pixel in pixels: | |
| r, g, b = pixel | |
| h, s, v = colorsys.rgb_to_hsv(r / 255, g / 255, b / 255) | |
| if v > 0.5: | |
| continue | |
| if v > 0.99: | |
| continue | |
| colors.append((h, s, v)) | |
| if not colors: # Handle case where all pixels are filtered out | |
| return "rgb(0,0,0)" # Default to black or handle as error | |
| dominant_color = max(colors, key=lambda x: x[2]) | |
| dominant_color_rgb = colorsys.hsv_to_rgb(dominant_color[0], dominant_color[1], dominant_color[2]) | |
| dominant_color_rgb = [int(c * 255) for c in dominant_color_rgb] | |
| dominant_color_rgb_str = f"rgb({dominant_color_rgb[0]}, {dominant_color_rgb[1]}, {dominant_color_rgb[2]})" | |
| return dominant_color_rgb_str | |
| # MARK: CLEAR COMPONENTS | |
| def clear_components(): | |
| return gr.update(value=None, visible=False), gr.update(visible=False) | |
| def process(Prompt, used_model, image_width, image_height, image_seed, p_randomize_seed, current_gallery_items): | |
| if Prompt == "": | |
| gr.Info("Kein Prompt angegeben, es wird ein zufälliger Prompt generiert.", duration=12) | |
| Prompt = groq_enhance_process("random prompt") | |
| used_seed = random.randint(0, MAX_SEED) if image_seed == 0 or p_randomize_seed else image_seed | |
| used_model = "turbo" if "schnell" in used_model.lower() else "flux" | |
| timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S") | |
| filename_prompt = re.sub(r"[^\w\s-]", "", Prompt).strip().replace(" ", "_") | |
| filename = timestamp + "_" + filename_prompt[:100] + ".png" | |
| absolute_file_path = IMAGE_DIR / filename | |
| relative_image_path = f"cache/images/{filename}" | |
| session = Session() | |
| retries = Retry(total=3, status_forcelist=[429, 500, 502, 503, 504], backoff_factor=0.3, respect_retry_after_header=True) | |
| adapter = HTTPAdapter(max_retries=retries) | |
| session.mount("https://", adapter) | |
| session.mount("http://", adapter) | |
| REQUEST_URL = ( | |
| f"{API1}{urllib.parse.quote(Prompt)}?model={used_model}&width={image_width}&height={image_height}&nologo=true&enhance=false&nofeed=true&safe=false&seed={used_seed}" | |
| ) | |
| try: | |
| response = session.get(REQUEST_URL, timeout=60) | |
| if response.status_code == 200: | |
| print("REQUEST URL:\n" + REQUEST_URL + "\n\nImagine API Request solved") | |
| img_pil = PILImage.open(BytesIO(response.content)) | |
| img_pil.save(absolute_file_path, "png") | |
| print("Save image to: ", absolute_file_path) | |
| img_dominant_color = image_get_dominant_color(absolute_file_path) | |
| img_width_actual, img_height_actual = image_get_size(absolute_file_path) # Use actual size | |
| image_metadata = { | |
| "prompt": Prompt, | |
| "seed": used_seed, | |
| "model": used_model, | |
| "dominant_color": img_dominant_color, | |
| "width": img_width_actual, | |
| "height": img_height_actual, | |
| "timestamp": timestamp, | |
| } | |
| metadata_json_string = json.dumps(image_metadata) | |
| png_info_to_add = PngImagePlugin.PngInfo() | |
| png_info_to_add.add_text("user_comment", metadata_json_string) | |
| try: | |
| # Re-save the image, this time embedding the PngInfo metadata | |
| img_pil.save(absolute_file_path, "png", pnginfo=png_info_to_add) | |
| print(f"Image re-saved with PNG metadata: {absolute_file_path}") | |
| except Exception as e: | |
| # This error would be if saving with PngInfo fails for some reason | |
| print(f"Warning: Could not save PNG with embedded metadata for {absolute_file_path}. Error: {e}") | |
| # The file 'absolute_file_path' would still exist from the temporary save, but without our metadata. | |
| new_gallery_item_state_data = (str(absolute_file_path), Prompt, used_seed) | |
| updated_gallery_state_list = [item for item in current_gallery_items] if current_gallery_items else [] | |
| updated_gallery_state_list.append(new_gallery_item_state_data) | |
| all_image_filepaths_for_gallery_files = [item[0] for item in updated_gallery_state_list][::-1] | |
| gallery_display_list = [(item[0], item[1]) for item in updated_gallery_state_list][::-1] | |
| return ( | |
| updated_gallery_state_list, # 1 generated_image | |
| all_image_filepaths_for_gallery_files, # 2 gallery_files | |
| gr.update(value=gallery_display_list, selected_index=0), # 3 gallery | |
| gr.update(value=Prompt), # 4 Text Prompt | |
| gr.update(value=str(absolute_file_path), visible=False), # 5 output_image | |
| gr.update(value=relative_image_path, visible=True), | |
| gr.update(value=Prompt, visible=True), | |
| img_width_actual, # Use actual width | |
| img_height_actual, # Use actual height | |
| used_seed, | |
| gr.update(value=str(absolute_file_path), visible=True), | |
| img_dominant_color, | |
| used_seed, | |
| gr.update(visible=True), | |
| ) | |
| else: | |
| print(f"Imagine API Request ERROR, Status: {response.status_code}, Response: {response.text}") | |
| raise gr.Error(f"Imagine API-Aufruf fehlgeschlagen (Code: {response.status_code}) 💥!", duration=15) | |
| except requests.exceptions.Timeout: | |
| raise gr.Error("⏰ Zeitüberschreitung beim API-Aufruf", duration=15) | |
| except requests.exceptions.RequestException as e: | |
| print(f"Unbekannter Fehler beim API-Aufruf: {e}") | |
| raise gr.Error(f"Unbekannter Fehler beim API-Aufruf! 🤷♂️ {e}", duration=15) | |
| def check_api(url): | |
| try: | |
| response = requests.get(url, timeout=8) | |
| print(f"Checking Status for: {url}, it returns statuscode {response.status_code}") | |
| return response.status_code | |
| except requests.exceptions.RequestException as e: | |
| print(f"An error occurred: {e}") | |
| return 999 | |
| def get_inference_models(): | |
| status_api_1 = check_api(API1) | |
| info_api_1 = "🟢API1👍" if status_api_1 == 200 else "🔴API1👎🔌" | |
| api_models_1 = ["FLUX Dev", "Flux Schnell (Low-Res)"] | |
| models = [model for model in api_models_1 if status_api_1 == 200] | |
| info_api_status = f"Status: {info_api_1}" | |
| default_model = models[0] if models else None | |
| return gr.update(choices=models, value=default_model, interactive=bool(models), info=info_api_status) | |
| # MARK: Gradio BLOCKS UI css=custom_css | |
| with gr.Blocks(theme=theme, head=custom_head, js=custom_js, css=custom_css, title=title) as demo: | |
| # ... (Your existing UI header markdown) ... | |
| with gr.Row(elem_classes="row-header"): | |
| gr.Markdown( | |
| f""" | |
| <div class="flex-wrapper"> | |
| <div style="width: 100%"> | |
| <h1>{title}</h1> | |
| <p class="errorinfo" style="color: #e26c5a; margin-top: 0.3rem; margin-bottom: 0.3rem;">Aktuell funktioniert die APP nicht, da ich heute wenig Zeit habe werde ich es erst morgen schaffen die Probleme zu beheben.</p> | |
| <p> | |
| <span style="font-weight: 600">LG Sebastian</span> | |
| <i class="winking-hand-emoji"></i> gib dem Space gerne ein | |
| <i class="heart-beat-emoji"></i> | |
| </p> | |
| </div> | |
| <div style=""> | |
| <div class="btn-container"> | |
| <a title="Support me on ko-fi.com" class="kofi-button" style="background-color: #72a4f2" href="https://ko-fi.com/Z8Z316DSO4" target="_blank" > | |
| <span class="kofitext"><img src="https://storage.ko-fi.com/cdn/cup-border.png" alt="Ko-fi donations" class="kofiimg"/>Support me on Ko-fi</span> | |
| </a> | |
| </div> | |
| </div> | |
| </div> | |
| """, | |
| elem_classes="md-header", | |
| ) | |
| with gr.Row(elem_classes="row-main"): | |
| with gr.Column(scale=2): | |
| generated_images = gr.State([]) | |
| # generated_images_count = gr.State(0) # This state was unused | |
| with gr.Row(): | |
| placeholder_text = "[???] Generiert dir einen zufälligen Prompt.\n[STERN] optimiert deinen eignen Prompt.\n[RUN] generiert dein Bild." | |
| text_prompt = gr.Textbox( | |
| label="Prompt", | |
| show_label=False, | |
| lines=12, | |
| max_lines=18, | |
| placeholder=placeholder_text, | |
| elem_id="prompt_input", | |
| elem_classes="prompt-input hide-progress", | |
| autofocus=True, | |
| ) | |
| with gr.Row(): | |
| random_prompt_button = gr.Button("", variant="secondary", elem_id="random_prompt_btn", elem_classes="random-prompt-btn", icon=str(ASSETS / "random.png")) | |
| enhance_prompt_button = gr.Button("", variant="secondary", elem_id="enhance_prompt_btn", elem_classes="enhance-prompt-btn", icon=str(ASSETS / "star_light_48.png")) | |
| run_button = gr.Button("Erstellen", variant="primary", elem_id="run_btn", elem_classes="run-btn", interactive=False) | |
| with gr.Row(elem_classes="image_size_selctor_wrapper"): | |
| with gr.Column(scale=1): | |
| with gr.Row(): | |
| with gr.Column(): | |
| select_model = gr.Dropdown(label="Model", elem_id="select_model", elem_classes="select-model") | |
| image_width = gr.Number( | |
| label="Breite", | |
| minimum=256, | |
| maximum=MAX_IMAGE_SIZE, | |
| value=576, | |
| step=32, | |
| elem_id="image_width_selector", | |
| elem_classes="image-width-selector", | |
| scale=1, | |
| visible=False, | |
| ) | |
| image_height = gr.Number( | |
| label="Höhe", | |
| minimum=256, | |
| maximum=MAX_IMAGE_SIZE, | |
| value=1024, | |
| step=32, | |
| elem_id="image_height_selector", | |
| elem_classes="image-height-selector", | |
| scale=1, | |
| visible=False, | |
| ) | |
| with gr.Row(): | |
| image_ratio_buttons = gr.Radio( | |
| ["9:16", "3:4", "2:3", "1:1"], | |
| value="9:16", | |
| label="Hochformat", | |
| show_label=True, | |
| info="Seitenverhältniss drehen", | |
| interactive=True, | |
| elem_id="image_ratio_buttons", | |
| elem_classes="image-ratio-buttons", | |
| container=True, | |
| scale=2, | |
| ) | |
| switch_width_height = gr.Button("", size="sm", elem_id="switch_width_height", elem_classes="switch-ratio-btn", variant="primary", scale=1) | |
| with gr.Column(): | |
| randomize_seed = gr.Checkbox(label="Randomize seed", value=True, elem_classes="random-seed-cb toggle-btn") | |
| image_seed = gr.Slider( | |
| label="Seed", | |
| info="Jeder Seed generiert ein anderes Bild mit dem selben Prompt", | |
| minimum=0, | |
| step=1, | |
| value=42, | |
| maximum=MAX_SEED, | |
| elem_id="image_seed", | |
| elem_classes="image-seed hide-progress", | |
| interactive=False, | |
| ) | |
| with gr.Column(scale=4): | |
| with gr.Row(): | |
| with gr.Column(scale=3): # Changed scale for better layout with info panel | |
| with gr.Row(): | |
| output_image = gr.Image( | |
| show_label=False, min_width=320, elem_id="output_image", elem_classes="output-image", type="filepath", format="webp", visible=False | |
| ) # format="png" might be better if webp causes issues with EXIF | |
| gallery = gr.Gallery( | |
| label="Bisher erstellte Bilder", | |
| show_label=False, | |
| format="png", | |
| elem_id="gallery", | |
| columns=[4], | |
| rows=[4], | |
| object_fit="contain", | |
| height="auto", | |
| type="filepath", | |
| preview=True, | |
| allow_preview=True, | |
| ) | |
| with gr.Row(): | |
| gallery_files = gr.Files(visible=False, elem_id="gallery_files", elem_classes="gallery-files", interactive=False, label="Generierte Bilddateien") | |
| with gr.Column(scale=1, visible=False, elem_classes="image-info-wrapper") as image_info_wrapper: | |
| with gr.Accordion("Bild Informationen", open=False): | |
| # with gr.Group(): | |
| # gr.Markdown(f"""## Bildinformationen""") # Removed visible=False as parent controls it | |
| image_info_tb_prompt = gr.Textbox(label="Bild Prompt", lines=18, interactive=False, elem_classes="hide-progress", show_copy_button=True, visible=True) | |
| with gr.Row(elem_classes="img-size-wrapper"): | |
| 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") | |
| 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") | |
| with gr.Row(elem_classes="img-seed-wrapper"): | |
| 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") | |
| # Made visible as parent controls wrapper | |
| image_download_button = gr.DownloadButton("Bild herunterladen", value=None, elem_classes="download-button", variant="primary", visible=True) # Made visible | |
| output_url = gr.Textbox(label="Output URL", show_label=True, interactive=False, visible=False) | |
| # outpu_image_comment = gr.Json(visible=False) # This was unused | |
| output_dominant_image_color = gr.Textbox(show_label=False, elem_id="dominant_image_color", visible=True, elem_classes="output-dominant-image-color") | |
| # MARK: Functionen after | |
| demo.load( | |
| lambda: (gr.update(interactive=False), gr.update(interactive=False), gr.update(interactive=False)), | |
| outputs=[run_button, enhance_prompt_button, random_prompt_button], | |
| ).then(get_inference_models, outputs=[select_model]).then( | |
| lambda: (gr.update(interactive=True), gr.update(interactive=True), gr.update(interactive=True)), | |
| outputs=[run_button, enhance_prompt_button, random_prompt_button], | |
| ) | |
| def gallery_select_handler(evt: gr.SelectData, gallery_files): | |
| # For debugging, to understand what evt.value is: | |
| # print(f"DEBUG: gallery_select_handler called. evt: {evt}") | |
| # print(f"DEBUG: evt.value: {evt.value}, type(evt.value): {type(evt.value)}") | |
| # print(f"DEBUG: evt.index: {evt.index}, evt.selected: {evt.selected}") | |
| selected_absolute_path = None | |
| # Safely try to get the filepath from evt.value | |
| # evt.value is expected to be a tuple: (filepath, caption) | |
| if isinstance(gallery_files[evt.index], str) and gallery_files[evt.index]: | |
| selected_absolute_path = gallery_files[evt.index] | |
| # If no valid path was extracted, or if evt.value was not as expected (e.g. None, empty dict, etc.) | |
| if not selected_absolute_path: | |
| gr.Info("Kein Bild aus der Galerie ausgewählt, Pfad ist leer oder ungültiger Wert.") | |
| return ( | |
| gr.update(value=None, visible=False), # 1. output_image | |
| gr.update(value="", visible=False), # 2. output_url | |
| gr.update(value="", visible=True), # 3. image_info_tb_prompt (clear value) | |
| "", # 4. image_info_tb_width | |
| "", # 5. image_info_tb_height | |
| "", # 6. image_info_tb_seed | |
| gr.update(value=None, visible=False), # 7. image_download_button | |
| gr.update(visible=False), # 8. image_info_wrapper (hide the whole section) | |
| ) | |
| # print(f"Gallery selected image path: {selected_absolute_path}") # Keep for your debugging | |
| if not os.path.exists(selected_absolute_path): | |
| gr.Warning(f"Ausgewählte Bilddatei nicht gefunden: {selected_absolute_path}") | |
| return ( | |
| gr.update(value=None, visible=False), | |
| gr.update(value="", visible=False), | |
| gr.update(value="Datei nicht gefunden", visible=True), | |
| "N/A", | |
| "N/A", | |
| "N/A", | |
| gr.update(value=None, visible=False), | |
| gr.update(visible=True), # Show wrapper with error | |
| ) | |
| relative_path_for_url_tb = "cache/images/" + Path(selected_absolute_path).name | |
| try: | |
| exif_data = read_png_metadata(selected_absolute_path) | |
| selected_prompt = exif_data.get("prompt", "N/A (EXIF Fehler)") | |
| img_width = exif_data.get("width", "N/A") | |
| img_height = exif_data.get("height", "N/A") | |
| selected_seed = exif_data.get("seed", "N/A") | |
| return ( | |
| gr.update(value=selected_absolute_path, visible=False), | |
| gr.update(value=relative_path_for_url_tb, visible=False), # output_url can be visible | |
| gr.update(value=selected_prompt, visible=True), | |
| str(img_width), | |
| str(img_height), | |
| str(selected_seed), | |
| gr.update(value=selected_absolute_path, visible=False), | |
| gr.update(visible=True), | |
| ) | |
| except Exception as e: # Catch any other unexpected error during this block | |
| print(f"Error in gallery_select_handler after path validation: {e}") | |
| gr.Warning(f"Fehler beim Verarbeiten der Bilddetails: {e}") | |
| # Fallback if try block fails | |
| return ( | |
| gr.update(value=selected_absolute_path, visible=False), # Still show image if path is valid | |
| gr.update(value=relative_path_for_url_tb, visible=True), | |
| gr.update(value="Fehler beim Lesen der Details", visible=True), | |
| "Fehler", | |
| "Fehler", | |
| "Fehler", | |
| gr.update(value=selected_absolute_path, visible=True), # Download button for the image | |
| gr.update(visible=True), # Show info wrapper | |
| ) | |
| gallery.select( | |
| fn=gallery_select_handler, | |
| inputs=[gallery_files], # evt is passed automatically by gr.SelectData | |
| # outputs=[debug_text], | |
| outputs=[ | |
| output_image, | |
| output_url, | |
| image_info_tb_prompt, | |
| image_info_tb_width, | |
| image_info_tb_height, | |
| image_info_tb_seed, | |
| image_download_button, | |
| output_dominant_image_color, # Add if you want to update this too | |
| ], | |
| show_progress="hidden", | |
| ) | |
| # 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) | |
| def switch_image_size_values(w, h): | |
| return h, w | |
| def switch_image_ratio_buttons(ratio_val): | |
| parts = ratio_val.split(":") | |
| new_ratio = f"{parts[1]}:{parts[0]}" | |
| is_landscape = int(parts[1]) > int(parts[0]) | |
| is_portrait = int(parts[1]) < int(parts[0]) | |
| choices = ["16:9", "4:3", "3:2", "1:1"] if is_landscape else ["9:16", "3:4", "2:3", "1:1"] | |
| label = "Querformat" if is_landscape else "Hochformat" if is_portrait else "Quadratisch" | |
| return gr.update(choices=choices, value=new_ratio, label=label) | |
| def calculate_ratio_values(ratio_str): | |
| w_s, h_s = map(int, ratio_str.split(":")) | |
| base_dim = 1024 | |
| if w_s > h_s: # Landscape or square | |
| new_w = base_dim | |
| new_h = round(base_dim * h_s / w_s / 8) * 8 | |
| else: # Portrait | |
| new_h = base_dim | |
| new_w = round(base_dim * w_s / h_s / 8) * 8 | |
| label = "Querformat" if w_s > h_s else "Hochformat" if w_s < h_s else "Quadratisch" | |
| return gr.update(label=label), new_w, new_h | |
| 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) | |
| switch_width_height.click(fn=switch_image_ratio_buttons, inputs=[image_ratio_buttons], outputs=[image_ratio_buttons], show_progress="hidden", api_name=False) | |
| image_ratio_buttons.input( | |
| fn=calculate_ratio_values, inputs=[image_ratio_buttons], outputs=[image_ratio_buttons, image_width, image_height], show_progress="hidden", api_name=False | |
| ) | |
| run_button.click( | |
| fn=lambda: (gr.update(interactive=False), gr.update(interactive=False), gr.update(interactive=False)), | |
| outputs=[run_button, enhance_prompt_button, random_prompt_button], | |
| api_name=False, | |
| js="() => { const player = document.querySelector('dotlottie-player'); if (player) player.play(); }", | |
| ).then( | |
| fn=process, | |
| inputs=[text_prompt, select_model, image_width, image_height, image_seed, randomize_seed, generated_images], | |
| outputs=[ | |
| generated_images, # 1 generated_image | |
| gallery_files, # 2 gallery_files | |
| gallery, # 3 gallery | |
| text_prompt, # 4 Text Prompt | |
| output_image, # 5 output_image | |
| output_url, | |
| image_info_tb_prompt, | |
| image_info_tb_width, | |
| image_info_tb_height, | |
| image_info_tb_seed, | |
| image_download_button, | |
| output_dominant_image_color, | |
| image_seed, | |
| image_info_wrapper, | |
| ], | |
| show_progress="hidden", | |
| ).then( | |
| fn=lambda: (gr.update(interactive=True), gr.update(interactive=True), gr.update(interactive=True)), | |
| outputs=[run_button, enhance_prompt_button, random_prompt_button], | |
| api_name=False, | |
| js="() => { const player = document.querySelector('dotlottie-player'); if (player) player.stop(); }", | |
| ) | |
| 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) | |
| randomize_seed.input(lambda x: gr.update(interactive=not x), inputs=[randomize_seed], outputs=[image_seed], api_name=False) | |
| enhance_prompt_button.click(fn=groq_enhance_process, inputs=[text_prompt], outputs=[text_prompt], api_name=False) | |
| random_prompt_button.click(fn=groq_enhance_process, inputs=None, outputs=[text_prompt], api_name=False) | |
| # MARK: Gradio LAUNCH | |
| demo.launch(show_api=False, debug=True) | |