img_3 / app.py
Kims12's picture
Update app.py
f9fa24e verified
raw
history blame
27 kB
import os
import tempfile
import json
from PIL import Image
import gradio as gr
import logging
import re
import time
from google import genai
from google.genai import types
from dotenv import load_dotenv
load_dotenv()
# Logging setup
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Gemini API ํ‚ค ์„ค์ •
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
genai.configure(api_key=GEMINI_API_KEY)
# ๋ฐฐ๊ฒฝ JSON ํŒŒ์ผ ๊ฒฝ๋กœ ์„ค์ • - ์ƒ๋Œ€ ๊ฒฝ๋กœ ์‚ฌ์šฉ
BACKGROUNDS_DIR = "./background"
# ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ
if not os.path.exists(BACKGROUNDS_DIR):
os.makedirs(BACKGROUNDS_DIR)
logger.info(f"๋ฐฐ๊ฒฝ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค: {BACKGROUNDS_DIR}")
else:
logger.info(f"๋ฐฐ๊ฒฝ ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค: {BACKGROUNDS_DIR}")
# ๊ธฐ๋ณธ ๋ฐฐ๊ฒฝ ๋ฐ์ดํ„ฐ
SIMPLE_BACKGROUNDS = {"ํ™”์ดํŠธ ๋ฐฐ๊ฒฝ": "white background", "๋ธ”๋ž™ ๋ฐฐ๊ฒฝ": "black background", "๊ทธ๋ผ๋ฐ์ด์…˜ ๋ฐฐ๊ฒฝ": "gradient background"}
STUDIO_BACKGROUNDS = {"์ œํ’ˆ ์‚ฌ์ง„ ์ŠคํŠœ๋””์˜ค": "product photography studio", "๋ฏธ๋‹ˆ๋ฉ€ ์ŠคํŠœ๋””์˜ค": "minimal studio"}
NATURE_BACKGROUNDS = {"์—ด๋Œ€ ํ•ด๋ณ€": "tropical beach", "์ˆฒ์†": "forest", "์‚ฐ์•… ์ง€๋Œ€": "mountain landscape"}
INDOOR_BACKGROUNDS = {"๋ชจ๋˜ ๋ฆฌ๋น™๋ฃธ": "modern living room", "์„ธ๋ จ๋œ ์‚ฌ๋ฌด์‹ค": "elegant office", "๋Ÿญ์…”๋ฆฌ ํ˜ธํ…”": "luxury hotel"}
ABSTRACT_BACKGROUNDS = {"๋„ค์˜จ ์กฐ๋ช…": "neon lights", "์ถ”์ƒ์  ๊ทธ๋ผ๋ฐ์ด์…˜": "abstract gradient", "์šฐ์ฃผ ๋ฐฐ๊ฒฝ": "space background"}
# JSON ํŒŒ์ผ ๋กœ๋“œ ํ•จ์ˆ˜
def load_background_json(filename):
file_path = os.path.join(BACKGROUNDS_DIR, filename)
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
logger.info(f"{filename} ํŒŒ์ผ์„ ์„ฑ๊ณต์ ์œผ๋กœ ๋กœ๋“œํ–ˆ์Šต๋‹ˆ๋‹ค. {len(data)} ํ•ญ๋ชฉ ํฌํ•จ.")
return data
except FileNotFoundError:
logger.warning(f"๊ฒฝ๊ณ : {filename} ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
# ๊ธฐ๋ณธ๊ฐ’ ์ƒ์„ฑ - ํŒŒ์ผ์ด ์—†๋Š” ๊ฒฝ์šฐ ์ƒ์„ฑ
try:
with open(file_path, 'w', encoding='utf-8') as f:
if "simple" in filename:
json.dump(SIMPLE_BACKGROUNDS, f, ensure_ascii=False, indent=2)
elif "studio" in filename:
json.dump(STUDIO_BACKGROUNDS, f, ensure_ascii=False, indent=2)
elif "nature" in filename:
json.dump(NATURE_BACKGROUNDS, f, ensure_ascii=False, indent=2)
elif "indoor" in filename:
json.dump(INDOOR_BACKGROUNDS, f, ensure_ascii=False, indent=2)
elif "abstract" in filename:
json.dump(ABSTRACT_BACKGROUNDS, f, ensure_ascii=False, indent=2)
logger.info(f"{filename} ํŒŒ์ผ์„ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.")
except Exception as e:
logger.error(f"ํŒŒ์ผ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜: {str(e)}")
# ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜
if "simple" in filename:
return SIMPLE_BACKGROUNDS
elif "studio" in filename:
return STUDIO_BACKGROUNDS
elif "nature" in filename:
return NATURE_BACKGROUNDS
elif "indoor" in filename:
return INDOOR_BACKGROUNDS
elif "abstract" in filename:
return ABSTRACT_BACKGROUNDS
return {}
except json.JSONDecodeError:
logger.warning(f"๊ฒฝ๊ณ : {filename} ํŒŒ์ผ์˜ JSON ํ˜•์‹์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
return {}
except Exception as e:
logger.warning(f"๊ฒฝ๊ณ : {filename} ํŒŒ์ผ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}. ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
return {}
# ๋ฐฐ๊ฒฝ ๋ฐ์ดํ„ฐ ๋กœ๋“œ
SIMPLE_BACKGROUNDS = load_background_json("simple_backgrounds.json")
STUDIO_BACKGROUNDS = load_background_json("studio_backgrounds.json")
NATURE_BACKGROUNDS = load_background_json("nature_backgrounds.json")
INDOOR_BACKGROUNDS = load_background_json("indoor_backgrounds.json")
ABSTRACT_BACKGROUNDS = load_background_json("abstract_backgrounds.json")
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")
try:
if not GEMINI_API_KEY:
logger.error("Gemini API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
prompt = prompt.replace("IMAGE_TAG_ONE", "#1")
return prompt
client = genai.Client(api_key=GEMINI_API_KEY)
translation_prompt = f"""
Translate the following Korean text to English:
{prompt}
IMPORTANT: The token IMAGE_TAG_ONE is a special tag
and must be preserved exactly as is in your translation. Do not translate this token.
"""
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")
logger.info(f"Translated text: {translated_text.strip()}")
return translated_text.strip()
else:
logger.warning("๋ฒˆ์—ญ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์›๋ณธ ํ”„๋กฌํ”„ํŠธ ์‚ฌ์šฉ")
prompt = prompt.replace("IMAGE_TAG_ONE", "#1")
return prompt
except Exception as e:
logger.exception("๋ฒˆ์—ญ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
prompt = prompt.replace("IMAGE_TAG_ONE", "#1")
return prompt
def preprocess_prompt(prompt, image1):
has_img1 = image1 is not None
if "#1" in prompt and not has_img1:
prompt = prompt.replace("#1", "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€(์—†์Œ)")
else:
prompt = prompt.replace("#1", "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€")
prompt += " ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”. ์ด๋ฏธ์ง€์— ํ…์ŠคํŠธ๋‚˜ ๊ธ€์ž๋ฅผ ํฌํ•จํ•˜์ง€ ๋งˆ์„ธ์š”."
return prompt
def generate_with_images(prompt, images, variation_index=0):
try:
if not GEMINI_API_KEY:
return None, "API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”."
client = genai.Client(api_key=GEMINI_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
)
)
# ์ž„์‹œ ํŒŒ์ผ์€ ํ•ญ์ƒ JPG ํ™•์žฅ์ž๋กœ ์ƒ์„ฑ
with tempfile.NamedTemporaryFile(suffix=".jpg", 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") # JPG๋Š” ํˆฌ๋ช…๋„๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ RGB๋กœ ๋ณ€ํ™˜
# ๋ณ€ํ™˜๋œ ์ด๋ฏธ์ง€๋ฅผ JPG๋กœ ์ €์žฅ
result_img.save(temp_path, format="JPEG", quality=95)
# ํŒŒ์ผ ๊ฒฝ๋กœ ๋ฐ˜ํ™˜
return temp_path, f"์ด๋ฏธ์ง€๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. {result_text}"
except Exception as e:
logger.exception("์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
return None, f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
def process_images_with_prompt(image1, prompt, variation_index=0, max_retries=3):
retry_count = 0
last_error = None
while retry_count < max_retries:
try:
images = [image1]
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)
if re.search("[๊ฐ€-ํžฃ]", processed_prompt):
final_prompt = translate_prompt_to_english(processed_prompt)
else:
final_prompt = processed_prompt
else:
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")
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, 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, 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 generate_system_instruction():
return """๋‹น์‹ ์€ ์ƒํ’ˆ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ์„ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•œ ๊ณ ํ’ˆ์งˆ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.
์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ƒํ’ˆ๋ช…, ๋ฐฐ๊ฒฝ ์œ ํ˜•, ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ์„ ๋ฐ”ํƒ•์œผ๋กœ ๋ฏธ๋“œ์ €๋‹ˆ(Midjourney)์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”
์ƒ์„ธํ•˜๊ณ  ์ „๋ฌธ์ ์ธ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์˜์–ด๋กœ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”.
๋‹ค์Œ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ๋ฐ˜๋“œ์‹œ ๋”ฐ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค:
1. ์ƒํ’ˆ์„ "#1"๋กœ ์ง€์ •ํ•˜์—ฌ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: "skincare tube (#1)")
2. *** ๋งค์šฐ ์ค‘์š”: ์ƒํ’ˆ์˜ ์›๋ž˜ ํŠน์„ฑ(๋””์ž์ธ, ์ƒ‰์ƒ, ํ˜•ํƒœ, ๋กœ๊ณ , ํŒจํ‚ค์ง€ ๋“ฑ)์€ ์–ด๋–ค ์ƒํ™ฉ์—์„œ๋„ ์ ˆ๋Œ€ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ***
3. *** ์ƒํ’ˆ์˜ ๋ณธ์งˆ์  ํŠน์„ฑ์€ ์œ ์ง€ํ•˜๋˜, ์ž์—ฐ์Šค๋Ÿฌ์šด ํ™˜๊ฒฝ ํ†ตํ•ฉ์„ ์œ„ํ•œ ์กฐ๋ช…๊ณผ ๊ทธ๋ฆผ์ž๋Š” ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค: ***
- ์ƒํ’ˆ ์ž์ฒด์˜ ์ƒ‰์ƒ, ๋””์ž์ธ, ํ˜•ํƒœ, ํ…์Šค์ฒ˜๋Š” ์ ˆ๋Œ€ ์ˆ˜์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
- ํ™˜๊ฒฝ๊ณผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์–ด์šธ๋ฆฌ๋Š” ๊ทธ๋ฆผ์ž, ์ฃผ๋ณ€ ์กฐ๋ช… ํšจ๊ณผ๋Š” ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค.
- ์ƒํ’ˆ์— ๋ฌผ๋ฐฉ์šธ, ์‘์ถ•, ๊ธˆ, ์€๊ณผ ๊ฐ™์€ ์ถ”๊ฐ€ ์š”์†Œ๋‚˜ ๋ฌผ๋ฆฌ์  ํšจ๊ณผ๋Š” ์ ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
- ํ™˜๊ฒฝ์— ์–ด์šธ๋ฆฌ๋Š” ์ž์—ฐ์Šค๋Ÿฌ์šด ๋น› ๋ฐ˜์‚ฌ, ์ฃผ๋ณ€ ์กฐ๋ช…, ๊ทธ๋ฆผ์ž๋Š” ์‚ฌ์‹ค์  ํ†ตํ•ฉ๊ฐ์„ ์œ„ํ•ด ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
4. ์ด๋ฏธ์ง€ ๋น„์œจ์€ ์ •ํ™•ํžˆ 1:1(์ •์‚ฌ๊ฐํ˜•) ํ˜•์‹์œผ๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กฌํ”„ํŠธ์— "square format", "1:1 ratio" ๋˜๋Š” "aspect ratio 1:1"์„ ๋ช…์‹œ์ ์œผ๋กœ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
5. ์ƒํ’ˆ์€ ๋ฐ˜๋“œ์‹œ ์ •์‚ฌ๊ฐํ˜• ๊ตฌ๋„์˜ ์ •์ค‘์•™์— ๋ฐฐ์น˜๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
6. ์ƒํ’ˆ์„ ์ด๋ฏธ์ง€์˜ ์ฃผ์š” ์ดˆ์ ์œผ๋กœ ๋ถ€๊ฐ์‹œํ‚ค๊ณ , ์ƒํ’ˆ์˜ ๋น„์œจ์ด ์ „์ฒด ์ด๋ฏธ์ง€์—์„œ ํฌ๊ฒŒ ์ฐจ์ง€ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
7. ์ƒํ’ˆ ์ด๋ฏธ์ง€ ์ปท์•„์›ƒ(#1)์˜ ๊ธฐ๋ณธ ํ˜•ํƒœ์™€ ์ƒ‰์ƒ์€ ์œ ์ง€ํ•˜๋ฉด์„œ, ์„ ํƒํ•œ ํ™˜๊ฒฝ์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ†ตํ•ฉ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
8. ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ์ƒ์—…์  ์ด๋ฏธ์ง€๋ฅผ ์œ„ํ•œ ๋‹ค์Œ ํ™˜๊ฒฝ ์š”์†Œ๋“ค์„ ํฌํ•จํ•˜์„ธ์š”:
- ์ƒํ’ˆ๊ณผ ์–ด์šธ๋ฆฌ๋Š” ์ฃผ๋ณ€ ํ™˜๊ฒฝ/๋ฐฐ๊ฒฝ ์š”์†Œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ™”์žฅํ’ˆ ์ฃผ๋ณ€์— ๊ฝƒ์ด๋‚˜ ํ—ˆ๋ธŒ, ์Œ๋ฃŒ ์ œํ’ˆ ์˜†์— ๊ณผ์ผ, ์ „์ž์ œํ’ˆ ๊ทผ์ฒ˜์— ํ˜„๋Œ€์  ์†Œํ’ˆ ๋“ฑ.
- ํ™˜๊ฒฝ์˜ ์กฐ๋ช… ํšจ๊ณผ(๋ฆผ ๋ผ์ดํŠธ, ๋ฐฑ๋ผ์ดํŠธ, ์†Œํ”„ํŠธ๋ฐ•์Šค ๋“ฑ)๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.
- ์ƒํ’ˆ์ด ํ™˜๊ฒฝ์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์กด์žฌํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๋„๋ก ์ ์ ˆํ•œ ๊ทธ๋ฆผ์ž์™€ ๋น› ํ‘œํ˜„์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
- ์ƒํ’ˆ์˜ ์šฉ๋„๋‚˜ ์žฅ์ ์„ ๊ฐ„์ ‘์ ์œผ๋กœ ์•”์‹œํ•˜๋Š” ๋ฐฐ๊ฒฝ ์š”์†Œ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
- ํ”„๋กœํŽ˜์…”๋„ํ•œ ์ƒ์—… ์‚ฌ์ง„ ํšจ๊ณผ(์„ ํƒ์  ํ”ผ์‚ฌ๊ณ„ ์‹ฌ๋„, ์†Œํ”„ํŠธ ํฌ์ปค์Šค, ์ŠคํŠœ๋””์˜ค ์กฐ๋ช… ๋“ฑ)๋ฅผ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค.
9. ํ”„๋กฌํ”„ํŠธ์— ๋‹ค์Œ ์š”์†Œ๋“ค์„ ๋ช…์‹œ์ ์œผ๋กœ ํฌํ•จํ•˜์„ธ์š”:
- "highly detailed commercial photography"
- "award-winning product photography"
- "professional advertising imagery"
- "studio quality"
- "magazine advertisement quality"
10. ๋ฐฐ๊ฒฝ ํ™˜๊ฒฝ ์š”์†Œ๋ฅผ ์ƒํ’ˆ ์นดํ…Œ๊ณ ๋ฆฌ์— ๋งž๊ฒŒ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค:
- ์Šคํ‚จ์ผ€์–ด ์ œํ’ˆ: ๊นจ๋—ํ•œ ์š•์‹ค ์„ ๋ฐ˜, ์šฐ์•„ํ•œ ํ™”์žฅ๋Œ€, ์ŠคํŒŒ ๊ฐ™์€ ํ™˜๊ฒฝ ๋“ฑ
- ์Œ๋ฃŒ ์ œํ’ˆ: ์„ธ๋ จ๋œ ํ…Œ์ด๋ธ”, ํŒŒํ‹ฐ ํ™˜๊ฒฝ, ์•ผ์™ธ ํ”ผํฌ๋‹‰ ์žฅ๋ฉด ๋“ฑ
- ์ „์ž ์ œํ’ˆ: ์„ธ๋ จ๋œ ์ž‘์—… ๊ณต๊ฐ„, ํ˜„๋Œ€์ ์ธ ๊ฑฐ์‹ค, ๋ฏธ๋‹ˆ๋ฉ€ํ•œ ์ฑ…์ƒ ๋“ฑ
- ํŒจ์…˜/์˜๋ฅ˜: ์„ธ๋ จ๋œ ์‡ผ๋ฃธ, ๋„์‹œ ๊ฑฐ๋ฆฌ, ์—˜๋ ˆ๊ฐ•์Šคํ•œ ๋ผ์ดํ”„์Šคํƒ€์ผ ํ™˜๊ฒฝ ๋“ฑ
- ์‹ํ’ˆ ์ œํ’ˆ: ๊น”๋”ํ•œ ์ฃผ๋ฐฉ, ์‹ํƒ, ์š”๋ฆฌ ํ™˜๊ฒฝ ๋“ฑ
11. ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ ๊ตฌ์ฒด์ ์ธ ๋ฐฐ๊ฒฝ๊ณผ ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ์„ ์ •ํ™•ํžˆ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.
12. ํ”„๋กฌํ”„ํŠธ๋Š” ๋ฏธ๋“œ์ €๋‹ˆ AI์— ์ตœ์ ํ™”๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
13. ํ”„๋กฌํ”„ํŠธ ๋์— "--ar 1:1 --s 750 --q 2" ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฏธ๋“œ์ €๋‹ˆ์—์„œ ๊ณ ํ’ˆ์งˆ ์ •์‚ฌ๊ฐํ˜• ๋น„์œจ์„ ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค.
์ถœ๋ ฅ ํ˜•์‹์€ ์˜์–ด๋กœ ๋œ ๋‹จ์ผ ๋‹จ๋ฝ์˜ ์ƒ์„ธํ•œ ํ”„๋กฌํ”„ํŠธ์—ฌ์•ผ ํ•˜๋ฉฐ, ๋์— ๋ฏธ๋“œ์ €๋‹ˆ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
"""
def generate_prompt_with_gemini(product_name, background_info, additional_info=""):
if not GEMINI_API_KEY:
return "Gemini API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ GEMINI_API_KEY๋ฅผ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ ์ฝ”๋“œ์— ์ง์ ‘ ์ž…๋ ฅํ•˜์„ธ์š”."
try:
prompt_request = f"""
์ƒํ’ˆ๋ช…: {product_name}
๋ฐฐ๊ฒฝ ์œ ํ˜•: {background_info.get('english', 'studio')}
๋ฐฐ๊ฒฝ ์นดํ…Œ๊ณ ๋ฆฌ: {background_info.get('category', '')}
๋ฐฐ๊ฒฝ ์ด๋ฆ„: {background_info.get('name', '')}
์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ: {additional_info}
์ค‘์š” ์š”๊ตฌ์‚ฌํ•ญ:
1. ์ƒํ’ˆ์ด ํฌ๊ฒŒ ๋ถ€๊ฐ๋˜๊ณ  ์ด๋ฏธ์ง€์—์„œ ์ค‘์‹ฌ์ ์ธ ์œ„์น˜๋ฅผ ์ฐจ์ง€ํ•˜๋„๋ก ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”.
2. ์ด๋ฏธ์ง€๋Š” ์ •ํ™•ํžˆ 1:1 ๋น„์œจ(์ •์‚ฌ๊ฐํ˜•)์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
3. ์ƒํ’ˆ์€ ์ •์‚ฌ๊ฐํ˜• ํ”„๋ ˆ์ž„์˜ ์ •์ค‘์•™์— ์œ„์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
4. ์ƒํ’ˆ์˜ ๋””์ž์ธ, ์ƒ‰์ƒ, ํ˜•ํƒœ, ๋กœ๊ณ  ๋“ฑ ๋ณธ์งˆ์  ํŠน์„ฑ์€ ์ ˆ๋Œ€ ์ˆ˜์ •ํ•˜์ง€ ๋งˆ์„ธ์š”.
5. ํ™˜๊ฒฝ๊ณผ์˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ํ†ตํ•ฉ์„ ์œ„ํ•œ ์กฐ๋ช… ํšจ๊ณผ์™€ ๊ทธ๋ฆผ์ž๋Š” ํฌํ•จํ•ด์ฃผ์„ธ์š”.
6. ์ƒํ’ˆ์„ ๋” ๋‹๋ณด์ด๊ฒŒ ํ•˜๋Š” ๋ฐฐ๊ฒฝ ํ™˜๊ฒฝ์„ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.
7. ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ์ƒ์—… ๊ด‘๊ณ  ํ’ˆ์งˆ์˜ ์ด๋ฏธ์ง€๊ฐ€ ๋˜๋„๋ก ํ™˜๊ฒฝ ์„ค๋ช…์„ ํ•ด์ฃผ์„ธ์š”.
8. ํ”„๋กฌํ”„ํŠธ ๋์— ๋ฏธ๋“œ์ €๋‹ˆ ํŒŒ๋ผ๋ฏธํ„ฐ "--ar 1:1 --s 750 --q 2"๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”.
ํ•œ๊ตญ์–ด ์ž…๋ ฅ ๋‚ด์šฉ์„ ์˜์–ด๋กœ ์ ์ ˆํžˆ ๋ฒˆ์—ญํ•˜์—ฌ ๋ฐ˜์˜ํ•ด์ฃผ์„ธ์š”.
"""
model = genai.GenerativeModel(
'gemini-2.0-flash',
system_instruction=generate_system_instruction()
)
response = model.generate_content(
prompt_request,
generation_config=genai.types.GenerationConfig(
temperature=0.7,
top_p=0.95,
top_k=64,
max_output_tokens=1024,
)
)
response_text = response.text.strip()
if "--ar 1:1" not in response_text:
response_text = response_text.rstrip(".") + ". --ar 1:1 --s 750 --q 2"
# ๋ฏธ๋“œ์ €๋‹ˆ ํŒŒ๋ผ๋ฏธํ„ฐ ์ œ๊ฑฐ (Gemini API์™€๋Š” ๋‹ฌ๋ฆฌ ๋ถˆํ•„์š”ํ•˜๋ฏ€๋กœ)
response_text = response_text.replace(" --ar 1:1 --s 750 --q 2", "")
return response_text
except Exception as e:
return f"ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
# ์„ ํƒ๋œ ๋ฐฐ๊ฒฝ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
def get_selected_background_info(bg_type, simple, studio, nature, indoor, abstract):
if bg_type == "์‹ฌํ”Œ ๋ฐฐ๊ฒฝ":
return {
"category": "์‹ฌํ”Œ ๋ฐฐ๊ฒฝ",
"name": simple,
"english": SIMPLE_BACKGROUNDS.get(simple, "white background")
}
elif bg_type == "์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ":
return {
"category": "์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ",
"name": studio,
"english": STUDIO_BACKGROUNDS.get(studio, "product photography studio")
}
elif bg_type == "์ž์—ฐ ํ™˜๊ฒฝ":
return {
"category": "์ž์—ฐ ํ™˜๊ฒฝ",
"name": nature,
"english": NATURE_BACKGROUNDS.get(nature, "natural environment")
}
elif bg_type == "์‹ค๋‚ด ํ™˜๊ฒฝ":
return {
"category": "์‹ค๋‚ด ํ™˜๊ฒฝ",
"name": indoor,
"english": INDOOR_BACKGROUNDS.get(indoor, "indoor environment")
}
elif bg_type == "์ถ”์ƒ/ํŠน์ˆ˜ ๋ฐฐ๊ฒฝ":
return {
"category": "์ถ”์ƒ/ํŠน์ˆ˜ ๋ฐฐ๊ฒฝ",
"name": abstract,
"english": ABSTRACT_BACKGROUNDS.get(abstract, "abstract background")
}
else:
return {
"category": "๊ธฐ๋ณธ ๋ฐฐ๊ฒฝ",
"name": "ํ™”์ดํŠธ ๋ฐฐ๊ฒฝ",
"english": "white background"
}
# ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ํ•จ์ˆ˜
def create_prompt(image, bg_type, simple, studio, nature, indoor, abstract, product_text, additional_text):
if image is None:
return "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”."
product_text = product_text.strip() or "์ œํ’ˆ"
# ๋ฐฐ๊ฒฝ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
background_info = get_selected_background_info(bg_type, simple, studio, nature, indoor, abstract)
try:
prompt = generate_prompt_with_gemini(product_text, background_info, additional_text)
return prompt
except Exception as e:
error_msg = f"ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
return error_msg
with gr.Blocks() as demo: ๋ถ€๋ถ„๋ถ€ํ„ฐ ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ์ž‘์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค:
python๋ณต์‚ฌ# UI ๊ตฌ์„ฑ
with gr.Blocks() as demo:
with gr.Row():
with gr.Column():
# ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์„น์…˜
gr.Markdown("## ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ")
with gr.Row():
image1_input = gr.Image(type="pil", label="์ด๋ฏธ์ง€ ์—…๋กœ๋“œ", image_mode="RGB")
# ์ œํ’ˆ๋ช… ์ž…๋ ฅ ์„น์…˜
gr.Markdown("## ์ œํ’ˆ ์ •๋ณด")
product_name = gr.Textbox(label="์ œํ’ˆ๋ช… (ํ•œ๊ตญ์–ด ์ž…๋ ฅ)", placeholder="์˜ˆ: ์Šคํ‚จ์ผ€์–ด ํŠœ๋ธŒ, ํ…€๋ธ”๋Ÿฌ ๋“ฑ")
# ๋ฐฐ๊ฒฝ ์œ ํ˜• ์„น์…˜
gr.Markdown("## ๋ฐฐ๊ฒฝ ์„ค์ •")
background_type = gr.Radio(
choices=["์‹ฌํ”Œ ๋ฐฐ๊ฒฝ", "์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ", "์ž์—ฐ ํ™˜๊ฒฝ", "์‹ค๋‚ด ํ™˜๊ฒฝ", "์ถ”์ƒ/ํŠน์ˆ˜ ๋ฐฐ๊ฒฝ"],
label="๋ฐฐ๊ฒฝ ์œ ํ˜•",
value="์‹ฌํ”Œ ๋ฐฐ๊ฒฝ"
)
# ๊ฐ ๋ฐฐ๊ฒฝ ์œ ํ˜•์— ๋งž๋Š” ๋“œ๋กญ๋‹ค์šด ์ปดํฌ๋„ŒํŠธ๋“ค
simple_dropdown = gr.Dropdown(
choices=list(SIMPLE_BACKGROUNDS.keys()),
value=list(SIMPLE_BACKGROUNDS.keys())[0] if SIMPLE_BACKGROUNDS else None,
label="์‹ฌํ”Œ ๋ฐฐ๊ฒฝ ์„ ํƒ",
visible=True,
interactive=True
)
studio_dropdown = gr.Dropdown(
choices=list(STUDIO_BACKGROUNDS.keys()),
value=list(STUDIO_BACKGROUNDS.keys())[0] if STUDIO_BACKGROUNDS else None,
label="์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ ์„ ํƒ",
visible=False,
interactive=True
)
nature_dropdown = gr.Dropdown(
choices=list(NATURE_BACKGROUNDS.keys()),
value=list(NATURE_BACKGROUNDS.keys())[0] if NATURE_BACKGROUNDS else None,
label="์ž์—ฐ ํ™˜๊ฒฝ ์„ ํƒ",
visible=False,
interactive=True
)
indoor_dropdown = gr.Dropdown(
choices=list(INDOOR_BACKGROUNDS.keys()),
value=list(INDOOR_BACKGROUNDS.keys())[0] if INDOOR_BACKGROUNDS else None,
label="์‹ค๋‚ด ํ™˜๊ฒฝ ์„ ํƒ",
visible=False,
interactive=True
)
abstract_dropdown = gr.Dropdown(
choices=list(ABSTRACT_BACKGROUNDS.keys()),
value=list(ABSTRACT_BACKGROUNDS.keys())[0] if ABSTRACT_BACKGROUNDS else None,
label="์ถ”์ƒ/ํŠน์ˆ˜ ๋ฐฐ๊ฒฝ ์„ ํƒ",
visible=False,
interactive=True
)
# ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ
additional_info = gr.Textbox(
label="์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ (์„ ํƒ์‚ฌํ•ญ)",
placeholder="์˜ˆ: ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ๋А๋‚Œ, ๋ฐ์€ ์กฐ๋ช…, ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ณด์กฐ์ ์ธ ๊ฐ์ฒด๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š” ๋“ฑ",
lines=2
)
# ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ & ์ƒ์„ฑ ์„น์…˜
gr.Markdown("## ํ”„๋กฌํ”„ํŠธ ์„ค์ •")
generate_prompt_btn = gr.Button("ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ")
prompt_input = gr.Textbox(
lines=3,
placeholder="์—ฌ๊ธฐ์— ํ”„๋กฌํ”„ํŠธ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ˆ˜๋™์œผ๋กœ ์ˆ˜์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. '#1'์œผ๋กœ ์—…๋กœ๋“œํ•œ ์ด๋ฏธ์ง€๋ฅผ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค.",
label="์ƒ์„ฑ๋œ ํ”„๋กฌํ”„ํŠธ"
)
with gr.Row():
submit_single_btn = gr.Button('์ด๋ฏธ์ง€ ์ƒ์„ฑ (1์žฅ)')
submit_btn = gr.Button('์ด๋ฏธ์ง€ ์ƒ์„ฑ (4์žฅ)')
with gr.Column():
gr.Markdown("## ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€")
with gr.Row():
with gr.Column():
output_image1 = gr.Image(label="์ด๋ฏธ์ง€ #1", type="filepath")
output_image3 = gr.Image(label="์ด๋ฏธ์ง€ #3", type="filepath")
with gr.Column():
output_image2 = gr.Image(label="์ด๋ฏธ์ง€ #2", type="filepath")
output_image4 = gr.Image(label="์ด๋ฏธ์ง€ #4", type="filepath")
output_text = gr.Textbox(label="๊ฒฐ๊ณผ ์ •๋ณด", lines=2)
prompt_display = gr.Textbox(label="์‚ฌ์šฉ๋œ ํ”„๋กฌํ”„ํŠธ (์˜์–ด)", lines=2)
# ๋ฐฐ๊ฒฝ ์œ ํ˜•์— ๋”ฐ๋ผ ๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ ์—…๋ฐ์ดํŠธ
def update_dropdowns(bg_type):
return {
simple_dropdown: gr.update(visible=(bg_type == "์‹ฌํ”Œ ๋ฐฐ๊ฒฝ")),
studio_dropdown: gr.update(visible=(bg_type == "์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ")),
nature_dropdown: gr.update(visible=(bg_type == "์ž์—ฐ ํ™˜๊ฒฝ")),
indoor_dropdown: gr.update(visible=(bg_type == "์‹ค๋‚ด ํ™˜๊ฒฝ")),
abstract_dropdown: gr.update(visible=(bg_type == "์ถ”์ƒ/ํŠน์ˆ˜ ๋ฐฐ๊ฒฝ"))
}
# ๋ฐฐ๊ฒฝ ์œ ํ˜• ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
background_type.change(
fn=update_dropdowns,
inputs=[background_type],
outputs=[simple_dropdown, studio_dropdown, nature_dropdown, indoor_dropdown, abstract_dropdown]
)
# ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ๋ฒ„ํŠผ ํด๋ฆญ ์ด๋ฒคํŠธ
generate_prompt_btn.click(
fn=create_prompt,
inputs=[
image1_input,
background_type,
simple_dropdown,
studio_dropdown,
nature_dropdown,
indoor_dropdown,
abstract_dropdown,
product_name,
additional_info
],
outputs=[prompt_input]
)
# ๋‹จ์ผ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
submit_single_btn.click(
fn=lambda image1, prompt: process_images_with_prompt(image1, prompt, 0),
inputs=[image1_input, prompt_input],
outputs=[output_image1, output_text, prompt_display],
)
# 4์žฅ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
submit_btn.click(
fn=generate_multiple_images,
inputs=[image1_input, prompt_input],
outputs=[output_image1, output_image2, output_image3, output_image4, output_text, prompt_display],
)
demo.queue()
demo.launch()