|
|
import gradio as gr |
|
|
import google.generativeai as genai |
|
|
from PIL import Image |
|
|
import os |
|
|
import json |
|
|
|
|
|
|
|
|
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "") |
|
|
|
|
|
|
|
|
genai.configure(api_key=GEMINI_API_KEY) |
|
|
|
|
|
|
|
|
BACKGROUNDS_DIR = os.path.join(os.path.dirname(__file__), "background") |
|
|
|
|
|
|
|
|
if not os.path.exists(BACKGROUNDS_DIR): |
|
|
os.makedirs(BACKGROUNDS_DIR) |
|
|
|
|
|
|
|
|
def load_background_json(filename): |
|
|
file_path = os.path.join(BACKGROUNDS_DIR, filename) |
|
|
try: |
|
|
with open(file_path, 'r', encoding='utf-8') as f: |
|
|
return json.load(f) |
|
|
except FileNotFoundError: |
|
|
print(f"κ²½κ³ : {filename} νμΌμ μ°Ύμ μ μμ΅λλ€. κΈ°λ³Έκ°μ μ¬μ©ν©λλ€.") |
|
|
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") |
|
|
|
|
|
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.types.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 create_app(): |
|
|
with gr.Blocks(title="κ³ κΈ μν μ΄λ―Έμ§ λ°°κ²½ ν둬ννΈ μμ±κΈ°") as demo: |
|
|
gr.Markdown("# κ³ κΈ μν μ΄λ―Έμ§ λ°°κ²½ ν둬ννΈ μμ±κΈ°") |
|
|
gr.Markdown("μν μ΄λ―Έμ§λ₯Ό μ
λ‘λνκ³ μ΅μ
μ μ ννλ©΄ κ³ νμ§ μμ
μ© ν둬ννΈκ° μμ±λ©λλ€.") |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
product_name = gr.Textbox(label="μνλͺ
(νκ΅μ΄ μ
λ ₯)", placeholder="μ: μ€ν¨μΌμ΄ νλΈ, ν
λΈλ¬ λ±", interactive=True) |
|
|
|
|
|
with gr.Row(): |
|
|
|
|
|
with gr.Column(scale=1): |
|
|
image_input = gr.Image(label="μν μ΄λ―Έμ§ μ
λ‘λ", type="pil") |
|
|
|
|
|
|
|
|
gr.Markdown("## λ°°κ²½ μ ν (ν κ°μ§λ§ μ ν)") |
|
|
|
|
|
with gr.Tabs() as background_tabs: |
|
|
with gr.TabItem("μ¬ν λ°°κ²½"): |
|
|
simple_dropdown = gr.Dropdown( |
|
|
choices=list(SIMPLE_BACKGROUNDS.keys()), |
|
|
value=list(SIMPLE_BACKGROUNDS.keys())[0] if SIMPLE_BACKGROUNDS else None, |
|
|
label="μ¬ν λ°°κ²½ μ ν", |
|
|
interactive=True |
|
|
) |
|
|
|
|
|
with gr.TabItem("μ€νλμ€ λ°°κ²½"): |
|
|
studio_dropdown = gr.Dropdown( |
|
|
choices=list(STUDIO_BACKGROUNDS.keys()), |
|
|
value=list(STUDIO_BACKGROUNDS.keys())[0] if STUDIO_BACKGROUNDS else None, |
|
|
label="μ€νλμ€ λ°°κ²½ μ ν", |
|
|
interactive=True |
|
|
) |
|
|
|
|
|
with gr.TabItem("μμ° νκ²½"): |
|
|
nature_dropdown = gr.Dropdown( |
|
|
choices=list(NATURE_BACKGROUNDS.keys()), |
|
|
value=list(NATURE_BACKGROUNDS.keys())[0] if NATURE_BACKGROUNDS else None, |
|
|
label="μμ° νκ²½ μ ν", |
|
|
interactive=True |
|
|
) |
|
|
|
|
|
with gr.TabItem("μ€λ΄ νκ²½"): |
|
|
indoor_dropdown = gr.Dropdown( |
|
|
choices=list(INDOOR_BACKGROUNDS.keys()), |
|
|
value=list(INDOOR_BACKGROUNDS.keys())[0] if INDOOR_BACKGROUNDS else None, |
|
|
label="μ€λ΄ νκ²½ μ ν", |
|
|
interactive=True |
|
|
) |
|
|
|
|
|
with gr.TabItem("μΆμ/νΉμ λ°°κ²½"): |
|
|
abstract_dropdown = gr.Dropdown( |
|
|
choices=list(ABSTRACT_BACKGROUNDS.keys()), |
|
|
value=list(ABSTRACT_BACKGROUNDS.keys())[0] if ABSTRACT_BACKGROUNDS else None, |
|
|
label="μΆμ/νΉμ λ°°κ²½ μ ν", |
|
|
interactive=True |
|
|
) |
|
|
|
|
|
|
|
|
additional_info = gr.Textbox( |
|
|
label="μΆκ° μμ²μ¬ν (μ νμ¬ν)", |
|
|
placeholder="μ: κ³ κΈμ€λ¬μ΄ λλ, λ°μ μ‘°λͺ
λ±", |
|
|
lines=3, |
|
|
interactive=True |
|
|
) |
|
|
|
|
|
submit_btn = gr.Button("ν둬ννΈ μμ±", variant="primary") |
|
|
|
|
|
|
|
|
with gr.Column(scale=1): |
|
|
prompt_output = gr.Textbox(label="μμ±λ ν둬ννΈ", lines=10) |
|
|
image_preview = gr.Image(label="μ
λ‘λλ μ΄λ―Έμ§ (#1)", type="pil") |
|
|
preview_html = gr.HTML("ν둬ννΈ μμ± μ μ¬κΈ°μ λ―Έλ¦¬λ³΄κΈ°κ° νμλ©λλ€.") |
|
|
|
|
|
|
|
|
def get_selected_background_info(active_tab, simple, studio, nature, indoor, abstract): |
|
|
if active_tab == "μ¬ν λ°°κ²½" and simple: |
|
|
return { |
|
|
"category": "μ¬ν λ°°κ²½", |
|
|
"name": simple, |
|
|
"english": SIMPLE_BACKGROUNDS.get(simple, "white background") |
|
|
} |
|
|
elif active_tab == "μ€νλμ€ λ°°κ²½" and studio: |
|
|
return { |
|
|
"category": "μ€νλμ€ λ°°κ²½", |
|
|
"name": studio, |
|
|
"english": STUDIO_BACKGROUNDS.get(studio, "product photography studio") |
|
|
} |
|
|
elif active_tab == "μμ° νκ²½" and nature: |
|
|
return { |
|
|
"category": "μμ° νκ²½", |
|
|
"name": nature, |
|
|
"english": NATURE_BACKGROUNDS.get(nature, "natural environment") |
|
|
} |
|
|
elif active_tab == "μ€λ΄ νκ²½" and indoor: |
|
|
return { |
|
|
"category": "μ€λ΄ νκ²½", |
|
|
"name": indoor, |
|
|
"english": INDOOR_BACKGROUNDS.get(indoor, "indoor environment") |
|
|
} |
|
|
elif active_tab == "μΆμ/νΉμ λ°°κ²½" and abstract: |
|
|
return { |
|
|
"category": "μΆμ/νΉμ λ°°κ²½", |
|
|
"name": abstract, |
|
|
"english": ABSTRACT_BACKGROUNDS.get(abstract, "abstract background") |
|
|
} |
|
|
else: |
|
|
return { |
|
|
"category": "κΈ°λ³Έ λ°°κ²½", |
|
|
"name": "νμ΄νΈ λ°°κ²½", |
|
|
"english": "white background" |
|
|
} |
|
|
|
|
|
|
|
|
def generate_output(image, active_tab, simple, studio, nature, indoor, abstract, product_text, additional_text): |
|
|
if image is None: |
|
|
gr.Warning("μ΄λ―Έμ§λ₯Ό μ
λ‘λν΄μ£ΌμΈμ.") |
|
|
return "μ΄λ―Έμ§λ₯Ό μ
λ‘λν΄μ£ΌμΈμ.", None, "μ΄λ―Έμ§λ₯Ό μ
λ‘λ ν ν둬ννΈλ₯Ό μμ±ν΄μ£ΌμΈμ." |
|
|
|
|
|
product_text = product_text.strip() or "μ ν" |
|
|
|
|
|
|
|
|
background_info = get_selected_background_info(active_tab, simple, studio, nature, indoor, abstract) |
|
|
|
|
|
try: |
|
|
prompt = generate_prompt_with_gemini(product_text, background_info, additional_text) |
|
|
|
|
|
if not GEMINI_API_KEY: |
|
|
gr.Warning("Gemini API ν€κ° μ€μ λμ§ μμμ΅λλ€. ν€λ₯Ό λ°κΈλ°μ μ€μ ν΄μ£ΌμΈμ.") |
|
|
prompt = """ |
|
|
[Gemini API ν€ λλ½] |
|
|
API ν€ μ€μ λ°©λ²: |
|
|
1. νκ²½ λ³μ: export GEMINI_API_KEY="your-api-key" |
|
|
2. μ½λ λ΄ μ§μ μ
λ ₯: GEMINI_API_KEY = "your-api-key" |
|
|
ν€ λ°κΈ: https://makersuite.google.com/ |
|
|
""" |
|
|
return prompt, image, "API ν€λ₯Ό μ€μ ν΄μΌ ν©λλ€." |
|
|
|
|
|
|
|
|
preview = f""" |
|
|
<div style="padding:10px; border:1px solid #ddd; border-radius:8px; margin-top:10px;"> |
|
|
<h3>ν둬ννΈ μμ½</h3> |
|
|
<p><strong>μ΄ κΈΈμ΄:</strong> {len(prompt)} κΈμ</p> |
|
|
<p><strong>λ°°κ²½:</strong> {background_info['category']} > {background_info['name']}</p> |
|
|
<p><strong>μ£Όμ μμ:</strong> {", ".join([kw for kw in ["commercial photography", "product", "square format", "centered", "detailed"] if kw.lower() in prompt.lower()])}</p> |
|
|
<p><strong>λ―Έλμ λ νλΌλ―Έν°:</strong> {" ".join([param for param in ["--ar 1:1", "--s 750", "--q 2"] if param in prompt])}</p> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
return prompt, image, preview |
|
|
except Exception as e: |
|
|
error_msg = f"ν둬ννΈ μμ± μ€ μ€λ₯ λ°μ: {str(e)}" |
|
|
gr.Error(error_msg) |
|
|
return error_msg, image, "μ€λ₯κ° λ°μνμ΅λλ€." |
|
|
|
|
|
|
|
|
submit_btn.click( |
|
|
fn=generate_output, |
|
|
inputs=[ |
|
|
image_input, |
|
|
background_tabs, |
|
|
simple_dropdown, |
|
|
studio_dropdown, |
|
|
nature_dropdown, |
|
|
indoor_dropdown, |
|
|
abstract_dropdown, |
|
|
product_name, |
|
|
additional_info |
|
|
], |
|
|
outputs=[ |
|
|
prompt_output, |
|
|
image_preview, |
|
|
preview_html |
|
|
] |
|
|
) |
|
|
|
|
|
return demo |
|
|
|
|
|
if __name__ == "__main__": |
|
|
app = create_app() |
|
|
app.launch() |