import random import os import uuid from datetime import datetime import gradio as gr import numpy as np import spaces import torch from diffusers import DiffusionPipeline from PIL import Image import re import tempfile import io import logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') ############################################################################### # 1. Diffusion Pipeline 로드 및 기본 세팅 ############################################################################### SAVE_DIR = "saved_images" if not os.path.exists(SAVE_DIR): os.makedirs(SAVE_DIR, exist_ok=True) device = "cuda" if torch.cuda.is_available() else "cpu" repo_id = "black-forest-labs/FLUX.1-dev" adapter_id = "ginipick/flux-lora-eric-cat" def load_model_with_retry(max_retries=5): """ 로컬 또는 Hugging Face로부터 모델(FLUX.1-dev) + LoRA 어댑터(weights)를 불러온다. """ for attempt in range(max_retries): try: logging.info(f"Loading model attempt {attempt+1}/{max_retries}...") pipeline = DiffusionPipeline.from_pretrained( repo_id, torch_dtype=torch.bfloat16, use_safetensors=True, resume_download=True ) logging.info("Model loaded successfully, loading LoRA weights...") pipeline.load_lora_weights(adapter_id) pipeline = pipeline.to(device) logging.info("Pipeline ready!") return pipeline except Exception as e: if attempt < max_retries - 1: wait_time = 10 * (attempt + 1) logging.error(f"Error loading model: {e}. Retrying in {wait_time} seconds...") import time time.sleep(wait_time) else: raise Exception(f"Failed to load model after {max_retries} attempts: {e}") pipeline = load_model_with_retry() MAX_SEED = np.iinfo(np.int32).max MAX_IMAGE_SIZE = 1024 def save_generated_image(image, prompt): """ 생성된 이미지를 저장하면서 메타 정보를 기록한다. """ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") unique_id = str(uuid.uuid4())[:8] filename = f"{timestamp}_{unique_id}.png" filepath = os.path.join(SAVE_DIR, filename) image.save(filepath) metadata_file = os.path.join(SAVE_DIR, "metadata.txt") with open(metadata_file, "a", encoding="utf-8") as f: f.write(f"{filename}|{prompt}|{timestamp}\n") return filepath def load_generated_images(): """ 저장된 이미지를 최신순으로 불러온다. """ if not os.path.exists(SAVE_DIR): return [] image_files = [ os.path.join(SAVE_DIR, f) for f in os.listdir(SAVE_DIR) if f.endswith(('.png', '.jpg', '.jpeg', '.webp')) ] image_files.sort(key=lambda x: os.path.getctime(x), reverse=True) return image_files @spaces.GPU(duration=120) def inference( prompt: str, seed: int, randomize_seed: bool, width: int, height: int, guidance_scale: float, num_inference_steps: int, lora_scale: float, progress: gr.Progress = gr.Progress(track_tqdm=True), ): """ Diffusion Pipeline을 사용해 이미지를 생성. (LoRA 스케일, Steps 등 설정 가능) """ if randomize_seed: seed = random.randint(0, MAX_SEED) generator = torch.Generator(device=device).manual_seed(seed) try: image = pipeline( prompt=prompt, guidance_scale=guidance_scale, num_inference_steps=num_inference_steps, width=width, height=height, generator=generator, joint_attention_kwargs={"scale": lora_scale}, ).images[0] filepath = save_generated_image(image, prompt) return image, seed, load_generated_images() except Exception as e: logging.error(f"Error during inference: {e}") error_img = Image.new('RGB', (width, height), color='red') return error_img, seed, load_generated_images() ############################################################################### # 3. Gradio UI ############################################################################### examples = [ "cat style cosmic explorer with starry fur, floating in a nebula of vibrant pink and purple clouds, mystical glowing eyes, surrounded by tiny planets and cosmic dust. Extremely detailed, artistic masterpiece. [trigger]", "cat style samurai warrior with ornate armor decorated with cherry blossoms, sitting in meditation under a moonlit waterfall, katana resting beside, glowing spiritual aura, ultra-detailed metallic reflections. [trigger]", "cat style magical librarian in an ancient floating library, surrounded by flying books with glowing runes, wearing wizards robes with star patterns, soft magical lighting, shelves extending to infinity, detailed bookbindings. [trigger]", "cat style royal empress with jeweled crown and elaborate golden silk robes, sitting on a throne made of jade and cherry blossoms, palace backdrop with ornate architecture, atmospheric lighting, ultra-detailed embroidery patterns. [trigger]", "cat style mystical forest guardian, fur intertwined with vines and flowers, ancient moss-covered stone artifacts floating around, emerald glowing eyes, sunbeams filtering through dense canopy, magical particles in the air, studio quality. [trigger]", "cat style space astronaut on an alien planet, exploring crystal caves with bioluminescent plants, helmet reflecting the colorful surroundings, paw prints leaving glowing marks, scientific equipment, cinematic lighting, highly detailed suit. [trigger]" ] css = """ :root { --primary-color: #ff7e5f; --primary-hover: #ff6347; --secondary-color: #feb47b; --tertiary-color: #ffead0; --background-color: #f9f4f0; --panel-background: #ffffff; --text-color: #4a3933; --border-radius: 16px; --shadow: 0 8px 24px rgba(255, 126, 95, 0.12); --font-main: 'Poppins', -apple-system, BlinkMacSystemFont, sans-serif; } body { background-color: var(--background-color); font-family: var(--font-main); color: var(--text-color); } .gradio-container { margin: 0 auto; max-width: 1200px !important; } .main-header { text-align: center; padding: 2.5rem 1.5rem 1.5rem; background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%); color: white; margin-bottom: 2rem; border-radius: var(--border-radius); box-shadow: var(--shadow); position: relative; overflow: hidden; } .main-header::before { content: ""; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: url('data:image/svg+xml;utf8,') no-repeat center center; background-size: 180px; opacity: 0.5; } .main-header h1 { font-size: 3rem; margin-bottom: 0.5rem; font-weight: 800; text-shadow: 0 2px 5px rgba(0,0,0,0.2); position: relative; } .main-header p { font-size: 1.1rem; margin: 1rem 0; opacity: 0.95; position: relative; font-weight: 500; max-width: 800px; margin-left: auto; margin-right: auto; line-height: 1.6; } .main-header .tribute { font-style: italic; opacity: 0.9; font-size: 1rem; margin-top: 1rem; position: relative; } .main-header a { color: var(--tertiary-color); text-decoration: none; font-weight: 600; transition: all 0.2s ease; position: relative; } .main-header a:hover { text-decoration: underline; opacity: 0.9; } .container { background-color: var(--panel-background); padding: 2rem; border-radius: var(--border-radius); box-shadow: var(--shadow); margin-bottom: 2rem; border: 1px solid rgba(255, 126, 95, 0.1); } button.primary { background: var(--primary-color) !important; background: linear-gradient(90deg, var(--primary-color) 0%, var(--secondary-color) 100%) !important; border: none !important; color: white !important; padding: 12px 24px !important; border-radius: 12px !important; font-weight: 600 !important; box-shadow: 0 4px 10px rgba(255, 126, 95, 0.3) !important; transition: all 0.3s ease !important; font-size: 1.05rem !important; letter-spacing: 0.5px !important; } button.primary:hover { background: linear-gradient(90deg, var(--primary-hover) 0%, var(--secondary-color) 100%) !important; transform: translateY(-3px) !important; box-shadow: 0 6px 15px rgba(255, 126, 95, 0.4) !important; } button.secondary { background: white !important; border: 1px solid rgba(255, 126, 95, 0.3) !important; color: var(--primary-color) !important; padding: 12px 24px !important; border-radius: 12px !important; font-weight: 500 !important; box-shadow: 0 2px 5px rgba(255, 126, 95, 0.1) !important; transition: all 0.3s ease !important; } button.secondary:hover { background: rgba(255, 126, 95, 0.05) !important; transform: translateY(-2px) !important; box-shadow: 0 4px 8px rgba(255, 126, 95, 0.15) !important; } .gr-box { border-radius: var(--border-radius) !important; border: 1px solid rgba(255, 126, 95, 0.2) !important; } .gr-panel { border-radius: var(--border-radius) !important; } .gr-input { border-radius: 12px !important; border: 1px solid rgba(255, 126, 95, 0.3) !important; padding: 12px !important; transition: all 0.3s ease !important; font-size: 1rem !important; } .gr-input:focus { border-color: var(--primary-color) !important; box-shadow: 0 0 0 2px rgba(255, 126, 95, 0.2) !important; } .gr-form { border-radius: var(--border-radius) !important; background-color: var(--panel-background) !important; } .gr-accordion { border-radius: var(--border-radius) !important; overflow: hidden !important; border: 1px solid rgba(255, 126, 95, 0.15) !important; } .gr-button { border-radius: 12px !important; } .gallery-item { border-radius: var(--border-radius) !important; transition: all 0.3s ease !important; overflow: hidden !important; border: 3px solid transparent !important; } .gallery-item:hover { transform: scale(1.03) !important; box-shadow: 0 8px 20px rgba(255, 126, 95, 0.2) !important; border: 3px solid var(--primary-color) !important; } .tabs { border-radius: var(--border-radius) !important; overflow: hidden !important; } footer { display: none !important; } .settings-accordion legend span { font-weight: 600 !important; color: var(--primary-color) !important; } .example-prompt { font-size: 0.95rem; color: var(--text-color); padding: 12px 16px; background: linear-gradient(to right, rgba(255, 126, 95, 0.05), rgba(254, 180, 123, 0.1)); border-radius: 12px; border-left: 4px solid var(--primary-color); margin-bottom: 12px; cursor: pointer; transition: all 0.3s; box-shadow: 0 2px 5px rgba(255, 126, 95, 0.05); } .example-prompt:hover { background: linear-gradient(to right, rgba(255, 126, 95, 0.1), rgba(254, 180, 123, 0.15)); transform: translateX(5px); box-shadow: 0 3px 8px rgba(255, 126, 95, 0.1); } .status-generating { color: var(--secondary-color); font-weight: 600; display: flex; align-items: center; gap: 8px; font-size: 1.05rem; } .status-generating::before { content: ""; display: inline-block; width: 14px; height: 14px; border-radius: 50%; background-color: var(--secondary-color); animation: pulse 1.5s infinite; } .status-complete { color: #4caf50; font-weight: 600; display: flex; align-items: center; gap: 8px; font-size: 1.05rem; } .status-complete::before { content: ""; display: inline-block; width: 14px; height: 14px; border-radius: 50%; background-color: #4caf50; } @keyframes pulse { 0% { opacity: 0.6; transform: scale(0.9); } 50% { opacity: 1; transform: scale(1.1); } 100% { opacity: 0.6; transform: scale(0.9); } } .gr-accordion-title { font-weight: 600 !important; color: var(--primary-color) !important; } .tabs button { font-weight: 500 !important; padding: 10px 16px !important; } .tabs button.selected { font-weight: 600 !important; color: var(--primary-color) !important; background: rgba(255, 126, 95, 0.1) !important; } .gr-slider-container { padding: 10px 0 !important; } .gr-prose h3 { font-weight: 700 !important; color: var(--primary-color) !important; margin-bottom: 1rem !important; font-size: 1.2rem !important; } .tab-nav { margin-bottom: 1rem !important; background-color: var(--panel-background) !important; border-radius: var(--border-radius) !important; overflow: hidden !important; } .gr-slider-thumb { background: var(--primary-color) !important; } .gr-slider-track { background: linear-gradient(90deg, var(--primary-color) 0%, var(--secondary-color) 100%) !important; } .cat-icon { display: inline-block; font-size: 1.8em; margin-right: 0.3em; vertical-align: middle; } .gr-gallery { display: grid !important; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)) !important; gap: 16px !important; padding: 16px !important; } #result-image .gr-image-viewer { border-radius: var(--border-radius); overflow: hidden; box-shadow: 0 5px 15px rgba(255, 126, 95, 0.1); transition: all 0.3s ease; } #result-image .gr-image-viewer:hover { box-shadow: 0 8px 25px rgba(255, 126, 95, 0.2); } .gr-padded { padding: 0 !important; } """ with gr.Blocks(css=css, analytics_enabled=False, theme="soft") as demo: with gr.Column(): gr.HTML('''

🐱✨ FLUX Cat LoRA Generator

Generate beautiful cat-styled images with this specialized LoRA model. Simply enter your prompt and watch the magic happen!

This project is a tribute to the first-ever "cat" LoRA model on Hugging Face, honoring its pioneering contribution to stylized AI image generation.

Community: https://discord.gg/openfreeai

''') # Text-to-Image tab only (removed Image-to-Image tab) with gr.Column(): with gr.Row(): with gr.Column(scale=3): with gr.Group(elem_classes="container"): prompt = gr.Textbox( label="Enter your imagination", placeholder="Describe your cat-style image here... (e.g., 'cat style majestic warrior with armor')", lines=3 ) with gr.Row(): run_button = gr.Button("✨ Generate Purrfect Image", elem_classes="primary") clear_button = gr.Button("Clear", elem_classes="secondary") with gr.Accordion("Advanced Settings", open=False, elem_classes="settings-accordion"): with gr.Row(): seed = gr.Slider( label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=42, ) randomize_seed = gr.Checkbox(label="Randomize seed", value=True) with gr.Row(): width = gr.Slider( label="Width", minimum=256, maximum=MAX_IMAGE_SIZE, step=32, value=1024, ) height = gr.Slider( label="Height", minimum=256, maximum=MAX_IMAGE_SIZE, step=32, value=768, ) with gr.Row(): guidance_scale = gr.Slider( label="Guidance scale", minimum=0.0, maximum=10.0, step=0.1, value=3.5, ) with gr.Row(): num_inference_steps = gr.Slider( label="Steps", minimum=1, maximum=50, step=1, value=30, ) lora_scale = gr.Slider( label="LoRA scale", minimum=0.0, maximum=1.0, step=0.1, value=1.0, ) with gr.Group(elem_classes="container"): gr.Markdown("### ✨ Inspirational Cat Prompts") examples_html = '\n'.join([f'
{ex}
' for ex in examples]) example_container = gr.HTML(examples_html) with gr.Column(scale=4): with gr.Group(elem_classes="container"): generation_status = gr.HTML('
Ready to generate
') result = gr.Image(label="Generated Cat Image", elem_id="result-image") seed_text = gr.Number(label="Used Seed", value=42) with gr.Group(elem_classes="container"): with gr.Tabs(elem_classes="tabs") as gallery_tabs: with gr.TabItem("Cat Gallery"): gallery_header = gr.Markdown("### 🖼️ Your Cat-tastic Creations") with gr.Row(): refresh_btn = gr.Button("🔄 Refresh Gallery", elem_classes="secondary") generated_gallery = gr.Gallery( label="Generated Images", columns=3, value=load_generated_images(), height="500px", elem_classes="gallery-item" ) ########################################################################### # Gradio Helper Functions ########################################################################### def refresh_gallery(): return load_generated_images() def clear_output(): return "", gr.update(value=None), seed, '
Ready to generate
' def before_generate(): return '
Creating cat masterpiece...
' def after_generate(image, seed_num, gallery): return image, seed_num, gallery, '
Meow-gnificent creation complete!
' ########################################################################### # Gradio Event Wiring ########################################################################### refresh_btn.click( fn=refresh_gallery, inputs=None, outputs=generated_gallery, ) # 텍스트-이미지 생성 이벤트 clear_button.click( fn=clear_output, inputs=None, outputs=[prompt, result, seed_text, generation_status] ) run_button.click( fn=before_generate, inputs=None, outputs=generation_status, ).then( fn=inference, inputs=[ prompt, seed, randomize_seed, width, height, guidance_scale, num_inference_steps, lora_scale, ], outputs=[result, seed_text, generated_gallery], ).then( fn=after_generate, inputs=[result, seed_text, generated_gallery], outputs=[result, seed_text, generated_gallery, generation_status], ) prompt.submit( fn=before_generate, inputs=None, outputs=generation_status, ).then( fn=inference, inputs=[ prompt, seed, randomize_seed, width, height, guidance_scale, num_inference_steps, lora_scale, ], outputs=[result, seed_text, generated_gallery], ).then( fn=after_generate, inputs=[result, seed_text, generated_gallery], outputs=[result, seed_text, generated_gallery, generation_status], ) # JS로 예시 prompt 클릭 시 자동 채우기 gr.HTML(""" """) ############################################################################### # 4. 실행 ############################################################################### try: demo.queue(concurrency_count=1, max_size=20) demo.launch(debug=True, show_api=False) except Exception as e: logging.error(f"Error during launch: {e}") logging.info("Trying alternative launch configuration...") demo.launch(debug=True, show_api=False, share=False)