geerg2 / app.py
ssboost's picture
Update app.py
a91da0a verified
raw
history blame
44.1 kB
import os
import tempfile
from PIL import Image, ImageEnhance, ImageFilter
import gradio as gr
import logging
import re
import time
import cv2
import numpy as np
from io import BytesIO
from datetime import datetime, timedelta
from google import genai
from google.genai import types
from dotenv import load_dotenv
load_dotenv()
# ๋กœ๊น… ์„ค์ •
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# ------------------- API ํ‚ค ์ˆœํ™˜ ์‹œ์Šคํ…œ -------------------
API_KEYS = [] # API ํ‚ค ๋ชฉ๋ก
current_key_index = 0 # ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ธ ํ‚ค ์ธ๋ฑ์Šค
def initialize_api_keys():
"""API ํ‚ค ๋ชฉ๋ก์„ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ํ•จ์ˆ˜"""
global API_KEYS
# ํ™˜๊ฒฝ ๋ณ€์ˆ˜์—์„œ API ํ‚ค ๊ฐ€์ ธ์˜ค๊ธฐ
key1 = os.environ.get("GEMINI_API_KEY_1", "")
key2 = os.environ.get("GEMINI_API_KEY_2", "")
key3 = os.environ.get("GEMINI_API_KEY_3", "")
key4 = os.environ.get("GEMINI_API_KEY_4", "")
key5 = os.environ.get("GEMINI_API_KEY_5", "")
# ๋นˆ ๋ฌธ์ž์—ด์ด ์•„๋‹Œ ํ‚ค๋งŒ ์ถ”๊ฐ€
if key1:
API_KEYS.append(key1)
if key2:
API_KEYS.append(key2)
if key3:
API_KEYS.append(key3)
if key4:
API_KEYS.append(key4)
if key5:
API_KEYS.append(key5)
# ๊ธฐ์กด GEMINI_API_KEY๊ฐ€ ์žˆ์œผ๋ฉด ์ถ”๊ฐ€
default_key = os.environ.get("GEMINI_API_KEY", "")
if default_key and default_key not in API_KEYS:
API_KEYS.append(default_key)
logger.info(f"API ํ‚ค {len(API_KEYS)}๊ฐœ๊ฐ€ ๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
def get_next_api_key():
"""๋‹ค์Œ API ํ‚ค๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ํ•จ์ˆ˜"""
global current_key_index
if not API_KEYS:
return None
# ํ˜„์žฌ ํ‚ค ๊ฐ€์ ธ์˜ค๊ธฐ
api_key = API_KEYS[current_key_index]
# ๋‹ค์Œ ํ‚ค ์ธ๋ฑ์Šค๋กœ ์—…๋ฐ์ดํŠธ
current_key_index = (current_key_index + 1) % len(API_KEYS)
return api_key
# ========== ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ ๊ด€๋ จ ํ•จ์ˆ˜ ==========
def save_binary_file(file_name, data):
with open(file_name, "wb") as f:
f.write(data)
def translate_prompt_to_english(prompt):
if not re.search("[๊ฐ€-ํžฃ]", prompt):
return prompt
prompt = prompt.replace("#1", "IMAGE_TAG_ONE")
prompt = prompt.replace("#2", "IMAGE_TAG_TWO")
prompt = prompt.replace("#3", "IMAGE_TAG_THREE")
try:
api_key = get_next_api_key()
if not api_key:
logger.error("Gemini API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
prompt = prompt.replace("IMAGE_TAG_ONE", "#1")
prompt = prompt.replace("IMAGE_TAG_TWO", "#2")
prompt = prompt.replace("IMAGE_TAG_THREE", "#3")
return prompt
client = genai.Client(api_key=api_key)
translation_prompt = f"""
Translate the following Korean text to English:
{prompt}
IMPORTANT: The tokens IMAGE_TAG_ONE, IMAGE_TAG_TWO, and IMAGE_TAG_THREE are special tags
and must be preserved exactly as is in your translation. Do not translate these tokens.
"""
logger.info(f"Translation prompt: {translation_prompt}")
response = client.models.generate_content(
model="gemini-2.0-flash",
contents=[translation_prompt],
config=types.GenerateContentConfig(
response_modalities=['Text'],
temperature=0.2,
top_p=0.95,
top_k=40,
max_output_tokens=512
)
)
translated_text = ""
for part in response.candidates[0].content.parts:
if hasattr(part, 'text') and part.text:
translated_text += part.text
if translated_text.strip():
translated_text = translated_text.replace("IMAGE_TAG_ONE", "#1")
translated_text = translated_text.replace("IMAGE_TAG_TWO", "#2")
translated_text = translated_text.replace("IMAGE_TAG_THREE", "#3")
logger.info(f"Translated text: {translated_text.strip()}")
return translated_text.strip()
else:
logger.warning("๋ฒˆ์—ญ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์›๋ณธ ํ”„๋กฌํ”„ํŠธ ์‚ฌ์šฉ")
prompt = prompt.replace("IMAGE_TAG_ONE", "#1")
prompt = prompt.replace("IMAGE_TAG_TWO", "#2")
prompt = prompt.replace("IMAGE_TAG_THREE", "#3")
return prompt
except Exception as e:
logger.exception("๋ฒˆ์—ญ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
prompt = prompt.replace("IMAGE_TAG_ONE", "#1")
prompt = prompt.replace("IMAGE_TAG_TWO", "#2")
prompt = prompt.replace("IMAGE_TAG_THREE", "#3")
return prompt
def preprocess_prompt(prompt, image1, image2, image3):
# ๊ธฐ์กด ํ•จ์ˆ˜ ์œ ์ง€
has_img1 = image1 is not None
has_img2 = image2 is not None
has_img3 = image3 is not None
if "#1" in prompt and not has_img1:
prompt = prompt.replace("#1", "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€(์—†์Œ)")
else:
prompt = prompt.replace("#1", "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€")
if "#2" in prompt and not has_img2:
prompt = prompt.replace("#2", "๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€(์—†์Œ)")
else:
prompt = prompt.replace("#2", "๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€")
if "#3" in prompt and not has_img3:
prompt = prompt.replace("#3", "์„ธ ๋ฒˆ์งธ ์ด๋ฏธ์ง€(์—†์Œ)")
else:
prompt = prompt.replace("#3", "์„ธ ๋ฒˆ์งธ ์ด๋ฏธ์ง€")
if "1. ์ด๋ฏธ์ง€ ๋ณ€๊ฒฝ" in prompt:
desc_match = re.search(r'#1์„ "(.*?)"์œผ๋กœ ๋ฐ”๊ฟ”๋ผ', prompt)
if desc_match:
description = desc_match.group(1)
prompt = f"์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋ฅผ {description}์œผ๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ์„ธ์š”. ์›๋ณธ ์ด๋ฏธ์ง€์˜ ์ฃผ์š” ๋‚ด์šฉ์€ ์œ ์ง€ํ•˜๋˜ ์ƒˆ๋กœ์šด ์Šคํƒ€์ผ๊ณผ ๋ถ„์œ„๊ธฐ๋กœ ์žฌํ•ด์„ํ•ด์ฃผ์„ธ์š”."
else:
prompt = "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋ฅผ ์ฐฝ์˜์ ์œผ๋กœ ๋ณ€ํ˜•ํ•ด์ฃผ์„ธ์š”. ๋” ์ƒ์ƒํ•˜๊ณ  ์˜ˆ์ˆ ์ ์ธ ๋ฒ„์ „์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”."
elif "2. ๊ธ€์ž์ง€์šฐ๊ธฐ" in prompt:
text_match = re.search(r'#1์—์„œ "(.*?)"๋ฅผ ์ง€์›Œ๋ผ', prompt)
if text_match:
text_to_remove = text_match.group(1)
prompt = f"์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์—์„œ '{text_to_remove}' ํ…์ŠคํŠธ๋ฅผ ์ฐพ์•„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ œ๊ฑฐํ•ด์ฃผ์„ธ์š”. ํ…์ŠคํŠธ๊ฐ€ ์žˆ๋˜ ๋ถ€๋ถ„์„ ๋ฐฐ๊ฒฝ๊ณผ ์กฐํ™”๋กญ๊ฒŒ ์ฑ„์›Œ์ฃผ์„ธ์š”."
else:
prompt = "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์—์„œ ๋ชจ๋“  ํ…์ŠคํŠธ๋ฅผ ์ฐพ์•„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ œ๊ฑฐํ•ด์ฃผ์„ธ์š”. ๊น”๋”ํ•œ ์ด๋ฏธ์ง€๋กœ ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”."
elif "4. ์˜ท๋ฐ”๊พธ๊ธฐ" in prompt:
prompt = "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ์ธ๋ฌผ ์˜์ƒ์„ ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ์˜์ƒ์œผ๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ์„ธ์š”. ์˜์ƒ์˜ ์Šคํƒ€์ผ๊ณผ ์ƒ‰์ƒ์€ ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋ฅผ ๋”ฐ๋ฅด๋˜, ์‹ ์ฒด ๋น„์œจ๊ณผ ํฌ์ฆˆ๋Š” ์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋ฅผ ์œ ์ง€ํ•ด์ฃผ์„ธ์š”."
elif "5. ๋ฐฐ๊ฒฝ๋ฐ”๊พธ๊ธฐ" in prompt:
prompt = "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ์„ ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ์œผ๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ์„ธ์š”. ์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ์ฃผ์š” ํ”ผ์‚ฌ์ฒด๋Š” ์œ ์ง€ํ•˜๊ณ , ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ๊ณผ ์กฐํ™”๋กญ๊ฒŒ ํ•ฉ์„ฑํ•ด์ฃผ์„ธ์š”."
elif "6. ์ด๋ฏธ์ง€ ํ•ฉ์„ฑ(์ƒํ’ˆํฌํ•จ)" in prompt:
prompt = "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์™€ ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€(๋˜๋Š” ์„ธ ๋ฒˆ์งธ ์ด๋ฏธ์ง€)๋ฅผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ•ฉ์„ฑํ•ด์ฃผ์„ธ์š”. ๋ชจ๋“  ์ด๋ฏธ์ง€์˜ ์ฃผ์š” ์š”์†Œ๋ฅผ ํฌํ•จํ•˜๊ณ , ํŠนํžˆ ์ƒํ’ˆ์ด ๋‹๋ณด์ด๋„๋ก ์กฐํ™”๋กญ๊ฒŒ ํ†ตํ•ฉํ•ด์ฃผ์„ธ์š”."
prompt += " ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”. ์ด๋ฏธ์ง€์— ํ…์ŠคํŠธ๋‚˜ ๊ธ€์ž๋ฅผ ํฌํ•จํ•˜์ง€ ๋งˆ์„ธ์š”."
return prompt
def generate_with_images(prompt, images, variation_index=0):
try:
api_key = get_next_api_key()
if not api_key:
return None, "API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— GEMINI_API_KEY_1, GEMINI_API_KEY_2, GEMINI_API_KEY_3, GEMINI_API_KEY_4, GEMINI_API_KEY_5 ์ค‘ ํ•˜๋‚˜ ์ด์ƒ์„ ์„ค์ •ํ•ด์ฃผ์„ธ์š”."
client = genai.Client(api_key=api_key)
logger.info(f"Gemini API ์š”์ฒญ ์‹œ์ž‘ - ํ”„๋กฌํ”„ํŠธ: {prompt}, ๋ณ€ํ˜• ์ธ๋ฑ์Šค: {variation_index}")
variation_suffixes = [
" Create this as the first variation. Do not add any text, watermarks, or labels to the image.",
" Create this as the second variation with more vivid colors. Do not add any text, watermarks, or labels to the image.",
" Create this as the third variation with a more creative style. Do not add any text, watermarks, or labels to the image.",
" Create this as the fourth variation with enhanced details. Do not add any text, watermarks, or labels to the image."
]
if variation_index < len(variation_suffixes):
prompt = prompt + variation_suffixes[variation_index]
else:
prompt = prompt + " Do not add any text, watermarks, or labels to the image."
contents = [prompt]
for idx, img in enumerate(images, 1):
if img is not None:
contents.append(img)
logger.info(f"์ด๋ฏธ์ง€ #{idx} ์ถ”๊ฐ€๋จ")
response = client.models.generate_content(
model="gemini-2.0-flash-exp-image-generation",
contents=contents,
config=types.GenerateContentConfig(
response_modalities=['Text', 'Image'],
temperature=1,
top_p=0.95,
top_k=40,
max_output_tokens=8192
)
)
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
temp_path = tmp.name
result_text = ""
image_found = False
for part in response.candidates[0].content.parts:
if hasattr(part, 'text') and part.text:
result_text += part.text
logger.info(f"์‘๋‹ต ํ…์ŠคํŠธ: {part.text}")
elif hasattr(part, 'inline_data') and part.inline_data:
save_binary_file(temp_path, part.inline_data.data)
image_found = True
logger.info("์‘๋‹ต์—์„œ ์ด๋ฏธ์ง€ ์ถ”์ถœ ์„ฑ๊ณต")
if not image_found:
return None, f"API์—์„œ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ์‘๋‹ต ํ…์ŠคํŠธ: {result_text}"
result_img = Image.open(temp_path)
if result_img.mode == "RGBA":
result_img = result_img.convert("RGB")
return result_img, f"์ด๋ฏธ์ง€๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. {result_text}"
except Exception as e:
logger.exception("์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
return None, f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
def process_images_with_prompt(image1, image2, image3, prompt, variation_index=0, max_retries=3):
retry_count = 0
last_error = None
while retry_count < max_retries:
try:
images = [image1, image2, image3]
valid_images = [img for img in images if img is not None]
if not valid_images:
return None, "์ ์–ด๋„ ํ•˜๋‚˜์˜ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.", ""
if prompt and prompt.strip():
processed_prompt = preprocess_prompt(prompt, image1, image2, image3)
if re.search("[๊ฐ€-ํžฃ]", processed_prompt):
final_prompt = translate_prompt_to_english(processed_prompt)
else:
final_prompt = processed_prompt
else:
if len(valid_images) == 1:
final_prompt = "Please creatively transform this image into a more vivid and artistic version. Do not include any text or watermarks in the generated image."
logger.info("Default prompt generated for single image")
elif len(valid_images) == 2:
final_prompt = "Please seamlessly composite these two images, integrating their key elements harmoniously into a single image. Do not include any text or watermarks in the generated image."
logger.info("Default prompt generated for two images")
else:
final_prompt = "Please creatively composite these three images, combining their main elements into a cohesive and natural scene. Do not include any text or watermarks in the generated image."
logger.info("Default prompt generated for three images")
result_img, status = generate_with_images(final_prompt, valid_images, variation_index)
if result_img is not None:
return result_img, status, final_prompt
else:
last_error = status
retry_count += 1
logger.warning(f"์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹คํŒจ, ์žฌ์‹œ๋„ {retry_count}/{max_retries}: {status}")
time.sleep(1)
except Exception as e:
last_error = str(e)
retry_count += 1
logger.exception(f"์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ, ์žฌ์‹œ๋„ {retry_count}/{max_retries}:")
time.sleep(1)
return None, f"์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜({max_retries}ํšŒ) ์ดˆ๊ณผ ํ›„ ์‹คํŒจ: {last_error}", prompt
def generate_multiple_images(image1, image2, image3, prompt, progress=gr.Progress()):
results = []
statuses = []
prompts = []
num_images = 4
max_retries = 3
progress(0, desc="์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค€๋น„ ์ค‘...")
for i in range(num_images):
progress((i / num_images), desc=f"{i+1}/{num_images} ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘...")
result_img, status, final_prompt = process_images_with_prompt(image1, image2, image3, prompt, i, max_retries)
if result_img is not None:
results.append(result_img)
statuses.append(f"์ด๋ฏธ์ง€ #{i+1}: {status}")
prompts.append(f"์ด๋ฏธ์ง€ #{i+1}: {final_prompt}")
else:
results.append(None)
statuses.append(f"์ด๋ฏธ์ง€ #{i+1} ์ƒ์„ฑ ์‹คํŒจ: {status}")
prompts.append(f"์ด๋ฏธ์ง€ #{i+1}: {final_prompt}")
time.sleep(1)
progress(1.0, desc="์ด๋ฏธ์ง€ ์ƒ์„ฑ ์™„๋ฃŒ!")
while len(results) < 4:
results.append(None)
combined_status = "\n".join(statuses)
combined_prompts = "\n".join(prompts)
return results[0], results[1], results[2], results[3], combined_status, combined_prompts
# ========== ์ด๋ฏธ์ง€ ํŽธ์ง‘๊ธฐ ๊ด€๋ จ ํ•จ์ˆ˜ ==========
def adjust_brightness(image, value):
"""์ด๋ฏธ์ง€ ๋ฐ๊ธฐ ์กฐ์ ˆ"""
value = float(value - 1) * 100 # 0-2 ๋ฒ”์œ„๋ฅผ -100์—์„œ +100์œผ๋กœ ๋ณ€ํ™˜
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
v = cv2.add(v, value)
v = np.clip(v, 0, 255)
final_hsv = cv2.merge((h, s, v))
return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)
def adjust_contrast(image, value):
"""์ด๋ฏธ์ง€ ๋Œ€๋น„ ์กฐ์ ˆ"""
value = float(value)
return np.clip(image * value, 0, 255).astype(np.uint8)
def adjust_saturation(image, value):
"""์ด๋ฏธ์ง€ ์ฑ„๋„ ์กฐ์ ˆ"""
value = float(value - 1) * 100 # 0-2 ๋ฒ”์œ„๋ฅผ -100์—์„œ +100์œผ๋กœ ๋ณ€ํ™˜
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
s = cv2.add(s, value)
s = np.clip(s, 0, 255)
final_hsv = cv2.merge((h, s, v))
return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)
def adjust_temperature(image, value):
"""์ด๋ฏธ์ง€ ์ƒ‰์˜จ๋„ ์กฐ์ ˆ (์ƒ‰์ƒ ๋ฐธ๋Ÿฐ์Šค)"""
value = float(value) * 30 # ํšจ๊ณผ ์Šค์ผ€์ผ ์กฐ์ ˆ
b, g, r = cv2.split(image)
if value > 0: # ๋”ฐ๋œปํ•˜๊ฒŒ
r = cv2.add(r, value)
b = cv2.subtract(b, value)
else: # ์ฐจ๊ฐ‘๊ฒŒ
r = cv2.add(r, value)
b = cv2.subtract(b, value)
r = np.clip(r, 0, 255)
b = np.clip(b, 0, 255)
return cv2.merge([b, g, r])
def adjust_tint(image, value):
"""์ด๋ฏธ์ง€ ์ƒ‰์กฐ ์กฐ์ ˆ"""
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv_image)
h = cv2.add(h, int(value))
h = np.clip(h, 0, 179) # Hue ๊ฐ’์€ 0-179 ๋ฒ”์œ„
final_hsv = cv2.merge((h, s, v))
return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)
def adjust_exposure(image, value):
"""์ด๋ฏธ์ง€ ๋…ธ์ถœ ์กฐ์ ˆ"""
enhancer = ImageEnhance.Brightness(Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)))
img_enhanced = enhancer.enhance(1 + float(value) / 5.0)
return cv2.cvtColor(np.array(img_enhanced), cv2.COLOR_RGB2BGR)
def adjust_vibrance(image, value):
"""์ด๋ฏธ์ง€ ํ™œ๊ธฐ ์กฐ์ ˆ"""
img = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
converter = ImageEnhance.Color(img)
factor = 1 + (float(value) / 100.0)
img = converter.enhance(factor)
return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
def adjust_color_mixer_blues(image, value):
"""์ด๋ฏธ์ง€ ์ปฌ๋Ÿฌ ๋ฏน์„œ (๋ธ”๋ฃจ) ์กฐ์ ˆ"""
b, g, r = cv2.split(image)
b = cv2.add(b, float(value))
b = np.clip(b, 0, 255)
return cv2.merge([b, g, r])
def adjust_shadows(image, value):
"""์ด๋ฏธ์ง€ ๊ทธ๋ฆผ์ž ์กฐ์ ˆ"""
pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
enhancer = ImageEnhance.Brightness(pil_image)
factor = 1 + (float(value) / 100.0)
pil_image = enhancer.enhance(factor)
return cv2.cvtColor(np.array(pil_image), cv2.COLOR_BGR2RGB)
def process_image(image, brightness, contrast, saturation, temperature, tint, exposure, vibrance, color_mixer_blues, shadows):
"""๋ชจ๋“  ์กฐ์ • ์‚ฌํ•ญ์„ ์ด๋ฏธ์ง€์— ์ ์šฉ"""
if image is None:
return None
# PIL ์ด๋ฏธ์ง€๋ฅผ OpenCV ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜
image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
# ์กฐ์ • ์‚ฌํ•ญ ์ˆœ์ฐจ ์ ์šฉ
image = adjust_brightness(image, brightness)
image = adjust_contrast(image, contrast)
image = adjust_saturation(image, saturation)
image = adjust_temperature(image, temperature)
image = adjust_tint(image, tint)
image = adjust_exposure(image, exposure)
image = adjust_vibrance(image, vibrance)
image = adjust_color_mixer_blues(image, color_mixer_blues)
image = adjust_shadows(image, shadows)
# PIL ์ด๋ฏธ์ง€๋กœ ๋‹ค์‹œ ๋ณ€ํ™˜
return Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
def download_edited_image(image, input_image_name):
"""์ด๋ฏธ์ง€๋ฅผ JPG ํ˜•์‹์œผ๋กœ ์ €์žฅํ•˜๊ณ  ๊ฒฝ๋กœ ๋ฐ˜ํ™˜"""
if image is None:
return None
# ํ•œ๊ตญ ์‹œ๊ฐ„ ํƒ€์ž„์Šคํƒฌํ”„ ์ƒ์„ฑ
def get_korean_timestamp():
korea_time = datetime.utcnow() + timedelta(hours=9)
return korea_time.strftime('%Y%m%d_%H%M%S')
timestamp = get_korean_timestamp()
if input_image_name and hasattr(input_image_name, 'name'):
base_name = input_image_name.name.split('.')[0] # ํŒŒ์ผ ๊ฐ์ฒด์—์„œ ์ด๋ฆ„ ์ถ”์ถœ
else:
base_name = "์ด๋ฏธ์ง€"
file_name = f"[๋์žฅAI]๋์žฅํ•„ํ„ฐ_{base_name}_{timestamp}.jpg"
# ํŒŒ์ผ ์ €์žฅ
temp_file_path = tempfile.gettempdir() + "/" + file_name
image.save(temp_file_path, format="JPEG")
return temp_file_path
# ์ปค์Šคํ…€ CSS ์Šคํƒ€์ผ - ์ƒˆ๋กœ์šด ์Šคํƒ€์ผ ์ ์šฉ
custom_css = """
:root {
--primary-color: #FB7F0D;
--secondary-color: #ff9a8b;
--accent-color: #FF6B6B;
--background-color: #FFF3E9;
--card-bg: #ffffff;
--text-color: #334155;
--border-radius: 18px;
--shadow: 0 8px 30px rgba(251, 127, 13, 0.08);
}
body {
font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
background-color: var(--background-color);
color: var(--text-color);
line-height: 1.6;
}
/* Gradio ์ปจํ…Œ์ด๋„ˆ ์˜ค๋ฒ„๋ผ์ด๋“œ */
.gradio-container {
max-width: 100% !important; /* 200%์—์„œ 100%๋กœ ๋ณ€๊ฒฝ */
width: 100% !important; /* ์ถ”๊ฐ€: ๋„ˆ๋น„ 100% ์ง€์ • */
margin: 0 auto !important;
padding: 0 !important;
background-color: var(--background-color) !important;
box-sizing: border-box !important; /* ์ถ”๊ฐ€: ํŒจ๋”ฉ์ด ๋„ˆ๋น„์— ํฌํ•จ๋˜๋„๋ก ์„ค์ • */
}
/* ์ถ”๊ฐ€: ๋‚ด๋ถ€ ์ปจํ…Œ์ด๋„ˆ๋„ 100% ๋„ˆ๋น„๋กœ ์„ค์ • */
.contain {
max-width: 100% !important;
width: 100% !important;
}
/* ์ถ”๊ฐ€: ๊ฐ ํ–‰(Row)๋„ 100% ๋„ˆ๋น„๋กœ ์„ค์ • */
.gr-padded {
padding: 0 !important;
width: 100% !important;
max-width: 100% !important;
}
/* ํŒจ๋„ ์Šคํƒ€์ผ๋ง */
.gr-group {
background-color: var(--card-bg);
border-radius: var(--border-radius) !important;
box-shadow: var(--shadow) !important;
padding: 1.5rem !important;
margin-bottom: 1.5rem !important;
border: 1px solid rgba(0, 0, 0, 0.04) !important;
transition: transform 0.3s ease;
}
.gr-group:hover {
transform: translateY(-5px);
}
/* ์„น์…˜ ์ œ๋ชฉ */
.section-title {
font-size: 22px !important;
font-weight: 700 !important;
color: #333333 !important;
margin-bottom: 1rem !important;
padding-bottom: 0.5rem !important;
border-bottom: 2px solid var(--primary-color) !important;
display: flex;
align-items: center;
}
.section-title span {
color: var(--primary-color);
}
/* ๋ฒ„ํŠผ ์Šคํƒ€์ผ๋ง */
.custom-button {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important;
color: white !important;
font-weight: 600 !important;
border: none !important;
border-radius: 30px !important;
padding: 12px 24px !important;
box-shadow: 0 4px 8px rgba(251, 127, 13, 0.25) !important;
transition: all 0.3s ease !important;
text-transform: none !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
.custom-button:hover {
transform: translateY(-2px) !important;
box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3) !important;
}
.custom-button.primary {
background: linear-gradient(135deg, var(--accent-color), #ff9a8b) !important;
}
/* ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ */
.image-container {
border-radius: var(--border-radius);
overflow: hidden;
border: 1px solid rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
background-color: white;
}
.image-container:hover {
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
/* ํƒญ ์Šคํƒ€์ผ ๊ฐœ์„  */
.tabs {
border-bottom: none !important;
}
.tab-nav {
background-color: transparent !important;
border-bottom: 1px solid #eeeeee !important;
padding: 0 !important;
}
.tab-nav button {
border-radius: var(--border-radius) var(--border-radius) 0 0 !important;
margin-right: 5px !important;
padding: 12px 20px !important;
font-size: 18px !important;
font-weight: 600 !important;
border: 1px solid #eeeeee !important;
border-bottom: none !important;
background-color: rgba(255, 255, 255, 0.7) !important;
color: var(--text-color) !important;
transition: all 0.3s ease !important;
min-width: 150px !important;
text-align: center !important;
}
.tab-nav button.selected {
background-color: var(--primary-color) !important;
color: white !important;
border-color: var(--primary-color) !important;
box-shadow: 0 -2px 6px rgba(251, 127, 13, 0.2) !important;
}
.tab-nav button:hover:not(.selected) {
background-color: var(--background-color) !important;
border-bottom: none !important;
}
/* ์ž…๋ ฅ ํ•„๋“œ ์Šคํƒ€์ผ */
.gr-input, .gr-text-input, .gr-sample-inputs {
border-radius: var(--border-radius) !important;
border: 1px solid #dddddd !important;
padding: 12px !important;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05) !important;
transition: all 0.3s ease !important;
}
.gr-input:focus, .gr-text-input:focus {
border-color: var(--primary-color) !important;
outline: none !important;
box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important;
}
/* ๋ฒ„ํŠผ ๊ทธ๋ฃน */
.button-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 0.8rem;
margin-bottom: 1.2rem;
}
/* ๋ฉ”์ธ ์ปจํ…์ธ  ์Šคํฌ๋กค๋ฐ” */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: var(--primary-color);
border-radius: 10px;
}
/* ํŽธ์ง‘๊ธฐ ํŠน๋ณ„ ์Šคํƒ€์ผ */
.editor-section {
background-color: var(--card-bg);
border-radius: var(--border-radius);
box-shadow: var(--shadow);
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.editor-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--primary-color);
margin-bottom: 1rem;
display: flex;
align-items: center;
}
.editor-title i {
margin-right: 0.5rem;
}
.download-button {
background-color: var(--accent-color) !important;
color: white !important;
border: none !important;
padding: 10px !important;
font-size: 12px !important;
border-radius: var(--border-radius) !important;
font-weight: 600 !important;
transition: all 0.3s ease !important;
}
.download-button:hover {
transform: translateY(-2px) !important;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15) !important;
}
.download-container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.download-output {
width: 100%;
margin-top: 1rem;
}
/* ์• ๋‹ˆ๋ฉ”์ด์…˜ ์Šคํƒ€์ผ */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
/* Examples ์„น์…˜ ์Šคํƒ€์ผ */
.examples-section {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
margin-top: 1rem;
}
.example-item {
background-color: white;
border-radius: var(--border-radius);
overflow: hidden;
box-shadow: var(--shadow);
transition: transform 0.3s ease;
}
.example-item:hover {
transform: translateY(-5px);
}
/* ๋ฐ˜์‘ํ˜• */
@media (max-width: 768px) {
.button-grid {
grid-template-columns: repeat(2, 1fr);
}
.examples-section {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
}
""" # CSS ๋ฌธ์ž์—ด ์ข…๋ฃŒ
# FontAwesome ์•„์ด์ฝ˜ ํฌํ•จ
fontawesome_link = """
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
"""
# ์ œ๋ชฉ๊ณผ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ ์ œ๊ฑฐ
header_html = ""
image_generator_guide_html = ""
image_editor_guide_html = ""
# UI ๊ตฌ์„ฑ
with gr.Blocks(css=custom_css, theme=gr.themes.Default(
primary_hue="orange",
secondary_hue="orange",
font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"]
)) as demo:
gr.HTML(fontawesome_link)
# ์ œ๋ชฉ ์ œ๊ฑฐ
# gr.HTML(header_html)
with gr.Tabs(elem_classes="tabs") as tabs:
# ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ ํƒญ
with gr.Tab("โœจ ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ", elem_classes="tab-content"):
# ์‚ฌ์šฉ ๊ฐ€์ด๋“œ ์„น์…˜ ์ œ๊ฑฐ
# gr.HTML(image_generator_guide_html)
with gr.Row(equal_height=True):
with gr.Column(scale=1):
# API ํ‚ค ์ž…๋ ฅ ์„น์…˜ ์ œ๊ฑฐ
# ======== ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฐ ์„ค์ • ์„น์…˜ ========
with gr.Group():
gr.HTML('<div class="section-title"><i class="fas fa-upload"></i> <span>์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฐ ์„ค์ •</span></div>')
with gr.Row():
image1_input = gr.Image(type="pil", label="#1", image_mode="RGB", elem_classes="image-container", height=400)
image2_input = gr.Image(type="pil", label="#2", image_mode="RGB", elem_classes="image-container", height=400)
image3_input = gr.Image(type="pil", label="#3", image_mode="RGB", elem_classes="image-container", height=400)
# ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ ํ•„๋“œ ์ถ”๊ฐ€
prompt_input = gr.Textbox(
lines=3,
placeholder="ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•˜๊ฑฐ๋‚˜ ๋น„์›Œ๋‘๋ฉด ์ž๋™ ํ•ฉ์„ฑ๋ฉ๋‹ˆ๋‹ค. '#1', '#2', '#3'์œผ๋กœ ๊ฐ ์ด๋ฏธ์ง€๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
label="ํ”„๋กฌํ”„ํŠธ (์„ ํƒ ์‚ฌํ•ญ)",
elem_classes="gr-text-input"
)
# ======== ๋ณ€ํ™˜ ์˜ต์…˜ ์„น์…˜ ========
with gr.Group():
gr.HTML('<div class="section-title"><i class="fas fa-sliders-h"></i> <span>ํ”„๋กฌํ”„ํŠธ ํ…œํ”Œ๋ฆฟ</span></div>')
with gr.Column(elem_classes="button-grid"):
image_change_btn1 = gr.Button('๐Ÿ”„ ๋ถ€๋ถ„๋ณ€๊ฒฝ-1', elem_classes="custom-button")
image_change_btn2 = gr.Button('๐Ÿ”„ ๋ถ€๋ถ„๋ณ€๊ฒฝ-2', elem_classes="custom-button")
image_change_btn3= gr.Button('๐Ÿ”„ ๋ถ€๋ถ„๋ณ€๊ฒฝ-3', elem_classes="custom-button")
text_remove_btn = gr.Button('๐Ÿงน ๊ธ€์ž์ง€์šฐ๊ธฐ', elem_classes="custom-button")
text_change_btn = gr.Button('๐Ÿ”ค ๊ธ€์ž๋ณ€๊ฒฝ', elem_classes="custom-button")
clothes_change_btn1 = gr.Button('๐Ÿ‘• ์ƒํ’ˆ์ฐฉ์šฉ-1', elem_classes="custom-button")
clothes_change_btn2 = gr.Button('๐Ÿ‘“ ์ƒํ’ˆ์ฐฉ์šฉ-2', elem_classes="custom-button")
holding_product_btn = gr.Button('๐Ÿท ์ƒํ’ˆ๋“ค๊ณ ', elem_classes="custom-button")
background_change_btn = gr.Button('๐Ÿ–ผ๏ธ ๋ฐฐ๊ฒฝ๋ฐ”๊พธ๊ธฐ', elem_classes="custom-button")
composite_product_btn = gr.Button('โœ‚๏ธ ๋ถ€๋ถ„์ง€์šฐ๊ธฐ', elem_classes="custom-button")
outpainting_btn = gr.Button('๐Ÿ” ์ด๋ฏธ์ง€ํ™•์žฅ', elem_classes="custom-button")
food_btn_1 = gr.Button('๐Ÿฝ๏ธ ํ”Œ๋ ˆ์ดํŒ…-1', elem_classes="custom-button")
food_btn_2 = gr.Button('๐Ÿฝ๏ธ ํ”Œ๋ ˆ์ดํŒ…-2', elem_classes="custom-button")
food_btn_3 = gr.Button('๐Ÿฝ๏ธ ํ”Œ๋ ˆ์ดํŒ…-3', elem_classes="custom-button")
# ======== ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์„น์…˜ ========
with gr.Group():
gr.HTML('<div class="section-title"><i class="fas fa-image"></i> <span>์ด๋ฏธ์ง€ ์ƒ์„ฑ</span></div>')
submit_single_btn = gr.Button('โœจ ์ด๋ฏธ์ง€ ์ƒ์„ฑ (1์žฅ)', elem_classes="custom-button primary")
submit_btn = gr.Button('โœจ ์ด๋ฏธ์ง€ ์ƒ์„ฑ (4์žฅ)', elem_classes="custom-button primary")
with gr.Column(scale=1):
# ======== ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€ ์„น์…˜ ========
with gr.Group():
gr.HTML('<div class="section-title"><i class="fas fa-images"></i> <span>์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€</span></div>')
with gr.Row():
with gr.Column():
output_image1 = gr.Image(label="์ด๋ฏธ์ง€ #1", elem_classes="image-container", height=400)
output_image3 = gr.Image(label="์ด๋ฏธ์ง€ #3", elem_classes="image-container", height=400)
with gr.Column():
output_image2 = gr.Image(label="์ด๋ฏธ์ง€ #2", elem_classes="image-container", height=400)
output_image4 = gr.Image(label="์ด๋ฏธ์ง€ #4", elem_classes="image-container", height=400)
# ======== ๊ฒฐ๊ณผ ์ •๋ณด ์„น์…˜ ========
with gr.Group():
gr.HTML('<div class="section-title"><i class="fas fa-info-circle"></i> <span>๊ฒฐ๊ณผ ์ •๋ณด</span></div>')
output_text = gr.Textbox(label="์ƒํƒœ ๋ฉ”์‹œ์ง€", lines=2, elem_classes="gr-text-input")
prompt_display = gr.Textbox(label="์‚ฌ์šฉ๋œ ํ”„๋กฌํ”„ํŠธ (์˜์–ด)", visible=True, lines=2, elem_classes="gr-text-input")
# ======== ์˜ˆ์ œ ์ด๋ฏธ์ง€ ์„น์…˜ ========
gr.HTML('<div class="section-title"><i class="fas fa-lightbulb"></i> <span>์˜ˆ์ œ ์ด๋ฏธ์ง€</span></div>')
# ๋ชจ๋“  ์˜ˆ์ œ ํ•œ ํŽ˜์ด์ง€์— ํ‘œ์‹œ
examples = [
["down/๋ชจ๋ธ.jpg", None, None, "(#1์˜ ์—ฌ์„ฑ)์ด ์‚ด์ง ๋’ค๋กœ ๋Œ์•„๋ณด๋Š” ๋ชจ์Šต์œผ๋กœ ์ตœ๋Œ€ํ•œ ์ด์ „ seed๋ฅผ ์œ ์ง€ํ•œ์ฒด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ณ€๊ฒฝํ•˜๋ผ."],
["down/์ƒ์–ด๋ ˆ๊ณ ๋ชจํ˜•.png", None, None, "(#1 ๋ ˆ๋ชจ๋ชจํ˜•)์—์„œ ์ฒญ์ƒ‰์ƒ์–ด๋ ˆ๊ณ ๋งŒ ๊ฒ€์€์ƒ‰ ๊ณ ๋ž˜๋ ˆ๊ณ ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„์€ seed๋ฅผ ๋ณ€๊ฒฝ์„ ํ•˜์ง€๋งˆ๋ผ."],
["down/์–ผ์Œ๊ฐ€๋ฐฉ.png", None, None, "(#1 ์—ฌํ–‰์šฉ ์–ผ์Œ๋ฐ•์Šค)์•ž์— ์–ผ์Œ์ด ๋‹ด๊ธด 3์ž”์˜ ์ฝœ๋ผ๊ฐ€ ๋†“์—ฌ์žˆ๋Š” ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ."],
["down/์ค‘๊ตญ์–ด.png", None, None, "(#1 ์ด๋ฏธ์ง€)์— ์žˆ๋Š” ์ค‘๊ตญ์–ด๋ฅผ ๋ชจ๋‘ ์ œ๊ฑฐํ•˜๋ผ."],
["down/ํ…์ŠคํŠธ.webp", None, None, '(#1์˜ ํ…์ŠคํŠธ)๋ฅผ ์Šคํƒ€์ผ์„ ์œ ์ง€ํ•œ์ฒด ํ…์ŠคํŠธ๋งŒ "Hello"๋กœ ๋ฐ”๊ฟ”๋ผ'],
["down/๋ชจ๋ธ2.png", "down/์„ ๊ธ€๋ผ์Šค.png", "down/์ฒญ๋ฐ”์ง€.png", "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด ์‹ ์ฒด ๋น„์œจ๊ณผ ํฌ์ฆˆ๋Š” ์œ ์ง€ํ•œ ์ฒด (#2์˜ ์„ ๊ธ€๋ผ์Šค)์™€ (#3์˜ ์ฒญ๋ฐ”์ง€)๋ฅผ ์ง์ ‘ ๋ชจ๋ธ์ด ์ฐฉ์šฉํ•œ๊ฒƒ ์ฒ˜๋Ÿผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."],
["down/๋ชจ๋ธ2.png", "down/์„ ๊ธ€๋ผ์Šค.png", "down/์นดํŽ˜์ „๊ฒฝ.png", "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด (#2์˜ ์„ ๊ธ€๋ผ์Šค)์„ ์ฐฉ์šฉํ•˜๊ณ  (#3์˜ ๋’ท๋ฐฐ๊ฒฝ์˜ ์นดํŽ˜์ „์ฒด๊ฐ€ ๋ณด์ด๋ฉฐ) ์˜์ž์— ์•‰์•„ ์žˆ๋Š” ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."],
["down/๋ชจ๋ธ2.png", "down/์™€์ธ์ž”.png", None, "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด(#2์˜ ์™€์ธ์ž”)์„ ๋“ค๊ณ  ์žˆ๋Š” ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."],
["down/๋ชจ๋ธ2.png", "down/์นดํŽ˜์ „๊ฒฝ.png", None, "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด (#2 ์นดํŽ˜)์—์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์žˆ๋Š” ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ."],
["down/์ƒ์–ด๋ ˆ๊ณ ๋ชจํ˜•.png", None, None, "(#1์˜ ๋ ˆ๊ณ ๋ชจํ˜•)์—์„œ ์ฒญ์ƒ‰์ƒ์–ด๋ ˆ๊ณ ๋ฅผ ์ œ๊ฑฐํ•œ ํ›„, ๊ทธ ์ž๋ฆฌ๋ฅผ ์ฃผ๋ณ€ ๋ฐฐ๊ฒฝ๊ณผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์–ด์šฐ๋Ÿฌ์ง€๋„๋ก ์ฑ„์›Œ์ฃผ์„ธ์š”. ๋‹จ, ์ด๋ฏธ์ง€์˜ ๋‹ค๋ฅธ ๋ถ€๋ถ„์˜ ์ฃผ์š” ์š”์†Œ๋Š” ๋™์ผํ•˜๊ฒŒ ์œ ์ง€ํ•ด์•ผํ•œ๋‹ค."],
["down/์นดํŽ˜์ „๊ฒฝ.png", None, None, "(#1 ์ด๋ฏธ์ง€)๋ฅผ ์›๋ณธ๊ทธ๋Œ€๋กœ ์ค‘์•™์— ๋‘๊ณ  ๋น„์œจ๋กœ ์œ ์ง€ํ•œ ์ฒด ์œ„์•„๋ž˜ ๋ฐ ์ขŒ์šฐ๋กœ ํฌ๊ฒŒ ํ™•์žฅํ•˜๋ผ."],
["down/์ƒ๋Ÿฌ๋“œ.png", None, None, "(#1์…€๋Ÿฌ๋“œ)์— ๋‹ด์€ ์šฉ๊ธฐ๋Š” ๋ฒ„๋ฆฌ๊ณ  ๋„“๊ณ  ํฐ ์˜ˆ์œ ์ ‘์‹œ์— (#1์…€๋Ÿฌ๋“œ)์Œ์‹๋งŒ ๊ฐ€๋“ ์ฑ„์›Œ์„œ ์ƒ์—…์ ์ธ ๊ฐ๋„๋กœ ์–ด์šธ๋ฆฌ๋Š” ์†Œํ’ˆ๊ณผ ํ•จ๊ป˜ ํ”Œ๋ ˆ์ดํŒ… ํ•œ ๋ชจ์Šต์„ ์ด๋ฏธ์ง€๋กœ ์ƒ์„ฑํ•˜๋ผ. "],
["down/์ƒ๋Ÿฌ๋“œ.png", "down/ํ”Œ๋ ˆ์ดํŒ….png", None, "(#2 ํ”Œ๋ ˆ์ดํŒ…ํ•œ ์ด๋ฏธ์ง€)์— ๋‹ด๊ธด ์Œ์‹์„ (#1 ์ƒ๋Ÿฌ๋“œ)๋กœ ๋ฐ”๊พธ๊ณ  ๋‚˜๋จธ์ง€๋Š” ์‹œ๋“œ๋ฅผ ์œ ์ง€ํ•œ ์ฒด ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ."],
["down/์ปต.png", None, None, "(#1์ปต)์— ๋”ธ๊ธฐ, ๋ฐ”๋‹๋ผ, ์ดˆ์ฝ” ์•„์ด์Šคํฌ๋ฆผ์„ ๋‹ด๊ณ  ๊ทธ ์œ„์— ์ดˆ์ฝ” ์‹œ๋Ÿฝ์ด ํ๋ฅด๊ฒŒ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ."]
]
# ๋ชจ๋“  ์˜ˆ์ œ๋ฅผ ํ•œ ํŽ˜์ด์ง€์— ํ‘œ์‹œํ•˜๋„๋ก ์ˆ˜์ •๋œ ๋ถ€๋ถ„
gr.Examples(
examples=examples,
inputs=[image1_input, image2_input, image3_input, prompt_input],
examples_per_page=len(examples) # ๋ชจ๋“  ์˜ˆ์ œ๋ฅผ ํ•œ ํŽ˜์ด์ง€์— ํ‘œ์‹œ
)
# ์ด๋ฏธ์ง€ ํŽธ์ง‘๊ธฐ ํƒญ ์ถ”๊ฐ€
with gr.Tab("๐ŸŽจ ์ด๋ฏธ์ง€ ํŽธ์ง‘๊ธฐ", elem_classes="tab-content"):
# ์‚ฌ์šฉ ๊ฐ€์ด๋“œ ์„น์…˜ ์ œ๊ฑฐ
# gr.HTML(image_editor_guide_html)
with gr.Row():
# ์™ผ์ชฝ ์—ด: ๋น„์œจ 1
with gr.Column(scale=1):
# ======== ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์„น์…˜ ========
with gr.Group():
gr.HTML('<div class="section-title"><i class="fas fa-upload"></i> <span>์ด๋ฏธ์ง€ ์—…๋กœ๋“œ</span></div>')
edit_input_image = gr.Image(type="pil", label="ํŽธ์ง‘ํ•  ์ด๋ฏธ์ง€", elem_classes="image-container")
# ======== ์ด๋ฏธ์ง€ ์กฐ์ • ์„น์…˜ ========
with gr.Group():
gr.HTML('<div class="section-title"><i class="fas fa-sliders-h"></i> <span>์ด๋ฏธ์ง€ ์กฐ์ •</span></div>')
brightness_slider = gr.Slider(0.0, 2.0, value=1.0, step=0.1, label="๋ฐ๊ธฐ ์กฐ์ ˆ")
contrast_slider = gr.Slider(0.5, 1.5, value=1.0, step=0.1, label="๋Œ€๋น„ ์กฐ์ ˆ")
saturation_slider = gr.Slider(0.0, 2.0, value=1.0, step=0.1, label="์ฑ„๋„ ์กฐ์ ˆ")
temperature_slider = gr.Slider(-1.0, 1.0, value=0.0, step=0.1, label="์ƒ‰์˜จ๋„ ์กฐ์ ˆ")
tint_slider = gr.Slider(-100, 100, value=0, step=1, label="์ƒ‰์กฐ ์กฐ์ ˆ")
exposure_slider = gr.Slider(-5.0, 5.0, value=0.0, step=0.1, label="๋…ธ์ถœ ์กฐ์ ˆ")
vibrance_slider = gr.Slider(-100.0, 100.0, value=0.0, step=1.0, label="ํ™œ๊ธฐ ์กฐ์ ˆ")
color_mixer_blues_slider = gr.Slider(-100.0, 100.0, value=0.0, step=1.0, label="์ปฌ๋Ÿฌ ๋ฏน์„œ (๋ธ”๋ฃจ)")
shadows_slider = gr.Slider(-100.0, 100.0, value=0.0, step=1.0, label="๊ทธ๋ฆผ์ž ์กฐ์ ˆ")
# ์˜ค๋ฅธ์ชฝ ์—ด: ๋น„์œจ 1
with gr.Column(scale=1):
# ======== ํŽธ์ง‘๋œ ์ด๋ฏธ์ง€ ์„น์…˜ ========
with gr.Group():
gr.HTML('<div class="section-title"><i class="fas fa-images"></i> <span>ํŽธ์ง‘๋œ ์ด๋ฏธ์ง€</span></div>')
edit_output_image = gr.Image(type="pil", label="ํŽธ์ง‘๋œ ์ด๋ฏธ์ง€", elem_classes="image-container")
# ======== ์ €์žฅ ์„น์…˜ ========
with gr.Group():
gr.HTML('<div class="section-title"><i class="fas fa-download"></i> <span>์ €์žฅ</span></div>')
with gr.Row(elem_classes="download-container"):
download_button = gr.Button("JPG๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ", elem_classes="download-button")
with gr.Row(elem_classes="download-container"):
download_output = gr.File(label="JPG ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ", elem_classes="download-output")
# ========== ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ ==========
# ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
image_change_btn1.click(
fn=lambda: "(#1์˜ ์—ฌ์„ฑ)์ด ์‚ด์ง ๋’ค๋กœ ๋Œ์•„๋ณด๋Š” ๋ชจ์Šต์œผ๋กœ ์ตœ๋Œ€ํ•œ ์ด์ „ seed๋ฅผ ์œ ์ง€ํ•œํ…Œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ณ€๊ฒฝํ•˜๋ผ.",
inputs=[],
outputs=prompt_input
)
image_change_btn2.click(
fn=lambda: "(#1 ๋ ˆ๋ชจ๋ชจํ˜•)์—์„œ ์ฒญ์ƒ‰์ƒ์–ด๋ ˆ๊ณ ๋งŒ ๊ฒ€์€์ƒ‰ ๊ณ ๋ž˜๋ ˆ๊ณ ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  ๋‚˜๋จธ์ง€ ๋ถ€๋ถ„์€ seed๋ฅผ ๋ณ€๊ฒฝ์„ ํ•˜์ง€๋งˆ๋ผ.",
inputs=[],
outputs=prompt_input
)
image_change_btn3.click(
fn=lambda: "(#1 ์—ฌํ–‰์šฉ ์–ผ์Œ๋ฐ•์Šค)์•ž์— ์–ผ์Œ์ด ๋‹ด๊ธด 3์ž”์˜ ์ฝœ๋ผ๊ฐ€ ๋†“์—ฌ์žˆ๋Š” ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ.",
inputs=[],
outputs=prompt_input
)
text_remove_btn.click(
fn=lambda: "(#1 ์ด๋ฏธ์ง€)์— ์žˆ๋Š” ์ค‘๊ตญ์–ด๋ฅผ ๋ชจ๋‘ ์ œ๊ฑฐํ•˜๋ผ.",
inputs=[],
outputs=prompt_input
)
text_change_btn.click(
fn=lambda: '(#1์˜ ํ…์ŠคํŠธ)๋ฅผ ์Šคํƒ€์ผ์„ ์œ ์ง€ํ•œ์ฒด ํ…์ŠคํŠธ๋งŒ "Hello"๋กœ ๋ฐ”๊ฟ”๋ผ',
inputs=[],
outputs=prompt_input
)
clothes_change_btn1.click(
fn=lambda: "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด ์‹ ์ฒด ๋น„์œจ๊ณผ ํฌ์ฆˆ๋Š” ์œ ์ง€ํ•œ ์ฒด (#2์˜ ์„ ๊ธ€๋ผ์Šค)์™€ (#3์˜ ์ฒญ๋ฐ”์ง€)๋ฅผ ์ง์ ‘ ๋ชจ๋ธ์ด ์ฐฉ์šฉํ•œ๊ฒƒ ์ฒ˜๋Ÿผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ.",
inputs=[],
outputs=prompt_input
)
clothes_change_btn2.click(
fn=lambda: "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด (#2์˜ ์„ ๊ธ€๋ผ์Šค)์„ ์ฐฉ์šฉํ•˜๊ณ  (#3์˜ ๋’ท๋ฐฐ๊ฒฝ์˜ ์นดํŽ˜์ „์ฒด๊ฐ€ ๋ณด์ด๋ฉฐ) ์˜์ž์— ์•‰์•„ ์žˆ๋Š” ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ.",
inputs=[],
outputs=prompt_input
)
holding_product_btn.click(
fn=lambda: "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด(#2์˜ ์™€์ธ์ž”)์„ ๋“ค๊ณ  ์žˆ๋Š” ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ.",
inputs=[],
outputs=prompt_input
)
background_change_btn.click(
fn=lambda: "(#1์˜ ์—ฌ์„ฑ๋ชจ๋ธ)์ด (#2 ์นดํŽ˜)์—์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์žˆ๋Š” ๋ชจ์Šต์„ ์ƒ์„ฑํ•˜๋ผ.",
inputs=[],
outputs=prompt_input
)
composite_product_btn.click(
fn=lambda: "(#1์˜ ๋ ˆ๊ณ ๋ชจํ˜•)์—์„œ ์ฒญ์ƒ‰์ƒ์–ด๋ ˆ๊ณ ๋ฅผ ์ œ๊ฑฐํ•œ ํ›„, ๊ทธ ์ž๋ฆฌ๋ฅผ ์ฃผ๋ณ€ ๋ฐฐ๊ฒฝ๊ณผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์–ด์šฐ๋Ÿฌ์ง€๋„๋ก ์ฑ„์›Œ์ฃผ์„ธ์š”. ๋‹จ, ์ด๋ฏธ์ง€์˜ ๋‹ค๋ฅธ ๋ถ€๋ถ„์˜ ์ฃผ์š” ์š”์†Œ๋Š” ๋™์ผํ•˜๊ฒŒ ์œ ์ง€ ํ•ด์•ผํ•œ๋‹ค.",
inputs=[],
outputs=prompt_input
)
outpainting_btn.click(
fn=lambda: "(#1 ์ด๋ฏธ์ง€)๋ฅผ ์›๋ณธ๊ทธ๋Œ€๋กœ ์ค‘์•™์— ๋‘๊ณ  ๋น„์œจ๋กœ ์œ ์ง€ํ•œ ์ฒด ์œ„์•„๋ž˜ ๋ฐ ์ขŒ์šฐ๋กœ ํฌ๊ฒŒ ํ™•์žฅํ•˜๋ผ.",
inputs=[],
outputs=prompt_input
)
food_btn_1.click(
fn=lambda: "(#1์…€๋Ÿฌ๋“œ)์— ๋‹ด์€ ์šฉ๊ธฐ๋Š” ๋ฒ„๋ฆฌ๊ณ  ๋„“๊ณ  ํฐ ์˜ˆ์œ ์ ‘์‹œ์— (#1์…€๋Ÿฌ๋“œ)์Œ์‹๋งŒ ๊ฐ€๋“ ์ฑ„์›Œ์„œ ์ƒ์—…์ ์ธ ๊ฐ๋„๋กœ ์–ด์šธ๋ฆฌ๋Š” ์†Œํ’ˆ๊ณผ ํ•จ๊ป˜ ํ”Œ๋ ˆ์ดํŒ… ํ•œ ๋ชจ์Šต์„ ์ด๋ฏธ์ง€๋กœ ์ƒ์„ฑํ•˜๋ผ. ",
inputs=[],
outputs=prompt_input
)
food_btn_2.click(
fn=lambda: "(#2 ํ”Œ๋ ˆ์ดํŒ…ํ•œ ์ด๋ฏธ์ง€)์— ๋‹ด๊ธด ์Œ์‹์„ (#1 ์ƒ๋Ÿฌ๋“œ)๋กœ ๋ฐ”๊พธ๊ณ  ๋‚˜๋จธ์ง€๋Š” ์‹œ๋“œ๋ฅผ ์œ ์ง€ํ•œ ์ฒด ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ.",
inputs=[],
outputs=prompt_input
)
food_btn_3.click(
fn=lambda: "(#1์ปต)์— ๋”ธ๊ธฐ, ๋ฐ”๋‹๋ผ, ์ดˆ์ฝ” ์•„์ด์Šคํฌ๋ฆผ์„ ๋‹ด๊ณ  ๊ทธ ์œ„์— ์ดˆ์ฝ” ์‹œ๋Ÿฝ์ด ํ๋ฅด๊ฒŒ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋ผ.",
inputs=[],
outputs=prompt_input
)
# ๋‹จ์ผ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ - API ํ‚ค ์ž…๋ ฅ๊ฐ’ ์ œ๊ฑฐ
def generate_single_image(image1, image2, image3, prompt):
return process_images_with_prompt(image1, image2, image3, prompt, 0, 3)
submit_single_btn.click(
fn=generate_single_image,
inputs=[image1_input, image2_input, image3_input, prompt_input],
outputs=[output_image1, output_text, prompt_display],
)
# 4์žฅ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ - API ํ‚ค ์ž…๋ ฅ๊ฐ’ ์ œ๊ฑฐ
submit_btn.click(
fn=generate_multiple_images,
inputs=[image1_input, image2_input, image3_input, prompt_input],
outputs=[output_image1, output_image2, output_image3, output_image4, output_text, prompt_display],
)
# ========== ์ด๋ฏธ์ง€ ํŽธ์ง‘๊ธฐ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ ==========
# ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜ ์—ฐ๊ฒฐ
edit_inputs = [
edit_input_image,
brightness_slider,
contrast_slider,
saturation_slider,
temperature_slider,
tint_slider,
exposure_slider,
vibrance_slider,
color_mixer_blues_slider,
shadows_slider
]
edit_input_components = [
brightness_slider,
contrast_slider,
saturation_slider,
temperature_slider,
tint_slider,
exposure_slider,
vibrance_slider,
color_mixer_blues_slider,
shadows_slider
]
for input_component in edit_input_components:
input_component.change(
fn=process_image,
inputs=edit_inputs,
outputs=edit_output_image
)
# ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์‹œ ์ž๋™์œผ๋กœ ํŽธ์ง‘ ์—…๋ฐ์ดํŠธ
edit_input_image.change(
fn=process_image,
inputs=edit_inputs,
outputs=edit_output_image
)
# ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ๊ธฐ๋Šฅ
download_button.click(
fn=download_edited_image,
inputs=[edit_output_image, edit_input_image],
outputs=download_output
)
# API ํ‚ค ์ดˆ๊ธฐํ™” ๋ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰
initialize_api_keys() # API ํ‚ค ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜ ํ˜ธ์ถœ
demo.queue()
demo.launch(share=False, inbrowser=True, width="100%") # width ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€