test-100 / app.py
Kims12's picture
Update app.py
993c58c verified
raw
history blame
13.1 kB
import json
import os
import time
import uuid
import tempfile
from PIL import Image
import gradio as gr
import base64
import mimetypes
import logging
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 generate(text, files_list, model="gemini-2.0-flash-exp-image-generation"):
"""
์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€์™€ ํ…์ŠคํŠธ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Gemini API๋กœ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜
Args:
text (str): ์ด๋ฏธ์ง€ ์ƒ์„ฑ์— ์‚ฌ์šฉํ•  ํ…์ŠคํŠธ ํ”„๋กฌํ”„ํŠธ
files_list (list): ์ด๋ฏธ์ง€ ํŒŒ์ผ ๊ฒฝ๋กœ ๋ฆฌ์ŠคํŠธ
model (str): ์‚ฌ์šฉํ•  Gemini ๋ชจ๋ธ๋ช…
Returns:
str: ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€์˜ ํŒŒ์ผ ๊ฒฝ๋กœ ๋˜๋Š” None (์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ)
"""
logger.debug(f"generate ํ•จ์ˆ˜ ์‹œ์ž‘ - ํ…์ŠคํŠธ: '{text}', ํŒŒ์ผ ์ˆ˜: {len(files_list)}, ๋ชจ๋ธ: '{model}'")
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 ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ.")
# ์—ฌ๋Ÿฌ ํŒŒ์ผ ์—…๋กœ๋“œ
uploaded_files = []
for file_path in files_list:
if file_path and os.path.exists(file_path):
uploaded_file = client.files.upload(file=file_path)
uploaded_files.append(uploaded_file)
logger.debug(f"ํŒŒ์ผ ์—…๋กœ๋“œ ์™„๋ฃŒ. URI: {uploaded_file.uri}, MIME ํƒ€์ž…: {uploaded_file.mime_type}")
else:
logger.warning(f"ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ None์ž…๋‹ˆ๋‹ค: {file_path}")
if not uploaded_files:
logger.error("์—…๋กœ๋“œํ•  ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค.")
return None
# ํŒŒํŠธ ๋ฆฌ์ŠคํŠธ ์ƒ์„ฑ (๋ชจ๋“  ์ด๋ฏธ์ง€ + ํ…์ŠคํŠธ ํ”„๋กฌํ”„ํŠธ)
parts = []
for uploaded_file in uploaded_files:
parts.append(
types.Part.from_uri(
file_uri=uploaded_file.uri,
mime_type=uploaded_file.mime_type,
)
)
# ํ…์ŠคํŠธ ํ”„๋กฌํ”„ํŠธ ์ถ”๊ฐ€
parts.append(types.Part.from_text(text=text))
# ์ปจํ…์ธ  ๊ฐ์ฒด ์ƒ์„ฑ
contents = [
types.Content(
role="user",
parts=parts,
),
]
logger.debug(f"์ปจํ…์ธ  ๊ฐ์ฒด ์ƒ์„ฑ ์™„๋ฃŒ: {contents}")
generate_content_config = types.GenerateContentConfig(
temperature=1,
top_p=0.95,
top_k=40,
max_output_tokens=8192,
response_modalities=[
"image",
"text",
],
response_mime_type="text/plain",
)
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_stream = client.models.generate_content_stream(
model=model,
contents=contents,
config=generate_content_config,
)
logger.debug("์‘๋‹ต ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ ์‹œ์ž‘...")
for chunk in response_stream:
if not chunk.candidates or not chunk.candidates[0].content or not chunk.candidates[0].content.parts:
logger.warning("chunk์— ํ›„๋ณด, ์ปจํ…์ธ , ๋˜๋Š” ํŒŒํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.")
continue
inline_data = chunk.candidates[0].content.parts[0].inline_data
if inline_data:
save_binary_file(temp_path, inline_data.data)
logger.info(f"MIME ํƒ€์ž… {inline_data.mime_type}์˜ ํŒŒ์ผ์ด ์ €์žฅ๋จ: {temp_path} (ํ”„๋กฌํ”„ํŠธ: {text})")
else:
logger.info(f"์ˆ˜์‹ ๋œ ํ…์ŠคํŠธ: {chunk.text}")
print(chunk.text)
logger.debug(f"Raw chunk: {chunk}")
# ์—…๋กœ๋“œ๋œ ํŒŒ์ผ ์ •๋ณด ์ •๋ฆฌ
del uploaded_files
logger.debug("์—…๋กœ๋“œ๋œ ํŒŒ์ผ ์ •๋ณด ์‚ญ์ œ ์™„๋ฃŒ.")
return temp_path
except Exception as e:
logger.exception("์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
return None # ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ None ๋ฐ˜ํ™˜
def save_image_temp(image):
"""์ด๋ฏธ์ง€๋ฅผ ์ž„์‹œ ํŒŒ์ผ๋กœ ์ €์žฅํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜"""
if image is None:
return None
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
temp_path = tmp.name
if isinstance(image, Image.Image):
if image.mode == "RGBA":
# ์•ŒํŒŒ ์ฑ„๋„์ด ์žˆ๋Š” ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ
image = image.convert("RGB")
image.save(temp_path)
else:
logger.warning(f"์ง€์›๋˜์ง€ ์•Š๋Š” ์ด๋ฏธ์ง€ ํƒ€์ž…: {type(image)}")
return None
logger.debug(f"์ด๋ฏธ์ง€ ์ €์žฅ ์™„๋ฃŒ: {temp_path}")
return temp_path
def process_with_background_and_style(main_image, background_image, style_image, prompt):
"""
์ฃผ ์ด๋ฏธ์ง€, ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€, ์Šคํƒ€์ผ ์ด๋ฏธ์ง€๋ฅผ ํ”„๋กฌํ”„ํŠธ์™€ ํ•จ๊ป˜ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•จ์ˆ˜
Args:
main_image (PIL.Image): ์ฃผ ๋Œ€์ƒ ์ด๋ฏธ์ง€
background_image (PIL.Image): ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€
style_image (PIL.Image): ์Šคํƒ€์ผ ์ฐธ์กฐ ์ด๋ฏธ์ง€
prompt (str): ์ด๋ฏธ์ง€ ํŽธ์ง‘ ํ”„๋กฌํ”„ํŠธ
Returns:
list: ์ฒ˜๋ฆฌ๋œ ์ด๋ฏธ์ง€ ๋ชฉ๋ก
"""
logger.debug(f"process_with_background_and_style ํ•จ์ˆ˜ ์‹œ์ž‘ - ํ”„๋กฌํ”„ํŠธ: '{prompt}'")
try:
# ๊ฐ ์ด๋ฏธ์ง€๋ฅผ ์ž„์‹œ ํŒŒ์ผ๋กœ ์ €์žฅ
files_list = []
# ์ฃผ ์ด๋ฏธ์ง€ ์ €์žฅ
main_path = save_image_temp(main_image)
if main_path:
files_list.append(main_path)
# ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์ €์žฅ (์žˆ๋Š” ๊ฒฝ์šฐ)
if background_image is not None:
bg_path = save_image_temp(background_image)
if bg_path:
files_list.append(bg_path)
# ์Šคํƒ€์ผ ์ด๋ฏธ์ง€ ์ €์žฅ (์žˆ๋Š” ๊ฒฝ์šฐ)
if style_image is not None:
style_path = save_image_temp(style_image)
if style_path:
files_list.append(style_path)
# ์ด๋ฏธ์ง€๊ฐ€ ํ•˜๋‚˜๋„ ์—†๋Š” ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ
if not files_list:
logger.error("์ฒ˜๋ฆฌํ•  ์ด๋ฏธ์ง€๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
return []
# ํ”„๋กฌํ”„ํŠธ ๋ณด๊ฐ• (๋ฐฐ๊ฒฝ ๋ฐ ์Šคํƒ€์ผ ๊ด€๋ จ ์ •๋ณด ์ถ”๊ฐ€)
enhanced_prompt = prompt
if background_image is not None and style_image is not None:
enhanced_prompt = f"{prompt}. ์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋Š” ์ฃผ ๋Œ€์ƒ ์ด๋ฏธ์ง€์ด๊ณ , ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋Š” ๋ฐฐ๊ฒฝ์œผ๋กœ ์‚ฌ์šฉํ•ด์ฃผ์„ธ์š”. ์„ธ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ์Šคํƒ€์ผ์„ ์ ์šฉํ•ด์ฃผ์„ธ์š”."
elif background_image is not None:
enhanced_prompt = f"{prompt}. ์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋Š” ์ฃผ ๋Œ€์ƒ ์ด๋ฏธ์ง€์ด๊ณ , ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋Š” ๋ฐฐ๊ฒฝ์œผ๋กœ ์‚ฌ์šฉํ•ด์ฃผ์„ธ์š”."
elif style_image is not None:
enhanced_prompt = f"{prompt}. ์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋Š” ์ฃผ ๋Œ€์ƒ ์ด๋ฏธ์ง€์ด๊ณ , ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ์Šคํƒ€์ผ์„ ์ ์šฉํ•ด์ฃผ์„ธ์š”."
logger.debug(f"๋ณด๊ฐ•๋œ ํ”„๋กฌํ”„ํŠธ: {enhanced_prompt}")
model = "gemini-2.0-flash-exp-image-generation"
# Gemini API ํ˜ธ์ถœ
result_path = generate(text=enhanced_prompt, files_list=files_list, model=model)
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")
return [result_img]
else:
logger.error("generate ํ•จ์ˆ˜์—์„œ None ๋ฐ˜ํ™˜๋จ.")
return [] # ์˜ค๋ฅ˜ ์‹œ ๋นˆ ๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜
except Exception as e:
logger.exception("process_with_background_and_style ํ•จ์ˆ˜์—์„œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
return [] # ์˜ค๋ฅ˜ ์‹œ ๋นˆ ๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜
# --- 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>
<p>Gemini API ํ‚ค๋Š” ํ™˜๊ฒฝ๋ณ€์ˆ˜(GEMINI_API_KEY)๋กœ ์„ค์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.</p>
</div>
</div>
"""
)
gr.Markdown("์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ณ , ํŽธ์ง‘ํ•  ๋‚ด์šฉ์„ ์ž…๋ ฅํ•˜์„ธ์š”. ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€์™€ ์Šคํƒ€์ผ ์ด๋ฏธ์ง€๋Š” ์„ ํƒ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค.")
with gr.Row():
with gr.Column():
# ์ž…๋ ฅ ์ปดํฌ๋„ŒํŠธ
main_image_input = gr.Image(type="pil", label="์ฃผ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ", image_mode="RGB")
background_image_input = gr.Image(type="pil", label="๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ (์„ ํƒ์‚ฌํ•ญ)", image_mode="RGB")
style_image_input = gr.Image(type="pil", label="์Šคํƒ€์ผ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ (์„ ํƒ์‚ฌํ•ญ)", image_mode="RGB")
prompt_input = gr.Textbox(
lines=3,
placeholder="ํŽธ์ง‘ํ•  ๋‚ด์šฉ์„ ์ž…๋ ฅํ•˜์„ธ์š”...",
label="ํŽธ์ง‘ ํ”„๋กฌํ”„ํŠธ"
)
# ํ”„๋กฌํ”„ํŠธ ์˜ˆ์‹œ
gr.Markdown("### ํ”„๋กฌํ”„ํŠธ ์˜ˆ์‹œ")
gr.Markdown("- '์ฃผ ์ด๋ฏธ์ง€๋ฅผ ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์œ„์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ฐฐ์น˜ํ•ด์ฃผ์„ธ์š”'")
gr.Markdown("- '์ฃผ ์ด๋ฏธ์ง€๋ฅผ ์Šคํƒ€์ผ ์ด๋ฏธ์ง€์˜ ํ™”ํ’์œผ๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ์„ธ์š”'")
gr.Markdown("- '์ฃผ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ์„ ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€๋กœ ๋Œ€์ฒดํ•˜๊ณ , ์„ธ ๋ฒˆ์งธ ์ด๋ฏธ์ง€์˜ ์ƒ‰๊ฐ์„ ์ ์šฉํ•ด์ฃผ์„ธ์š”'")
submit_btn = gr.Button("์ด๋ฏธ์ง€ ํŽธ์ง‘ ์‹คํ–‰")
with gr.Column():
# ์ถœ๋ ฅ ์ปดํฌ๋„ŒํŠธ
output_gallery = gr.Gallery(label="ํŽธ์ง‘ ๊ฒฐ๊ณผ")
output_info = gr.Textbox(label="์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ ์ •๋ณด", lines=2)
# ์ฒ˜๋ฆฌ ํ•จ์ˆ˜ ์—ฐ๊ฒฐ
submit_btn.click(
fn=process_with_background_and_style,
inputs=[main_image_input, background_image_input, style_image_input, prompt_input],
outputs=[output_gallery],
).then(
lambda: "ํŽธ์ง‘์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.",
None,
output_info
)
# ์˜ˆ์ œ ์ด๋ฏธ์ง€ ์ถ”๊ฐ€
gr.Examples(
examples=[
["example_main.jpg", "example_background.jpg", "example_style.jpg", "์ฃผ ์ด๋ฏธ์ง€์˜ ์ธ๋ฌผ์„ ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€์— ํ•ฉ์„ฑํ•˜๊ณ , ์Šคํƒ€์ผ ์ด๋ฏธ์ง€์˜ ํ™”ํ’์„ ์ ์šฉํ•ด์ฃผ์„ธ์š”"],
["example_main.jpg", None, "example_style.jpg", "์ด๋ฏธ์ง€๋ฅผ ์Šคํƒ€์ผ ์ด๋ฏธ์ง€์˜ ํ™”ํ’์œผ๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ์„ธ์š”"],
["example_main.jpg", "example_background.jpg", None, "์ฃผ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ์„ ์ œ๊ฑฐํ•˜๊ณ  ๋‘ ๋ฒˆ์งธ ์ด๋ฏธ์ง€ ์œ„์— ํ•ฉ์„ฑํ•ด์ฃผ์„ธ์š”"],
],
inputs=[main_image_input, background_image_input, style_image_input, prompt_input],
)
# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰
if __name__ == "__main__":
# ์‹ค์ œ ์šด์˜์—์„œ๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ œ๊ฑฐ
# ํ…Œ์ŠคํŠธ์šฉ ๋”๋ฏธ ์ด๋ฏธ์ง€ (์‹ค์ œ ์ด๋ฏธ์ง€๋กœ ๋Œ€์ฒด ๊ฐ€๋Šฅ)
# dummy_image = Image.new("RGB", (100, 100), color="red")
# dummy_bg = Image.new("RGB", (100, 100), color="blue")
# dummy_style = Image.new("RGB", (100, 100), color="green")
# dummy_prompt = "์ด๋ฏธ์ง€๋ฅผ ํ•ฉ์„ฑํ•ด์ค˜"
# logger.info("process_with_background_and_style ํ•จ์ˆ˜๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค...")
# result = process_with_background_and_style(dummy_image, dummy_bg, dummy_style, dummy_prompt)
# if result:
# logger.info(f"์ง์ ‘ ํ˜ธ์ถœ ์„ฑ๊ณต. ๊ฒฐ๊ณผ: {result}")
# else:
# logger.error("์ง์ ‘ ํ˜ธ์ถœ ์‹คํŒจ.")
# Gradio ์•ฑ ์‹คํ–‰
demo.launch(share=True)