img_3 / app.py
Kims12's picture
Update app.py
c69cb94 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
# ํŒจํ‚ค์ง€ ์ž„ํฌํŠธ ๋ฐฉ์‹์„ ๋ณ€๊ฒฝ
import google.generativeai as genai
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", "")
if GEMINI_API_KEY:
genai.configure(api_key=GEMINI_API_KEY)
else:
logger.warning("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
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}")
# ์ƒˆ๋กœ์šด API ์‚ฌ์šฉ ๋ฐฉ์‹
response = genai.generate_text(
model="gemini-2.0-flash",
prompt=translation_prompt,
temperature=0.2,
top_p=0.95,
top_k=40,
max_output_tokens=512
)
translated_text = response.result
if translated_text and 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"๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”.
ํ•œ๊ตญ์–ด ์ž…๋ ฅ ๋‚ด์šฉ์„ ์˜์–ด๋กœ ์ ์ ˆํžˆ ๋ฒˆ์—ญํ•˜์—ฌ ๋ฐ˜์˜ํ•ด์ฃผ์„ธ์š”.
"""
# google-generativeai ํŒจํ‚ค์ง€ ์‚ฌ์šฉ ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝ
system_instruction = generate_system_instruction()
# GenerativeModel ๋Œ€์‹  genai.chat ์‚ฌ์šฉ
chat = genai.chat(
model="gemini-2.0-flash",
messages=[
{"role": "system", "content": system_instruction},
{"role": "user", "content": prompt_request}
],
generation_config={
"temperature": 0.7,
"top_p": 0.95,
"top_k": 64,
"max_output_tokens": 1024,
}
)
response_text = chat.last.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:
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()