|
import gradio as gr |
|
import google.generativeai as genai |
|
from PIL import Image |
|
import os |
|
import json |
|
import tempfile |
|
import re |
|
import time |
|
import logging |
|
|
|
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') |
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "") |
|
|
|
|
|
genai.configure(api_key=GEMINI_API_KEY) |
|
|
|
|
|
BACKGROUNDS_DIR = "./background" |
|
|
|
|
|
print(f"νμ¬ μμ
λλ ν 리: {os.getcwd()}") |
|
print(f"μ¬μ© μ€μΈ λ°°κ²½ λλ ν 리 κ²½λ‘: {BACKGROUNDS_DIR}") |
|
|
|
|
|
if not os.path.exists(BACKGROUNDS_DIR): |
|
os.makedirs(BACKGROUNDS_DIR) |
|
print(f"λ°°κ²½ λλ ν 리λ₯Ό μμ±νμ΅λλ€: {BACKGROUNDS_DIR}") |
|
else: |
|
print(f"λ°°κ²½ λλ ν λ¦¬κ° μ΄λ―Έ μ‘΄μ¬ν©λλ€: {BACKGROUNDS_DIR}") |
|
try: |
|
for file in os.listdir(BACKGROUNDS_DIR): |
|
print(f"λ°κ²¬λ νμΌ: {file}") |
|
except Exception as e: |
|
print(f"λλ ν 리 λ΄μ©μ λμ΄νλ μ€ μ€λ₯ λ°μ: {str(e)}") |
|
|
|
|
|
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) |
|
print(f"{filename} νμΌμ μ±κ³΅μ μΌλ‘ λ‘λνμ΅λλ€. {len(data)} νλͺ© ν¬ν¨.") |
|
return data |
|
except FileNotFoundError: |
|
print(f"κ²½κ³ : {filename} νμΌμ μ°Ύμ μ μμ΅λλ€. κΈ°λ³Έκ°μ μ¬μ©ν©λλ€.") |
|
return {} |
|
except json.JSONDecodeError: |
|
print(f"κ²½κ³ : {filename} νμΌμ JSON νμμ΄ μ¬λ°λ₯΄μ§ μμ΅λλ€. κΈ°λ³Έκ°μ μ¬μ©ν©λλ€.") |
|
return {} |
|
except Exception as e: |
|
print(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") |
|
|
|
|
|
if not SIMPLE_BACKGROUNDS: |
|
SIMPLE_BACKGROUNDS = {"νμ΄νΈ λ°°κ²½": "white background"} |
|
if not STUDIO_BACKGROUNDS: |
|
STUDIO_BACKGROUNDS = {"μ ν μ¬μ§ μ€νλμ€": "product photography studio"} |
|
if not NATURE_BACKGROUNDS: |
|
NATURE_BACKGROUNDS = {"μ΄λ ν΄λ³": "tropical beach"} |
|
if not INDOOR_BACKGROUNDS: |
|
INDOOR_BACKGROUNDS = {"λͺ¨λ 리λΉλ£Έ": "modern living room"} |
|
if not ABSTRACT_BACKGROUNDS: |
|
ABSTRACT_BACKGROUNDS = {"λ€μ¨ μ‘°λͺ
": "neon lights"} |
|
|
|
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"λ₯Ό μΆκ°ν΄μ£ΌμΈμ. |
|
|
|
νκ΅μ΄ μ
λ ₯ λ΄μ©μ μμ΄λ‘ μ μ ν λ²μνμ¬ λ°μν΄μ£ΌμΈμ. |
|
""" |
|
model = genai.GenerativeModel( |
|
'gemini-2.0-flash', |
|
system_instruction=generate_system_instruction() |
|
) |
|
response = model.generate_content( |
|
prompt_request, |
|
generation_config=genai.GenerationConfig( |
|
temperature=0.7, |
|
top_p=0.95, |
|
top_k=64, |
|
max_output_tokens=1024, |
|
) |
|
) |
|
response_text = response.text.strip() |
|
if "--ar 1:1" not in response_text: |
|
response_text = response_text.rstrip(".") + ". --ar 1:1 --s 750 --q 2" |
|
return response_text |
|
except Exception as e: |
|
return f"ν둬ννΈ μμ± μ€ μ€λ₯κ° λ°μνμ΅λλ€: {str(e)}" |
|
|
|
|
|
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") |
|
prompt = prompt.replace("#2", "IMAGE_TAG_TWO") |
|
prompt = prompt.replace("#3", "IMAGE_TAG_THREE") |
|
|
|
try: |
|
if not GEMINI_API_KEY: |
|
logger.error("Gemini API ν€κ° μ€μ λμ§ μμμ΅λλ€.") |
|
prompt = prompt.replace("IMAGE_TAG_ONE", "#1") |
|
prompt = prompt.replace("IMAGE_TAG_TWO", "#2") |
|
prompt = prompt.replace("IMAGE_TAG_THREE", "#3") |
|
return prompt |
|
|
|
model = genai.GenerativeModel('gemini-2.0-flash') |
|
translation_prompt = f""" |
|
Translate the following Korean text to English: |
|
|
|
{prompt} |
|
|
|
IMPORTANT: The tokens IMAGE_TAG_ONE, IMAGE_TAG_TWO, and IMAGE_TAG_THREE are special tags |
|
and must be preserved exactly as is in your translation. Do not translate these tokens. |
|
""" |
|
|
|
logger.info(f"Translation prompt: {translation_prompt}") |
|
response = model.generate_content( |
|
translation_prompt, |
|
generation_config=genai.GenerationConfig( |
|
temperature=0.2, |
|
top_p=0.95, |
|
top_k=40, |
|
max_output_tokens=512 |
|
) |
|
) |
|
|
|
translated_text = response.text |
|
|
|
if translated_text.strip(): |
|
translated_text = translated_text.replace("IMAGE_TAG_ONE", "#1") |
|
translated_text = translated_text.replace("IMAGE_TAG_TWO", "#2") |
|
translated_text = translated_text.replace("IMAGE_TAG_THREE", "#3") |
|
logger.info(f"Translated text: {translated_text.strip()}") |
|
return translated_text.strip() |
|
else: |
|
logger.warning("λ²μ κ²°κ³Όκ° μμ΅λλ€. μλ³Έ ν둬ννΈ μ¬μ©") |
|
prompt = prompt.replace("IMAGE_TAG_ONE", "#1") |
|
prompt = prompt.replace("IMAGE_TAG_TWO", "#2") |
|
prompt = prompt.replace("IMAGE_TAG_THREE", "#3") |
|
return prompt |
|
except Exception as e: |
|
logger.exception("λ²μ μ€ μ€λ₯ λ°μ:") |
|
prompt = prompt.replace("IMAGE_TAG_ONE", "#1") |
|
prompt = prompt.replace("IMAGE_TAG_TWO", "#2") |
|
prompt = prompt.replace("IMAGE_TAG_THREE", "#3") |
|
return prompt |
|
|
|
def preprocess_prompt(prompt, image1, image2, image3): |
|
has_img1 = image1 is not None |
|
has_img2 = image2 is not None |
|
has_img3 = image3 is not None |
|
|
|
if "#1" in prompt and not has_img1: |
|
prompt = prompt.replace("#1", "첫 λ²μ§Έ μ΄λ―Έμ§(μμ)") |
|
else: |
|
prompt = prompt.replace("#1", "첫 λ²μ§Έ μ΄λ―Έμ§") |
|
|
|
if "#2" in prompt and not has_img2: |
|
prompt = prompt.replace("#2", "λ λ²μ§Έ μ΄λ―Έμ§(μμ)") |
|
else: |
|
prompt = prompt.replace("#2", "λ λ²μ§Έ μ΄λ―Έμ§") |
|
|
|
if "#3" in prompt and not has_img3: |
|
prompt = prompt.replace("#3", "μΈ λ²μ§Έ μ΄λ―Έμ§(μμ)") |
|
else: |
|
prompt = prompt.replace("#3", "μΈ λ²μ§Έ μ΄λ―Έμ§") |
|
|
|
if "1. μ΄λ―Έμ§ λ³κ²½" in prompt: |
|
desc_match = re.search(r'#1μ "(.*?)"μΌλ‘ λ°κΏλΌ', prompt) |
|
if desc_match: |
|
description = desc_match.group(1) |
|
prompt = f"첫 λ²μ§Έ μ΄λ―Έμ§λ₯Ό {description}μΌλ‘ λ³κ²½ν΄μ£ΌμΈμ. μλ³Έ μ΄λ―Έμ§μ μ£Όμ λ΄μ©μ μ μ§νλ μλ‘μ΄ μ€νμΌκ³Ό λΆμκΈ°λ‘ μ¬ν΄μν΄μ£ΌμΈμ." |
|
else: |
|
prompt = "첫 λ²μ§Έ μ΄λ―Έμ§λ₯Ό μ°½μμ μΌλ‘ λ³νν΄μ£ΌμΈμ. λ μμνκ³ μμ μ μΈ λ²μ μΌλ‘ λ§λ€μ΄μ£ΌμΈμ." |
|
|
|
elif "2. κΈμμ§μ°κΈ°" in prompt: |
|
text_match = re.search(r'#1μμ "(.*?)"λ₯Ό μ§μλΌ', prompt) |
|
if text_match: |
|
text_to_remove = text_match.group(1) |
|
prompt = f"첫 λ²μ§Έ μ΄λ―Έμ§μμ '{text_to_remove}' ν
μ€νΈλ₯Ό μ°Ύμ μμ°μ€λ½κ² μ κ±°ν΄μ£ΌμΈμ. ν
μ€νΈκ° μλ λΆλΆμ λ°°κ²½κ³Ό μ‘°νλ‘κ² μ±μμ£ΌμΈμ." |
|
else: |
|
prompt = "첫 λ²μ§Έ μ΄λ―Έμ§μμ λͺ¨λ ν
μ€νΈλ₯Ό μ°Ύμ μμ°μ€λ½κ² μ κ±°ν΄μ£ΌμΈμ. κΉλν μ΄λ―Έμ§λ‘ λ§λ€μ΄μ£ΌμΈμ." |
|
|
|
elif "4. μ·λ°κΎΈκΈ°" in prompt: |
|
prompt = "첫 λ²μ§Έ μ΄λ―Έμ§μ μΈλ¬Ό μμμ λ λ²μ§Έ μ΄λ―Έμ§μ μμμΌλ‘ λ³κ²½ν΄μ£ΌμΈμ. μμμ μ€νμΌκ³Ό μμμ λ λ²μ§Έ μ΄λ―Έμ§λ₯Ό λ°λ₯΄λ, μ 체 λΉμ¨κ³Ό ν¬μ¦λ 첫 λ²μ§Έ μ΄λ―Έμ§λ₯Ό μ μ§ν΄μ£ΌμΈμ." |
|
|
|
elif "5. λ°°κ²½λ°κΎΈκΈ°" in prompt: |
|
prompt = "첫 λ²μ§Έ μ΄λ―Έμ§μ λ°°κ²½μ λ λ²μ§Έ μ΄λ―Έμ§μ λ°°κ²½μΌλ‘ λ³κ²½ν΄μ£ΌμΈμ. 첫 λ²μ§Έ μ΄λ―Έμ§μ μ£Όμ νΌμ¬μ²΄λ μ μ§νκ³ , λ λ²μ§Έ μ΄λ―Έμ§μ λ°°κ²½κ³Ό μ‘°νλ‘κ² ν©μ±ν΄μ£ΌμΈμ." |
|
|
|
elif "6. μ΄λ―Έμ§ ν©μ±(μνν¬ν¨)" in prompt: |
|
prompt = "첫 λ²μ§Έ μ΄λ―Έμ§μ λ λ²μ§Έ μ΄λ―Έμ§(λλ μΈ λ²μ§Έ μ΄λ―Έμ§)λ₯Ό μμ°μ€λ½κ² ν©μ±ν΄μ£ΌμΈμ. λͺ¨λ μ΄λ―Έμ§μ μ£Όμ μμλ₯Ό ν¬ν¨νκ³ , νΉν μνμ΄ λ보μ΄λλ‘ μ‘°νλ‘κ² ν΅ν©ν΄μ£ΌμΈμ." |
|
|
|
prompt += " μ΄λ―Έμ§λ₯Ό μμ±ν΄μ£ΌμΈμ. μ΄λ―Έμ§μ ν
μ€νΈλ κΈμλ₯Ό ν¬ν¨νμ§ λ§μΈμ." |
|
return prompt |
|
|
|
def generate_with_images(prompt, images, variation_index=0): |
|
try: |
|
if not GEMINI_API_KEY: |
|
return None, "API ν€κ° μ€μ λμ§ μμμ΅λλ€. νκ²½λ³μλ₯Ό νμΈν΄μ£ΌμΈμ." |
|
|
|
model = genai.GenerativeModel('gemini-2.0-flash-exp-image-generation') |
|
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 = model.generate_content( |
|
contents=contents, |
|
generation_config=genai.GenerationConfig( |
|
temperature=1, |
|
top_p=0.95, |
|
top_k=40, |
|
max_output_tokens=8192 |
|
) |
|
) |
|
|
|
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp: |
|
temp_path = tmp.name |
|
result_text = "" |
|
image_found = False |
|
|
|
if hasattr(response, 'candidates') and response.candidates: |
|
candidate = response.candidates[0] |
|
if hasattr(candidate, 'content') and candidate.content: |
|
for part in candidate.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") |
|
|
|
return result_img, f"μ΄λ―Έμ§κ° μ±κ³΅μ μΌλ‘ μμ±λμμ΅λλ€. {result_text}" |
|
except Exception as e: |
|
logger.exception("μ΄λ―Έμ§ μμ± μ€ μ€λ₯ λ°μ:") |
|
return None, f"μ€λ₯ λ°μ: {str(e)}" |
|
|
|
def process_images_with_prompt(image1, image2, image3, prompt, variation_index=0, max_retries=3): |
|
retry_count = 0 |
|
last_error = None |
|
|
|
while retry_count < max_retries: |
|
try: |
|
images = [image1, image2, image3] |
|
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, image2, image3) |
|
if re.search("[κ°-ν£]", processed_prompt): |
|
final_prompt = translate_prompt_to_english(processed_prompt) |
|
else: |
|
final_prompt = processed_prompt |
|
else: |
|
if len(valid_images) == 1: |
|
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") |
|
elif len(valid_images) == 2: |
|
final_prompt = "Please seamlessly composite these two images, integrating their key elements harmoniously into a single image. Do not include any text or watermarks in the generated image." |
|
logger.info("Default prompt generated for two images") |
|
else: |
|
final_prompt = "Please creatively composite these three images, combining their main elements into a cohesive and natural scene. Do not include any text or watermarks in the generated image." |
|
logger.info("Default prompt generated for three images") |
|
|
|
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, image2, image3, 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, image2, image3, 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 |
|
|
|
|
|
custom_css = """ |
|
:root { |
|
--primary-color: #5561e9; |
|
--secondary-color: #6c8aff; |
|
--accent-color: #ff6b6b; |
|
--background-color: #f0f5ff; |
|
--card-bg: #ffffff; |
|
--text-color: #334155; |
|
--border-radius: 18px; |
|
--shadow: 0 8px 30px rgba(0, 0, 0, 0.08); |
|
} |
|
|
|
body { |
|
font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif; |
|
background-color: var(--background-color); |
|
color: var(--text-color); |
|
line-height: 1.6; |
|
} |
|
|
|
/* Gradio 컨ν
μ΄λ μ€λ²λΌμ΄λ */ |
|
.gradio-container { |
|
max-width: 100% !important; |
|
margin: 0 auto !important; |
|
padding: 0 !important; |
|
background-color: var(--background-color) !important; |
|
} |
|
|
|
/* μλ¨ ν€λ */ |
|
.app-header { |
|
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); |
|
color: white; |
|
padding: 2rem; |
|
border-radius: var(--border-radius); |
|
margin-bottom: 1.5rem; |
|
box-shadow: var(--shadow); |
|
text-align: center; |
|
} |
|
|
|
.app-header h1 { |
|
margin: 0; |
|
font-size: 2.5rem; |
|
font-weight: 700; |
|
letter-spacing: -0.5px; |
|
} |
|
|
|
.app-header p { |
|
margin: 0.75rem 0 0; |
|
font-size: 1.1rem; |
|
opacity: 0.9; |
|
} |
|
|
|
/* ν¨λ μ€νμΌλ§ */ |
|
.panel { |
|
background-color: var(--card-bg); |
|
border-radius: var(--border-radius); |
|
box-shadow: var(--shadow); |
|
padding: 1.5rem; |
|
margin-bottom: 1.5rem; |
|
border: 1px solid rgba(0, 0, 0, 0.04); |
|
transition: transform 0.3s ease; |
|
} |
|
|
|
.panel:hover { |
|
transform: translateY(-5px); |
|
} |
|
|
|
/* μΉμ
μ λͺ© */ |
|
.section-title { |
|
font-size: 1.5rem; |
|
font-weight: 700; |
|
color: var(--primary-color); |
|
margin-bottom: 1rem; |
|
padding-bottom: 0.5rem; |
|
border-bottom: 2px solid var(--secondary-color); |
|
display: flex; |
|
align-items: center; |
|
} |
|
|
|
.section-title i { |
|
margin-right: 0.5rem; |
|
font-size: 1.4rem; |
|
} |
|
|
|
/* λ²νΌ μ€νμΌλ§ */ |
|
.custom-button { |
|
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); |
|
color: white !important; |
|
border: none !important; |
|
border-radius: calc(var(--border-radius) - 5px) !important; |
|
padding: 0.8rem 1.2rem !important; |
|
font-weight: 600 !important; |
|
cursor: pointer !important; |
|
transition: all 0.3s ease !important; |
|
box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08); |
|
text-transform: none !important; |
|
display: flex !important; |
|
align-items: center !important; |
|
justify-content: center !important; |
|
} |
|
|
|
.custom-button:hover { |
|
transform: translateY(-2px) !important; |
|
box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08) !important; |
|
} |
|
|
|
.custom-button.primary { |
|
background: linear-gradient(135deg, var(--accent-color), #ff9a8b) !important; |
|
} |
|
|
|
.custom-button i { |
|
margin-right: 0.5rem; |
|
} |
|
|
|
/* μ΄λ―Έμ§ 컨ν
μ΄λ */ |
|
.image-container { |
|
border-radius: var(--border-radius); |
|
overflow: hidden; |
|
border: 1px solid rgba(0, 0, 0, 0.08); |
|
transition: all 0.3s ease; |
|
background-color: white; |
|
} |
|
|
|
.image-container:hover { |
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
/* ν 컨ν
μ΄λ */ |
|
.custom-tabs { |
|
background-color: transparent !important; |
|
border: none !important; |
|
margin-bottom: 1rem; |
|
} |
|
|
|
.custom-tabs button { |
|
background-color: rgba(255, 255, 255, 0.7) !important; |
|
border: none !important; |
|
border-radius: var(--border-radius) var(--border-radius) 0 0 !important; |
|
padding: 0.8rem 1.5rem !important; |
|
font-weight: 600 !important; |
|
color: var(--text-color) !important; |
|
transition: all 0.3s ease !important; |
|
} |
|
|
|
.custom-tabs button[aria-selected="true"] { |
|
background-color: var(--primary-color) !important; |
|
color: white !important; |
|
} |
|
|
|
/* μ
λ ₯ νλ */ |
|
.custom-input { |
|
border-radius: calc(var(--border-radius) - 5px) !important; |
|
border: 1px solid rgba(0, 0, 0, 0.1) !important; |
|
padding: 0.8rem 1rem !important; |
|
transition: all 0.3s ease !important; |
|
} |
|
|
|
.custom-input:focus { |
|
border-color: var(--primary-color) !important; |
|
box-shadow: 0 0 0 2px rgba(85, 97, 233, 0.2) !important; |
|
} |
|
|
|
/* μ¬μ©μ λ§€λ΄μΌ */ |
|
.user-manual { |
|
background-color: white; |
|
padding: 2rem; |
|
border-radius: var(--border-radius); |
|
box-shadow: var(--shadow); |
|
margin-top: 2rem; |
|
} |
|
|
|
.manual-title { |
|
font-size: 1.8rem; |
|
font-weight: 700; |
|
color: var(--primary-color); |
|
margin-bottom: 1.5rem; |
|
text-align: center; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
} |
|
|
|
.manual-title i { |
|
margin-right: 0.5rem; |
|
font-size: 1.8rem; |
|
} |
|
|
|
.manual-section { |
|
margin-bottom: 1.5rem; |
|
padding: 1.2rem; |
|
background-color: #f8faff; |
|
border-radius: calc(var(--border-radius) - 5px); |
|
} |
|
|
|
.manual-section-title { |
|
font-size: 1.3rem; |
|
font-weight: 700; |
|
margin-bottom: 1rem; |
|
color: var(--primary-color); |
|
display: flex; |
|
align-items: center; |
|
} |
|
|
|
.manual-section-title i { |
|
margin-right: 0.5rem; |
|
font-size: 1.2rem; |
|
} |
|
|
|
.manual-text { |
|
font-size: 1rem; |
|
line-height: 1.7; |
|
} |
|
|
|
.manual-text strong { |
|
color: var(--accent-color); |
|
} |
|
|
|
.tip-box { |
|
background-color: rgba(255, 107, 107, 0.1); |
|
border-left: 3px solid var(--accent-color); |
|
padding: 1rem 1.2rem; |
|
margin: 1rem 0; |
|
border-radius: 8px; |
|
} |
|
|
|
/* λ²νΌ κ·Έλ£Ή */ |
|
.button-grid { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); |
|
gap: 0.8rem; |
|
margin-bottom: 1.2rem; |
|
} |
|
|
|
/* λ‘λ© μ λλ©μ΄μ
*/ |
|
.progress-container { |
|
background-color: rgba(255, 255, 255, 0.9); |
|
border-radius: var(--border-radius); |
|
padding: 2rem; |
|
box-shadow: var(--shadow); |
|
text-align: center; |
|
} |
|
|
|
.progress-bar { |
|
height: 8px; |
|
border-radius: 4px; |
|
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color)); |
|
margin: 1rem 0; |
|
} |
|
|
|
/* μμ 그리λ */ |
|
.examples-grid { |
|
display: grid; |
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); |
|
gap: 1rem; |
|
margin: 1.5rem 0; |
|
} |
|
|
|
.example-card { |
|
background: white; |
|
border-radius: var(--border-radius); |
|
overflow: hidden; |
|
box-shadow: var(--shadow); |
|
transition: transform 0.3s ease; |
|
} |
|
|
|
.example-card:hover { |
|
transform: translateY(-5px); |
|
} |
|
|
|
/* λ°μν */ |
|
@media (max-width: 768px) { |
|
.button-grid { |
|
grid-template-columns: repeat(2, 1fr); |
|
} |
|
} |
|
|
|
/* μ 체 μΈν°νμ΄μ€ λ₯κ·Ό λͺ¨μ리 */ |
|
.block, .prose, .gr-prose, .gr-form, .gr-panel { |
|
border-radius: var(--border-radius) !important; |
|
} |
|
|
|
/* λ©μΈ 컨ν
μΈ μ€ν¬λ‘€λ° */ |
|
::-webkit-scrollbar { |
|
width: 8px; |
|
height: 8px; |
|
} |
|
|
|
::-webkit-scrollbar-track { |
|
background: rgba(0, 0, 0, 0.05); |
|
border-radius: 10px; |
|
} |
|
|
|
::-webkit-scrollbar-thumb { |
|
background: var(--secondary-color); |
|
border-radius: 10px; |
|
} |