|
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 |
|
|
|
|
|
from dotenv import load_dotenv |
|
load_dotenv() |
|
|
|
|
|
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: |
|
|
|
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 |
|
|
|
|
|
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" |
|
|
|
|
|
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 [] |
|
|
|
|
|
|
|
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__": |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
demo.launch(share=True) |