test-100 / app.py
Kims12's picture
Update app.py
edf8d31 verified
raw
history blame
9.36 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 ํŒŒ์ผ์— ์ €์žฅ๋œ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋กœ๋“œ (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, composite_file, background_file=None, style_file=None, model="gemini-2.0-flash-exp-image-generation"):
logger.debug(f"generate ํ•จ์ˆ˜ ์‹œ์ž‘ - ํ…์ŠคํŠธ: '{text}', composite_file: '{composite_file}', "
f"background_file: '{background_file}', style_file: '{style_file}', ๋ชจ๋ธ: '{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 ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ.")
# ์—…๋กœ๋“œํ•  ํŒŒ์ผ๋“ค ๋ฆฌ์ŠคํŠธ ์ƒ์„ฑ
files = []
# ์›๋ณธ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ (ํ•„์ˆ˜)
composite_upload = client.files.upload(file=composite_file)
files.append(composite_upload)
logger.debug(f"์›๋ณธ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์™„๋ฃŒ. URI: {composite_upload.uri}, MIME ํƒ€์ž…: {composite_upload.mime_type}")
# ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ (์„ ํƒ)
if background_file is not None:
background_upload = client.files.upload(file=background_file)
files.append(background_upload)
logger.debug(f"๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์™„๋ฃŒ. URI: {background_upload.uri}, MIME ํƒ€์ž…: {background_upload.mime_type}")
# ์Šคํƒ€์ผ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ (์„ ํƒ)
if style_file is not None:
style_upload = client.files.upload(file=style_file)
files.append(style_upload)
logger.debug(f"์Šคํƒ€์ผ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์™„๋ฃŒ. URI: {style_upload.uri}, MIME ํƒ€์ž…: {style_upload.mime_type}")
# ํŒŒ์ผ ์—…๋กœ๋“œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ฝ˜ํ…์ธ  ํŒŒํŠธ ์ƒ์„ฑ
parts = []
for file_obj in files:
parts.append(types.Part.from_uri(file_uri=file_obj.uri, mime_type=file_obj.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 files
logger.debug("์—…๋กœ๋“œ๋œ ํŒŒ์ผ ์ •๋ณด ์‚ญ์ œ ์™„๋ฃŒ.")
return temp_path
except Exception as e:
logger.exception("์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
return None # ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ None ๋ฐ˜ํ™˜
def process_image_and_prompt(composite_pil, background_pil, style_pil, prompt):
logger.debug(f"process_image_and_prompt ํ•จ์ˆ˜ ์‹œ์ž‘ - ํ”„๋กฌํ”„ํŠธ: '{prompt}'")
try:
# ์›๋ณธ ์ด๋ฏธ์ง€ ์ €์žฅ
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
composite_path = tmp.name
composite_pil.save(composite_path)
logger.debug(f"์›๋ณธ ์ด๋ฏธ์ง€ ์ €์žฅ ์™„๋ฃŒ: {composite_path}")
background_path = None
style_path = None
# ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์ €์žฅ (์ž…๋ ฅ๋œ ๊ฒฝ์šฐ)
if background_pil is not None:
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp2:
background_path = tmp2.name
background_pil.save(background_path)
logger.debug(f"๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์ €์žฅ ์™„๋ฃŒ: {background_path}")
# ์Šคํƒ€์ผ ์ด๋ฏธ์ง€ ์ €์žฅ (์ž…๋ ฅ๋œ ๊ฒฝ์šฐ)
if style_pil is not None:
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp3:
style_path = tmp3.name
style_pil.save(style_path)
logger.debug(f"์Šคํƒ€์ผ ์ด๋ฏธ์ง€ ์ €์žฅ ์™„๋ฃŒ: {style_path}")
model = "gemini-2.0-flash-exp-image-generation"
gemma_edited_image_path = generate(text=prompt,
composite_file=composite_path,
background_file=background_path,
style_file=style_path,
model=model)
if gemma_edited_image_path:
logger.debug(f"์ด๋ฏธ์ง€ ์ƒ์„ฑ ์™„๋ฃŒ. ๊ฒฝ๋กœ: {gemma_edited_image_path}")
result_img = Image.open(gemma_edited_image_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_image_and_prompt ํ•จ์ˆ˜์—์„œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
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>Gemini API ํ‚ค๋Š” ํ™˜๊ฒฝ๋ณ€์ˆ˜(GEMINI_API_KEY)๋กœ ์„ค์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.</p>
</div>
</div>
"""
)
gr.Markdown("์›๋ณธ ์ด๋ฏธ์ง€, ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€, ์Šคํƒ€์ผ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ณ , ํŽธ์ง‘ํ•  ๋‚ด์šฉ์„ ์ž…๋ ฅํ•˜์„ธ์š”.")
with gr.Row():
with gr.Column():
composite_input = gr.Image(type="pil", label="์›๋ณธ ์ด๋ฏธ์ง€", image_mode="RGBA")
background_input = gr.Image(type="pil", label="๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€", image_mode="RGBA")
style_input = gr.Image(type="pil", label="์Šคํƒ€์ผ ์ด๋ฏธ์ง€", image_mode="RGBA")
prompt_input = gr.Textbox(
lines=2,
placeholder="ํŽธ์ง‘ํ•  ๋‚ด์šฉ์„ ์ž…๋ ฅํ•˜์„ธ์š”...",
label="ํŽธ์ง‘ ํ”„๋กฌํ”„ํŠธ"
)
submit_btn = gr.Button("์ด๋ฏธ์ง€ ํŽธ์ง‘ ์‹คํ–‰")
with gr.Column():
output_gallery = gr.Gallery(label="ํŽธ์ง‘ ๊ฒฐ๊ณผ")
submit_btn.click(
fn=process_image_and_prompt,
inputs=[composite_input, background_input, style_input, prompt_input],
outputs=output_gallery,
)
# --- ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ---
dummy_composite = Image.new("RGBA", (100, 100), color="red")
dummy_background = Image.new("RGBA", (100, 100), color="green")
dummy_style = Image.new("RGBA", (100, 100), color="blue")
dummy_prompt = "์ด๋ฏธ์ง€์— ์ƒˆ๋กœ์šด ์Šคํƒ€์ผ์„ ์ ์šฉํ•ด์ค˜"
logger.info("process_image_and_prompt ํ•จ์ˆ˜๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค...")
result = process_image_and_prompt(dummy_composite, dummy_background, dummy_style, dummy_prompt)
if result:
logger.info(f"์ง์ ‘ ํ˜ธ์ถœ ์„ฑ๊ณต. ๊ฒฐ๊ณผ: {result}")
else:
logger.error("์ง์ ‘ ํ˜ธ์ถœ ์‹คํŒจ.")
demo.launch(share=True)