test-100 / app.py
Kims12's picture
Update app.py
654ca6d verified
raw
history blame
10.7 kB
import json
import os
import time
import tempfile
from PIL import Image
import gradio as gr
import logging
from io import BytesIO
from google import genai
from google.genai import types
# .env νŒŒμΌμ— μ €μž₯된 ν™˜κ²½λ³€μˆ˜ λ‘œλ“œ (python-dotenv μ„€μΉ˜ ν•„μš”: pip install python-dotenv)
from dotenv import load_dotenv
load_dotenv()
# λ‘œκΉ… μ„€μ • (둜그 레벨: DEBUG)
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def save_binary_file(file_name, data):
logger.debug(f"νŒŒμΌμ— 이진 데이터 μ €μž₯ 쀑: {file_name}")
with open(file_name, "wb") as f:
f.write(data)
logger.debug(f"파일 μ €μž₯ μ™„λ£Œ: {file_name}")
def merge_images(person_img_path, product_img_path, background_img_path, prompt, model="gemini-2.0-flash-exp-image-generation"):
logger.debug(f"merge_images ν•¨μˆ˜ μ‹œμž‘ - ν”„λ‘¬ν”„νŠΈ: '{prompt}'")
try:
# API ν‚€λŠ” ν™˜κ²½λ³€μˆ˜μ—μ„œ 뢈러옴
effective_api_key = os.environ.get("GEMINI_API_KEY")
if effective_api_key:
logger.debug("ν™˜κ²½λ³€μˆ˜μ—μ„œ API ν‚€ 뢈러옴")
else:
logger.error("API ν‚€κ°€ ν™˜κ²½λ³€μˆ˜μ— μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
raise ValueError("API ν‚€κ°€ ν•„μš”ν•©λ‹ˆλ‹€.")
client = genai.Client(api_key=effective_api_key)
logger.debug("Gemini ν΄λΌμ΄μ–ΈνŠΈ μ΄ˆκΈ°ν™” μ™„λ£Œ.")
# PIL 이미지 객체둜 λ³€ν™˜
person_img = Image.open(person_img_path)
product_img = Image.open(product_img_path)
# 컨텐츠 리슀트 생성
contents = [person_img, product_img]
# λ°°κ²½ 이미지가 있으면 μΆ”κ°€
if background_img_path:
background_img = Image.open(background_img_path)
contents.append(background_img)
logger.debug("λ°°κ²½ 이미지 좔가됨")
# λ§ˆμ§€λ§‰μ— ν”„λ‘¬ν”„νŠΈ μΆ”κ°€
contents.append(prompt)
logger.debug(f"컨텐츠 객체 생성 μ™„λ£Œ: {len(contents)} μ•„μ΄ν…œ")
# 생성 μ„€μ •
generate_content_config = types.GenerateContentConfig(
temperature=1,
top_p=0.95,
top_k=40,
max_output_tokens=8192,
response_modalities=["text", "image"],
)
logger.debug(f"생성 μ„€μ •: {generate_content_config}")
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
temp_path = tmp.name
logger.debug(f"μž„μ‹œ 파일 생성됨: {temp_path}")
# 단일 μš”μ²­μœΌλ‘œ 이미지 생성
response = client.models.generate_content(
model=model,
contents=contents,
config=generate_content_config,
)
logger.debug("응닡 처리 μ‹œμž‘...")
# μ‘λ‹΅μ—μ„œ 이미지와 ν…μŠ€νŠΈ μΆ”μΆœ
image_saved = False
response_text = ""
for part in response.candidates[0].content.parts:
if hasattr(part, 'text') and part.text:
response_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)
logger.info(f"MIME νƒ€μž… {part.inline_data.mime_type}의 파일이 μ €μž₯됨: {temp_path}")
image_saved = True
if not image_saved:
logger.warning("이미지가 μƒμ„±λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
return None, response_text
logger.debug("이미지 생성 μ™„λ£Œ.")
return temp_path, response_text
except Exception as e:
logger.exception("이미지 생성 쀑 였λ₯˜ λ°œμƒ:")
return None, str(e) # 였λ₯˜ λ°œμƒ μ‹œ Noneκ³Ό 였λ₯˜ λ©”μ‹œμ§€ λ°˜ν™˜
def process_images_and_prompt(person_pil, product_pil, background_pil, prompt):
logger.debug(f"process_images_and_prompt ν•¨μˆ˜ μ‹œμž‘ - ν”„λ‘¬ν”„νŠΈ: '{prompt}'")
try:
# κΈ°λ³Έ ν”„λ‘¬ν”„νŠΈ μ„€μ • (λΉ„μ–΄μžˆλŠ” 경우)
if not prompt or not prompt.strip():
if background_pil:
prompt = "이 배경에 이 μ‚¬λžŒμ΄ 이 μƒν’ˆμ„ μ‚¬μš©ν•˜λŠ” λͺ¨μŠ΅μ„ μžμ—°μŠ€λŸ½κ²Œ λ³΄μ—¬μ£Όμ„Έμš”. μƒν’ˆμ„ 잘 보이게 ν•΄μ£Όμ„Έμš”. Create a natural composite image showing this person using this product in this background setting. Make sure the product is clearly visible."
else:
prompt = "이 μ‚¬λžŒμ΄ 이 μƒν’ˆμ„ μ‚¬μš©ν•˜λŠ” λͺ¨μŠ΅μ„ μžμ—°μŠ€λŸ½κ²Œ λ³΄μ—¬μ£Όμ„Έμš”. μƒν’ˆμ„ 잘 보이게 ν•΄μ£Όμ„Έμš”. Create a natural composite image showing this person using this product. Make sure the product is clearly visible."
# ν”„λ‘¬ν”„νŠΈμ— μ˜μ–΄κ°€ μ—†μœΌλ©΄ μ˜μ–΄ ν”„λ‘¬ν”„νŠΈ μΆ”κ°€ (더 λ‚˜μ€ κ²°κ³Όλ₯Ό μœ„ν•΄)
if not any(ord(c) < 128 for c in prompt):
if background_pil:
prompt += " Create a realistic composite image of this person with this product in this background."
else:
prompt += " Create a realistic composite image of this person with this product."
# 이미지 μ €μž₯
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_person:
person_path = tmp_person.name
person_pil.save(person_path)
logger.debug(f"μ‚¬λžŒ 이미지 μ €μž₯ μ™„λ£Œ: {person_path}")
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_product:
product_path = tmp_product.name
product_pil.save(product_path)
logger.debug(f"μƒν’ˆ 이미지 μ €μž₯ μ™„λ£Œ: {product_path}")
# λ°°κ²½ 이미지 μ €μž₯ (μžˆλŠ” 경우)
background_path = None
if background_pil is not None:
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_bg:
background_path = tmp_bg.name
background_pil.save(background_path)
logger.debug(f"λ°°κ²½ 이미지 μ €μž₯ μ™„λ£Œ: {background_path}")
# 이미지 ν•©μ„± μ‹€ν–‰
result_path, response_text = merge_images(
person_img_path=person_path,
product_img_path=product_path,
background_img_path=background_path,
prompt=prompt
)
# 이미지 λ°˜ν™˜ 및 μž„μ‹œ 파일 정리
if result_path:
logger.debug(f"이미지 생성 μ™„λ£Œ. 경둜: {result_path}")
result_img = Image.open(result_path)
if result_img.mode == "RGBA":
result_img = result_img.convert("RGB")
# μž„μ‹œ 파일 정리
try:
os.unlink(person_path)
os.unlink(product_path)
if background_path:
os.unlink(background_path)
except Exception as e:
logger.warning(f"μž„μ‹œ 파일 μ‚­μ œ 쀑 였λ₯˜: {str(e)}")
return [result_img], response_text
else:
logger.error("merge_images ν•¨μˆ˜μ—μ„œ None λ°˜ν™˜λ¨.")
return [], response_text # 였λ₯˜ μ‹œ 빈 리슀트 λ°˜ν™˜
except Exception as e:
logger.exception("process_images_and_prompt ν•¨μˆ˜μ—μ„œ 였λ₯˜ λ°œμƒ:")
return [], str(e) # 였λ₯˜ μ‹œ 빈 λ¦¬μŠ€νŠΈμ™€ 였λ₯˜ λ©”μ‹œμ§€ λ°˜ν™˜
# --- Gradio μΈν„°νŽ˜μ΄μŠ€ ꡬ성 ---
with gr.Blocks() as demo:
gr.HTML(
"""
<div style='display: flex; align-items: center; justify-content: center; gap: 20px'>
<div style="background-color: var(--block-background-fill); border-radius: 8px">
<img src="https://www.gstatic.com/lamda/images/gemini_favicon_f069958c85030456e93de685481c559f160ea06b.png" style="width: 100px; height: 100px;">
</div>
<div>
<h1>Geminiλ₯Ό μ΄μš©ν•œ 이미지 ν•©μ„±</h1>
<p>μ‚¬λžŒ, μƒν’ˆ, λ°°κ²½ 이미지λ₯Ό ν•©μ„±ν•˜λŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μž…λ‹ˆλ‹€.</p>
</div>
</div>
"""
)
gr.Markdown("μ‚¬λžŒ 이미지, μƒν’ˆ 이미지, λ°°κ²½ 이미지λ₯Ό μ—…λ‘œλ“œν•˜κ³ , μ–΄λ–»κ²Œ ν•©μ„±ν• μ§€ μ„€λͺ…ν•΄μ£Όμ„Έμš”.")
with gr.Row():
with gr.Column():
person_input = gr.Image(type="pil", label="μ‚¬λžŒ 이미지 (ν•„μˆ˜)", image_mode="RGB")
product_input = gr.Image(type="pil", label="μƒν’ˆ 이미지 (ν•„μˆ˜)", image_mode="RGB")
background_input = gr.Image(type="pil", label="λ°°κ²½ 이미지 (선택 사항)", image_mode="RGB")
prompt_input = gr.Textbox(
lines=2,
placeholder="ν•©μ„± 방법을 μ„€λͺ…ν•΄μ£Όμ„Έμš”. (예: '이 λ°°κ²½μ—μ„œ 이 μ‚¬λžŒμ΄ μƒν’ˆμ„ μ‚¬μš©ν•˜λŠ” λͺ¨μŠ΅' λ˜λŠ” '이 μ‚¬λžŒμ΄ 이 μƒν’ˆμ„ λ“€κ³  이 배경에 μ„œ μžˆλŠ” λͺ¨μŠ΅')",
label="ν•©μ„± 방법 μ„€λͺ…"
)
submit_btn = gr.Button("이미지 ν•©μ„± μ‹€ν–‰")
with gr.Column():
output_gallery = gr.Gallery(label="ν•©μ„± κ²°κ³Ό")
output_text = gr.Textbox(label="AI 응닡 ν…μŠ€νŠΈ", visible=True)
submit_btn.click(
fn=process_images_and_prompt,
inputs=[person_input, product_input, background_input, prompt_input],
outputs=[output_gallery, output_text],
)
gr.HTML("""
<div style="margin-top: 20px; padding: 10px; background-color: #f8f9fa; border-radius: 8px;">
<h3>μ‚¬μš© 팁:</h3>
<ul>
<li><strong>λ°°κ²½ ν™œμš©:</strong> "이 λ°°κ²½μ—μ„œ 이 μ‚¬λžŒμ΄ 이 μƒν’ˆμ„ μ‚¬μš©ν•˜λŠ” μžμ—°μŠ€λŸ¬μš΄ λͺ¨μŠ΅μ„ λ§Œλ“€μ–΄μ£Όμ„Έμš”."</li>
<li><strong>νŠΉμ • μœ„μΉ˜ μ§€μ •:</strong> "이 μ‚¬λžŒμ΄ 이 μƒν’ˆμ„ 손에 λ“€κ³  이 λ°°κ²½ μ•žμ— μ„œ μžˆλŠ” λͺ¨μŠ΅μ„ λ³΄μ—¬μ£Όμ„Έμš”."</li>
<li><strong>μƒν’ˆ κ°•μ‘°:</strong> "이 μ‚¬λžŒμ΄ 이 λ°°κ²½μ—μ„œ 이 μƒν’ˆμ„ μ‚¬μš©ν•˜λŠ” λͺ¨μŠ΅μ„ λ³΄μ—¬μ£Όλ˜, μƒν’ˆμ΄ 잘 보이도둝 ν•΄μ£Όμ„Έμš”."</li>
<li><strong>μž₯λ©΄ μ„€μ •:</strong> "이 μ‚¬λžŒμ΄ 이 μƒν’ˆμœΌλ‘œ μš”λ¦¬ν•˜λŠ” λͺ¨μŠ΅μ„ 이 μ£Όλ°© λ°°κ²½μ—μ„œ λ³΄μ—¬μ£Όμ„Έμš”."</li>
<li><strong>μ˜μ–΄ ν”„λ‘¬ν”„νŠΈ:</strong> 더 λ‚˜μ€ κ²°κ³Όλ₯Ό μœ„ν•΄ μ˜μ–΄μ™€ ν•œκ΅­μ–΄λ₯Ό ν•¨κ»˜ μ‚¬μš©ν•΄ λ³΄μ„Έμš”.</li>
<li><strong>λ°°κ²½ 선택사항:</strong> λ°°κ²½ μ΄λ―Έμ§€λŠ” μ„ νƒμ‚¬ν•­μž…λ‹ˆλ‹€. λ°°κ²½ 없이 μ‚¬λžŒκ³Ό μƒν’ˆλ§Œ ν•©μ„±ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.</li>
</ul>
</div>
""")
# --- μ‹€ν–‰ ---
if __name__ == "__main__":
demo.launch(share=True)