Kims12 commited on
Commit
ad645a2
ยท
verified ยท
1 Parent(s): f4cdffe

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +342 -367
app.py CHANGED
@@ -1,182 +1,38 @@
1
  import os
2
  import tempfile
3
- from PIL import Image
4
- import gradio as gr
5
  import logging
6
  import re
7
  import time
8
  import json
9
-
 
10
  from google import genai
11
  from google.genai import types
 
12
  from dotenv import load_dotenv
13
 
14
  load_dotenv()
15
 
16
- # Logging setup
17
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
18
  logger = logging.getLogger(__name__)
19
 
20
- # Gemini API ํ‚ค ์„ค์ •
21
- GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
22
-
23
- # Gemini API ์ดˆ๊ธฐํ™”
24
- genai_client = genai.Client(api_key=GEMINI_API_KEY)
25
-
26
- # ๋ฐฐ๊ฒฝ JSON ํŒŒ์ผ ๊ฒฝ๋กœ ์„ค์ • - ์ƒ๋Œ€ ๊ฒฝ๋กœ ์‚ฌ์šฉ
27
- BACKGROUNDS_DIR = "./background"
28
-
29
- # ๋””๋ฒ„๊น…์„ ์œ„ํ•œ ์ •๋ณด ์ถœ๋ ฅ
30
- print(f"ํ˜„์žฌ ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ: {os.getcwd()}")
31
- print(f"์‚ฌ์šฉ ์ค‘์ธ ๋ฐฐ๊ฒฝ ๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ: {BACKGROUNDS_DIR}")
32
-
33
- # JSON ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ
34
- if not os.path.exists(BACKGROUNDS_DIR):
35
- os.makedirs(BACKGROUNDS_DIR)
36
- print(f"๋ฐฐ๊ฒฝ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค: {BACKGROUNDS_DIR}")
37
- else:
38
- print(f"๋ฐฐ๊ฒฝ ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค: {BACKGROUNDS_DIR}")
39
- try:
40
- for file in os.listdir(BACKGROUNDS_DIR):
41
- print(f"๋ฐœ๊ฒฌ๋œ ํŒŒ์ผ: {file}")
42
- except Exception as e:
43
- print(f"๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์šฉ์„ ๋‚˜์—ดํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}")
44
-
45
- # JSON ํŒŒ์ผ ๋กœ๋“œ ํ•จ์ˆ˜
46
- def load_background_json(filename):
47
- file_path = os.path.join(BACKGROUNDS_DIR, filename)
48
- try:
49
- with open(file_path, 'r', encoding='utf-8') as f:
50
- data = json.load(f)
51
- print(f"{filename} ํŒŒ์ผ์„ ์„ฑ๊ณต์ ์œผ๋กœ ๋กœ๋“œํ–ˆ์Šต๋‹ˆ๋‹ค. {len(data)} ํ•ญ๋ชฉ ํฌํ•จ.")
52
- return data
53
- except FileNotFoundError:
54
- print(f"๊ฒฝ๊ณ : {filename} ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
55
- return {}
56
- except json.JSONDecodeError:
57
- print(f"๊ฒฝ๊ณ : {filename} ํŒŒ์ผ์˜ JSON ํ˜•์‹์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
58
- return {}
59
- except Exception as e:
60
- print(f"๊ฒฝ๊ณ : {filename} ํŒŒ์ผ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}. ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
61
- return {}
62
-
63
- # ๋ฐฐ๊ฒฝ ๋ฐ์ดํ„ฐ ๋กœ๋“œ
64
- SIMPLE_BACKGROUNDS = load_background_json("simple_backgrounds.json")
65
- STUDIO_BACKGROUNDS = load_background_json("studio_backgrounds.json")
66
- NATURE_BACKGROUNDS = load_background_json("nature_backgrounds.json")
67
- INDOOR_BACKGROUNDS = load_background_json("indoor_backgrounds.json")
68
- ABSTRACT_BACKGROUNDS = load_background_json("abstract_backgrounds.json")
69
-
70
- # ๋ฐฐ๊ฒฝ์ด ๋กœ๋“œ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •
71
- if not SIMPLE_BACKGROUNDS:
72
- SIMPLE_BACKGROUNDS = {"ํ™”์ดํŠธ ๋ฐฐ๊ฒฝ": "white background"}
73
- if not STUDIO_BACKGROUNDS:
74
- STUDIO_BACKGROUNDS = {"์ œํ’ˆ ์‚ฌ์ง„ ์ŠคํŠœ๋””์˜ค": "product photography studio"}
75
- if not NATURE_BACKGROUNDS:
76
- NATURE_BACKGROUNDS = {"์—ด๋Œ€ ํ•ด๋ณ€": "tropical beach"}
77
- if not INDOOR_BACKGROUNDS:
78
- INDOOR_BACKGROUNDS = {"๋ชจ๋˜ ๋ฆฌ๋น™๋ฃธ": "modern living room"}
79
- if not ABSTRACT_BACKGROUNDS:
80
- ABSTRACT_BACKGROUNDS = {"๋„ค์˜จ ์กฐ๋ช…": "neon lights"}
81
-
82
  def save_binary_file(file_name, data):
83
  with open(file_name, "wb") as f:
84
  f.write(data)
85
 
86
- def generate_system_instruction():
87
- return """๋‹น์‹ ์€ ์ƒํ’ˆ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ์„ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•œ ๊ณ ํ’ˆ์งˆ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.
88
- ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ƒํ’ˆ๋ช…, ๋ฐฐ๊ฒฝ ์œ ํ˜•, ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ์„ ๋ฐ”ํƒ•์œผ๋กœ ๋ฏธ๋“œ์ €๋‹ˆ(Midjourney)์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”
89
- ์ƒ์„ธํ•˜๊ณ  ์ „๋ฌธ์ ์ธ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์˜์–ด๋กœ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”.
90
- ๋‹ค์Œ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ๋ฐ˜๋“œ์‹œ ๋”ฐ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค:
91
- 1. ์ƒํ’ˆ์„ "#1"๋กœ ์ง€์ •ํ•˜์—ฌ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: "skincare tube (#1)")
92
- 2. *** ๋งค์šฐ ์ค‘์š”: ์ƒํ’ˆ์˜ ์›๋ž˜ ํŠน์„ฑ(๋””์ž์ธ, ์ƒ‰์ƒ, ํ˜•ํƒœ, ๋กœ๊ณ , ํŒจํ‚ค์ง€ ๋“ฑ)์€ ์–ด๋–ค ์ƒํ™ฉ์—์„œ๋„ ์ ˆ๋Œ€ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ***
93
- 3. *** ์ƒํ’ˆ์˜ ๋ณธ์งˆ์  ํŠน์„ฑ์€ ์œ ์ง€ํ•˜๋˜, ์ž์—ฐ์Šค๋Ÿฌ์šด ํ™˜๊ฒฝ ํ†ตํ•ฉ์„ ์œ„ํ•œ ์กฐ๋ช…๊ณผ ๊ทธ๋ฆผ์ž๋Š” ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค: ***
94
- - ์ƒํ’ˆ ์ž์ฒด์˜ ์ƒ‰์ƒ, ๋””์ž์ธ, ํ˜•ํƒœ, ํ…์Šค์ฒ˜๋Š” ์ ˆ๋Œ€ ์ˆ˜์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
95
- - ํ™˜๊ฒฝ๊ณผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์–ด์šธ๋ฆฌ๋Š” ๊ทธ๋ฆผ์ž, ์ฃผ๋ณ€ ์กฐ๋ช… ํšจ๊ณผ๋Š” ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค.
96
- - ์ƒํ’ˆ์— ๋ฌผ๋ฐฉ์šธ, ์‘์ถ•, ๊ธˆ, ์€๊ณผ ๊ฐ™์€ ์ถ”๊ฐ€ ์š”์†Œ๋‚˜ ๋ฌผ๋ฆฌ์  ํšจ๊ณผ๋Š” ์ ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
97
- - ํ™˜๊ฒฝ์— ์–ด์šธ๋ฆฌ๋Š” ์ž์—ฐ์Šค๋Ÿฌ์šด ๋น› ๋ฐ˜์‚ฌ, ์ฃผ๋ณ€ ์กฐ๋ช…, ๊ทธ๋ฆผ์ž๋Š” ์‚ฌ์‹ค์  ํ†ตํ•ฉ๊ฐ์„ ์œ„ํ•ด ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
98
- 4. ์ด๋ฏธ์ง€ ๋น„์œจ์€ ์ •ํ™•ํžˆ 1:1(์ •์‚ฌ๊ฐํ˜•) ํ˜•์‹์œผ๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กฌํ”„ํŠธ์— "square format", "1:1 ratio" ๋˜๋Š” "aspect ratio 1:1"์„ ๋ช…์‹œ์ ์œผ๋กœ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
99
- 5. ์ƒํ’ˆ์€ ๋ฐ˜๋“œ์‹œ ์ •์‚ฌ๊ฐํ˜• ๊ตฌ๋„์˜ ์ •์ค‘์•™์— ๋ฐฐ์น˜๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
100
- 6. ์ƒํ’ˆ์„ ์ด๋ฏธ์ง€์˜ ์ฃผ์š” ์ดˆ์ ์œผ๋กœ ๋ถ€๊ฐ์‹œํ‚ค๊ณ , ์ƒํ’ˆ์˜ ๋น„์œจ์ด ์ „์ฒด ์ด๋ฏธ์ง€์—์„œ ํฌ๊ฒŒ ์ฐจ์ง€ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
101
- 7. ์ƒํ’ˆ ์ด๋ฏธ์ง€ ์ปท์•„์›ƒ(#1)์˜ ๊ธฐ๋ณธ ํ˜•ํƒœ์™€ ์ƒ‰์ƒ์€ ์œ ์ง€ํ•˜๋ฉด์„œ, ์„ ํƒํ•œ ํ™˜๊ฒฝ์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ†ตํ•ฉ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
102
- 8. ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ์ƒ์—…์  ์ด๋ฏธ์ง€๋ฅผ ์œ„ํ•œ ๋‹ค์Œ ํ™˜๊ฒฝ ์š”์†Œ๋“ค์„ ํฌํ•จํ•˜์„ธ์š”:
103
- - ์ƒํ’ˆ๊ณผ ์–ด์šธ๋ฆฌ๋Š” ์ฃผ๋ณ€ ํ™˜๊ฒฝ/๋ฐฐ๊ฒฝ ์š”์†Œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ™”์žฅํ’ˆ ์ฃผ๋ณ€์— ๊ฝƒ์ด๋‚˜ ํ—ˆ๋ธŒ, ์Œ๋ฃŒ ์ œํ’ˆ ์˜†์— ๊ณผ์ผ, ์ „์ž์ œํ’ˆ ๊ทผ์ฒ˜์— ํ˜„๋Œ€์  ์†Œํ’ˆ ๋“ฑ.
104
- - ํ™˜๊ฒฝ์˜ ์กฐ๋ช… ํšจ๊ณผ(๋ฆผ ๋ผ์ดํŠธ, ๋ฐฑ๋ผ์ดํŠธ, ์†Œํ”„ํŠธ๋ฐ•์Šค ๋“ฑ)๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.
105
- - ์ƒํ’ˆ์ด ํ™˜๊ฒฝ์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์กด์žฌํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๋„๋ก ์ ์ ˆํ•œ ๊ทธ๋ฆผ์ž์™€ ๋น› ํ‘œํ˜„์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
106
- - ์ƒํ’ˆ์˜ ์šฉ๋„๋‚˜ ์žฅ์ ์„ ๊ฐ„์ ‘์ ์œผ๋กœ ์•”์‹œํ•˜๋Š” ๋ฐฐ๊ฒฝ ์š”์†Œ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
107
- - ํ”„๋กœํŽ˜์…”๋„ํ•œ ์ƒ์—… ์‚ฌ์ง„ ํšจ๊ณผ(์„ ํƒ์  ํ”ผ์‚ฌ๊ณ„ ์‹ฌ๋„, ์†Œํ”„ํŠธ ํฌ์ปค์Šค, ์ŠคํŠœ๋””์˜ค ์กฐ๋ช… ๋“ฑ)๋ฅผ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค.
108
- 9. ํ”„๋กฌํ”„ํŠธ์— ๋‹ค์Œ ์š”์†Œ๋“ค์„ ๋ช…์‹œ์ ์œผ๋กœ ํฌํ•จํ•˜์„ธ์š”:
109
- - "highly detailed commercial photography"
110
- - "award-winning product photography"
111
- - "professional advertising imagery"
112
- - "studio quality"
113
- - "magazine advertisement quality"
114
- 10. ๋ฐฐ๊ฒฝ ํ™˜๊ฒฝ ์š”์†Œ๋ฅผ ์ƒํ’ˆ ์นดํ…Œ๊ณ ๋ฆฌ์— ๋งž๊ฒŒ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค:
115
- - ์Šคํ‚จ์ผ€์–ด ์ œํ’ˆ: ๊นจ๋—ํ•œ ์š•์‹ค ์„ ๋ฐ˜, ์šฐ์•„ํ•œ ํ™”์žฅ๋Œ€, ์ŠคํŒŒ ๊ฐ™์€ ํ™˜๊ฒฝ ๋“ฑ
116
- - ์Œ๋ฃŒ ์ œํ’ˆ: ์„ธ๋ จ๋œ ํ…Œ์ด๋ธ”, ํŒŒํ‹ฐ ํ™˜๊ฒฝ, ์•ผ์™ธ ํ”ผํฌ๋‹‰ ์žฅ๋ฉด ๋“ฑ
117
- - ์ „์ž ์ œํ’ˆ: ์„ธ๋ จ๋œ ์ž‘์—… ๊ณต๊ฐ„, ํ˜„๋Œ€์ ์ธ ๊ฑฐ์‹ค, ๋ฏธ๋‹ˆ๋ฉ€ํ•œ ์ฑ…์ƒ ๋“ฑ
118
- - ํŒจ์…˜/์˜๋ฅ˜: ์„ธ๋ จ๋œ ์‡ผ๋ฃธ, ๋„์‹œ ๊ฑฐ๋ฆฌ, ์—˜๋ ˆ๊ฐ•์Šคํ•œ ๋ผ์ดํ”„์Šคํƒ€์ผ ํ™˜๊ฒฝ ๋“ฑ
119
- - ์‹ํ’ˆ ์ œํ’ˆ: ๊น”๋”ํ•œ ์ฃผ๋ฐฉ, ์‹ํƒ, ์š”๋ฆฌ ํ™˜๊ฒฝ ๋“ฑ
120
- 11. ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ ๊ตฌ์ฒด์ ์ธ ๋ฐฐ๊ฒฝ๊ณผ ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ์„ ์ •ํ™•ํžˆ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.
121
- 12. ํ”„๋กฌํ”„ํŠธ๋Š” ๋ฏธ๋“œ์ €๋‹ˆ AI์— ์ตœ์ ํ™”๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
122
- 13. ํ”„๋กฌํ”„ํŠธ ๋์— "--ar 1:1 --s 750 --q 2" ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฏธ๋“œ์ €๋‹ˆ์—์„œ ๊ณ ํ’ˆ์งˆ ์ •์‚ฌ๊ฐํ˜• ๋น„์œจ์„ ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค.
123
- ์ถœ๋ ฅ ํ˜•์‹์€ ์˜์–ด๋กœ ๋œ ๋‹จ์ผ ๋‹จ๋ฝ์˜ ์ƒ์„ธํ•œ ํ”„๋กฌํ”„ํŠธ์—ฌ์•ผ ํ•˜๋ฉฐ, ๋์— ๋ฏธ๋“œ์ €๋‹ˆ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
124
- """
125
-
126
- def generate_prompt_with_gemini(product_name, background_info, additional_info=""):
127
- if not GEMINI_API_KEY:
128
- return "Gemini API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ GEMINI_API_KEY๋ฅผ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ ์ฝ”๋“œ์— ์ง์ ‘ ์ž…๋ ฅํ•˜์„ธ์š”."
129
- try:
130
- prompt_request = f"""
131
- ์ƒํ’ˆ๋ช…: {product_name}
132
- ๋ฐฐ๊ฒฝ ์œ ํ˜•: {background_info.get('english', 'studio')}
133
- ๋ฐฐ๊ฒฝ ์นดํ…Œ๊ณ ๋ฆฌ: {background_info.get('category', '')}
134
- ๋ฐฐ๊ฒฝ ์ด๋ฆ„: {background_info.get('name', '')}
135
- ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ: {additional_info}
136
- ์ค‘์š” ์š”๊ตฌ์‚ฌํ•ญ:
137
- 1. ์ƒํ’ˆ์ด ํฌ๊ฒŒ ๋ถ€๊ฐ๋˜๊ณ  ์ด๋ฏธ์ง€์—์„œ ์ค‘์‹ฌ์ ์ธ ์œ„์น˜๋ฅผ ์ฐจ์ง€ํ•˜๋„๋ก ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”.
138
- 2. ์ด๋ฏธ์ง€๋Š” ์ •ํ™•ํžˆ 1:1 ๋น„์œจ(์ •์‚ฌ๊ฐํ˜•)์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
139
- 3. ์ƒํ’ˆ์€ ์ •์‚ฌ๊ฐํ˜• ํ”„๋ ˆ์ž„์˜ ์ •์ค‘์•™์— ์œ„์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
140
- 4. ์ƒํ’ˆ์˜ ๋””์ž์ธ, ์ƒ‰์ƒ, ํ˜•ํƒœ, ๋กœ๊ณ  ๋“ฑ ๋ณธ์งˆ์  ํŠน์„ฑ์€ ์ ˆ๋Œ€ ์ˆ˜์ •ํ•˜์ง€ ๋งˆ์„ธ์š”.
141
- 5. ํ™˜๊ฒฝ๊ณผ์˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ํ†ตํ•ฉ์„ ์œ„ํ•œ ์กฐ๋ช… ํšจ๊ณผ์™€ ๊ทธ๋ฆผ์ž๋Š” ํฌํ•จํ•ด์ฃผ์„ธ์š”.
142
- 6. ์ƒํ’ˆ์„ ๋” ๋‹๋ณด์ด๊ฒŒ ํ•˜๋Š” ๋ฐฐ๊ฒฝ ํ™˜๊ฒฝ์„ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.
143
- 7. ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ์ƒ์—… ๊ด‘๊ณ  ํ’ˆ์งˆ์˜ ์ด๋ฏธ์ง€๊ฐ€ ๋˜๋„๋ก ํ™˜๊ฒฝ ์„ค๋ช…์„ ํ•ด์ฃผ์„ธ์š”.
144
- 8. ํ”„๋กฌํ”„ํŠธ ๋์— ๋ฏธ๋“œ์ €๋‹ˆ ํŒŒ๋ผ๋ฏธํ„ฐ "--ar 1:1 --s 750 --q 2"๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”.
145
- ํ•œ๊ตญ์–ด ์ž…๋ ฅ ๋‚ด์šฉ์„ ์˜์–ด๋กœ ์ ์ ˆํžˆ ๋ฒˆ์—ญํ•˜์—ฌ ๋ฐ˜์˜ํ•ด์ฃผ์„ธ์š”.
146
- """
147
- response = genai_client.models.generate_content(
148
- model="gemini-2.0-flash",
149
- contents=[
150
- {
151
- "role": "user",
152
- "parts": [
153
- {"text": generate_system_instruction()},
154
- {"text": prompt_request}
155
- ]
156
- }
157
- ],
158
- generation_config=types.GenerationConfig(
159
- temperature=0.7,
160
- top_p=0.95,
161
- top_k=64,
162
- max_output_tokens=1024,
163
- )
164
- )
165
-
166
- response_text = response.candidates[0].content.parts[0].text.strip()
167
- if "--ar 1:1" not in response_text:
168
- response_text = response_text.rstrip(".") + ". --ar 1:1 --s 750 --q 2"
169
- return response_text
170
- except Exception as e:
171
- return f"ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
172
-
173
  def translate_prompt_to_english(prompt):
174
  if not re.search("[๊ฐ€-ํžฃ]", prompt):
175
  return prompt
176
-
177
  prompt = prompt.replace("#1", "IMAGE_TAG_ONE")
178
-
179
  try:
 
 
 
 
 
 
180
  translation_prompt = f"""
181
  Translate the following Korean text to English:
182
 
@@ -185,21 +41,22 @@ def translate_prompt_to_english(prompt):
185
  IMPORTANT: The token IMAGE_TAG_ONE is a special tag
186
  and must be preserved exactly as is in your translation. Do not translate this token.
187
  """
188
-
189
  logger.info(f"Translation prompt: {translation_prompt}")
190
- response = genai_client.models.generate_content(
191
  model="gemini-2.0-flash",
192
  contents=[translation_prompt],
193
- generation_config=types.GenerationConfig(
 
194
  temperature=0.2,
195
  top_p=0.95,
196
  top_k=40,
197
  max_output_tokens=512
198
  )
199
  )
200
-
201
- translated_text = response.candidates[0].content.parts[0].text
202
-
 
203
  if translated_text.strip():
204
  translated_text = translated_text.replace("IMAGE_TAG_ONE", "#1")
205
  logger.info(f"Translated text: {translated_text.strip()}")
@@ -213,49 +70,52 @@ def translate_prompt_to_english(prompt):
213
  prompt = prompt.replace("IMAGE_TAG_ONE", "#1")
214
  return prompt
215
 
 
 
 
 
 
 
 
 
 
216
  def generate_with_images(prompt, images, variation_index=0):
217
  try:
218
- if not GEMINI_API_KEY:
 
219
  return None, "API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”."
220
-
221
  logger.info(f"Gemini API ์š”์ฒญ ์‹œ์ž‘ - ํ”„๋กฌํ”„ํŠธ: {prompt}, ๋ณ€ํ˜• ์ธ๋ฑ์Šค: {variation_index}")
222
-
223
  variation_suffixes = [
224
  " Create this as the first variation. Do not add any text, watermarks, or labels to the image.",
225
  " Create this as the second variation with more vivid colors. Do not add any text, watermarks, or labels to the image.",
226
  " Create this as the third variation with a more creative style. Do not add any text, watermarks, or labels to the image.",
227
  " Create this as the fourth variation with enhanced details. Do not add any text, watermarks, or labels to the image."
228
  ]
229
-
230
  if variation_index < len(variation_suffixes):
231
  prompt = prompt + variation_suffixes[variation_index]
232
  else:
233
  prompt = prompt + " Do not add any text, watermarks, or labels to the image."
234
-
235
  contents = [prompt]
236
  for idx, img in enumerate(images, 1):
237
  if img is not None:
238
- # PIL ์ด๋ฏธ์ง€๋ฅผ ํŒŒํŠธ๋กœ ๋ณ€ํ™˜
239
  contents.append(img)
240
  logger.info(f"์ด๋ฏธ์ง€ #{idx} ์ถ”๊ฐ€๋จ")
241
-
242
- response = genai_client.models.generate_content(
243
  model="gemini-2.0-flash-exp-image-generation",
244
  contents=contents,
245
- generation_config=types.GenerationConfig(
 
246
  temperature=1,
247
  top_p=0.95,
248
  top_k=40,
249
  max_output_tokens=8192
250
  )
251
  )
252
-
253
- # ์ž„์‹œ ํŒŒ์ผ์€ ํ•ญ์ƒ JPG ํ™•์žฅ์ž๋กœ ์ƒ์„ฑ
254
  with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
255
  temp_path = tmp.name
256
  result_text = ""
257
  image_found = False
258
-
259
  for part in response.candidates[0].content.parts:
260
  if hasattr(part, 'text') and part.text:
261
  result_text += part.text
@@ -264,18 +124,12 @@ def generate_with_images(prompt, images, variation_index=0):
264
  save_binary_file(temp_path, part.inline_data.data)
265
  image_found = True
266
  logger.info("์‘๋‹ต์—์„œ ์ด๋ฏธ์ง€ ์ถ”์ถœ ์„ฑ๊ณต")
267
-
268
  if not image_found:
269
  return None, f"API์—์„œ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ์‘๋‹ต ํ…์ŠคํŠธ: {result_text}"
270
-
271
  result_img = Image.open(temp_path)
272
  if result_img.mode == "RGBA":
273
- result_img = result_img.convert("RGB") # JPG๋Š” ํˆฌ๋ช…๋„๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ RGB๋กœ ๋ณ€ํ™˜
274
-
275
- # ๋ณ€ํ™˜๋œ ์ด๋ฏธ์ง€๋ฅผ JPG๋กœ ์ €์žฅ
276
  result_img.save(temp_path, format="JPEG", quality=95)
277
-
278
- # ํŒŒ์ผ ๊ฒฝ๋กœ ๋ฐ˜ํ™˜
279
  return temp_path, f"์ด๋ฏธ์ง€๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. {result_text}"
280
  except Exception as e:
281
  logger.exception("์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
@@ -284,21 +138,21 @@ def generate_with_images(prompt, images, variation_index=0):
284
  def process_images_with_prompt(image1, prompt, variation_index=0, max_retries=3):
285
  retry_count = 0
286
  last_error = None
287
-
288
  while retry_count < max_retries:
289
  try:
290
  images = [image1]
291
  valid_images = [img for img in images if img is not None]
292
  if not valid_images:
293
  return None, "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.", ""
294
-
295
  if prompt and prompt.strip():
296
- # ์ด๋ฏธ ์˜์–ด๋กœ ๋œ ํ”„๋กฌํ”„ํŠธ๋ผ๋ฉด ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ
297
- final_prompt = prompt
 
 
 
298
  else:
299
  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."
300
  logger.info("Default prompt generated for single image")
301
-
302
  result_img, status = generate_with_images(final_prompt, valid_images, variation_index)
303
  if result_img is not None:
304
  return result_img, status, final_prompt
@@ -312,23 +166,18 @@ def process_images_with_prompt(image1, prompt, variation_index=0, max_retries=3)
312
  retry_count += 1
313
  logger.exception(f"์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ, ์žฌ์‹œ๋„ {retry_count}/{max_retries}:")
314
  time.sleep(1)
315
-
316
  return None, f"์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜({max_retries}ํšŒ) ์ดˆ๊ณผ ํ›„ ์‹คํŒจ: {last_error}", prompt
317
 
318
  def generate_multiple_images(image1, prompt, progress=gr.Progress()):
319
  results = []
320
  statuses = []
321
  prompts = []
322
-
323
  num_images = 4
324
  max_retries = 3
325
-
326
  progress(0, desc="์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค€๋น„ ์ค‘...")
327
-
328
  for i in range(num_images):
329
  progress((i / num_images), desc=f"{i+1}/{num_images} ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘...")
330
  result_img, status, final_prompt = process_images_with_prompt(image1, prompt, i, max_retries)
331
-
332
  if result_img is not None:
333
  results.append(result_img)
334
  statuses.append(f"์ด๋ฏธ์ง€ #{i+1}: {status}")
@@ -337,20 +186,136 @@ def generate_multiple_images(image1, prompt, progress=gr.Progress()):
337
  results.append(None)
338
  statuses.append(f"์ด๋ฏธ์ง€ #{i+1} ์ƒ์„ฑ ์‹คํŒจ: {status}")
339
  prompts.append(f"์ด๋ฏธ์ง€ #{i+1}: {final_prompt}")
340
-
341
  time.sleep(1)
342
-
343
  progress(1.0, desc="์ด๋ฏธ์ง€ ์ƒ์„ฑ ์™„๋ฃŒ!")
344
-
345
  while len(results) < 4:
346
  results.append(None)
347
-
348
  combined_status = "\n".join(statuses)
349
  combined_prompts = "\n".join(prompts)
350
-
351
  return results[0], results[1], results[2], results[3], combined_status, combined_prompts
352
 
353
- # ์„ ํƒ๋œ ๋ฐฐ๊ฒฝ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  def get_selected_background_info(bg_type, simple, studio, nature, indoor, abstract):
355
  if bg_type == "์‹ฌํ”Œ ๋ฐฐ๊ฒฝ":
356
  return {
@@ -389,175 +354,185 @@ def get_selected_background_info(bg_type, simple, studio, nature, indoor, abstra
389
  "english": "white background"
390
  }
391
 
392
- # UI ๊ตฌ์„ฑ
393
- with gr.Blocks() as demo:
394
- with gr.Column():
395
- with gr.Group():
396
- with gr.Column():
397
- with gr.Row():
398
- image1_input = gr.Image(type="pil", label="์ด๋ฏธ์ง€ ์—…๋กœ๋“œ", image_mode="RGB")
399
-
400
- # ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ ์˜์—ญ
401
- with gr.Row():
402
- with gr.Column():
403
- # ์ƒํ’ˆ๋ช… ์ž…๋ ฅ
404
- product_name = gr.Textbox(label="์ƒํ’ˆ๋ช…", placeholder="์˜ˆ: ์Šคํ‚จ์ผ€์–ด ํŠœ๋ธŒ, ํ…€๋ธ”๋Ÿฌ ๋“ฑ", interactive=True)
405
-
406
- # ๋ฐฐ๊ฒฝ ์œ ํ˜• ์„ ํƒ ๋ผ๋””์˜ค ๋ฒ„ํŠผ
407
- background_type = gr.Radio(
408
- choices=["์‹ฌํ”Œ ๋ฐฐ๊ฒฝ", "์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ", "์ž์—ฐ ํ™˜๊ฒฝ", "์‹ค๋‚ด ํ™˜๊ฒฝ", "์ถ”์ƒ/ํŠน์ˆ˜ ๋ฐฐ๊ฒฝ"],
409
- label="๋ฐฐ๊ฒฝ ์œ ํ˜•",
410
- value="์‹ฌํ”Œ ๋ฐฐ๊ฒฝ"
411
- )
412
-
413
- # ๊ฐ ๋ฐฐ๊ฒฝ ์œ ํ˜•๋ณ„ ๋“œ๋กญ๋‹ค์šด
414
- with gr.Row():
415
- # ์‹ฌํ”Œ ๋ฐฐ๊ฒฝ ์„ ํƒ ๋“œ๋กญ๋‹ค์šด
416
- simple_dropdown = gr.Dropdown(
417
- choices=list(SIMPLE_BACKGROUNDS.keys()),
418
- value=list(SIMPLE_BACKGROUNDS.keys())[0] if SIMPLE_BACKGROUNDS else None,
419
- label="์‹ฌํ”Œ ๋ฐฐ๊ฒฝ ์„ ํƒ",
420
- visible=True,
421
- interactive=True
422
- )
423
-
424
- # ์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ ์„ ํƒ ๋“œ๋กญ๋‹ค์šด
425
- studio_dropdown = gr.Dropdown(
426
- choices=list(STUDIO_BACKGROUNDS.keys()),
427
- value=list(STUDIO_BACKGROUNDS.keys())[0] if STUDIO_BACKGROUNDS else None,
428
- label="์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ ์„ ํƒ",
429
- visible=False,
430
- interactive=True
431
- )
432
-
433
- # ์ž์—ฐ ํ™˜๊ฒฝ ์„ ํƒ ๋“œ๋กญ๋‹ค์šด
434
- nature_dropdown = gr.Dropdown(
435
- choices=list(NATURE_BACKGROUNDS.keys()),
436
- value=list(NATURE_BACKGROUNDS.keys())[0] if NATURE_BACKGROUNDS else None,
437
- label="์ž์—ฐ ํ™˜๊ฒฝ ์„ ํƒ",
438
- visible=False,
439
- interactive=True
440
- )
441
-
442
- # ์‹ค๋‚ด ํ™˜๊ฒฝ ์„ ํƒ ๋“œ๋กญ๋‹ค์šด
443
- indoor_dropdown = gr.Dropdown(
444
- choices=list(INDOOR_BACKGROUNDS.keys()),
445
- value=list(INDOOR_BACKGROUNDS.keys())[0] if INDOOR_BACKGROUNDS else None,
446
- label="์‹ค๋‚ด ํ™˜๊ฒฝ ์„ ํƒ",
447
- visible=False,
448
- interactive=True
449
- )
450
-
451
- # ์ถ”์ƒ/ํŠน์ˆ˜ ๋ฐฐ๊ฒฝ ์„ ํƒ ๋“œ๋กญ๋‹ค์šด
452
- abstract_dropdown = gr.Dropdown(
453
- choices=list(ABSTRACT_BACKGROUNDS.keys()),
454
- value=list(ABSTRACT_BACKGROUNDS.keys())[0] if ABSTRACT_BACKGROUNDS else None,
455
- label="์ถ”์ƒ/ํŠน์ˆ˜ ๋ฐฐ๊ฒฝ ์„ ํƒ",
456
- visible=False,
457
- interactive=True
458
- )
459
-
460
- # ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ
461
- additional_info = gr.Textbox(
462
- label="์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ (์„ ํƒ์‚ฌํ•ญ)",
463
- placeholder="์˜ˆ: ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ๋А๋‚Œ, ๋ฐ์€ ์กฐ๋ช…, ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ณด์กฐ์ ์ธ ๊ฐ์ฒด๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š” ๋“ฑ",
464
- lines=2,
465
- interactive=True
466
- )
467
 
468
- # ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ๋ฒ„ํŠผ
469
- generate_prompt_btn = gr.Button("ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ")
470
-
471
- with gr.Column():
472
- # ์ƒ์„ฑ๋œ ํ”„๋กฌํ”„ํŠธ ํ‘œ์‹œ
473
- prompt_output = gr.Textbox(label="์ƒ์„ฑ๋œ ํ”„๋กฌํ”„ํŠธ", lines=5)
474
-
475
- # ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฒ„ํŠผ
476
- with gr.Row():
477
- submit_single_btn = gr.Button('์ด๋ฏธ์ง€ ์ƒ์„ฑ (1์žฅ)')
478
- submit_btn = gr.Button('์ด๋ฏธ์ง€ ์ƒ์„ฑ (4์žฅ)')
479
-
480
- with gr.Column():
481
- with gr.Row():
482
- with gr.Column():
483
- output_image1 = gr.Image(label="์ด๋ฏธ์ง€ #1", type="filepath")
484
- output_image3 = gr.Image(label="์ด๋ฏธ์ง€ #3", type="filepath")
485
- with gr.Column():
486
- output_image2 = gr.Image(label="์ด๋ฏธ์ง€ #2", type="filepath")
487
- output_image4 = gr.Image(label="์ด๋ฏธ์ง€ #4", type="filepath")
488
-
489
- output_text = gr.Textbox(label="๊ฒฐ๊ณผ ์ •๋ณด", lines=2)
490
- prompt_display = gr.Textbox(label="์‚ฌ์šฉ๋œ ํ”„๋กฌํ”„ํŠธ (์˜์–ด)", lines=2)
491
-
492
- # ๋ฐฐ๊ฒฝ ์œ ํ˜•์— ๋”ฐ๋ผ ๋“œ๋กญ๋‹ค์šด ํ‘œ์‹œ ์—…๋ฐ์ดํŠธ
493
- def update_dropdowns(bg_type):
494
- return {
495
- simple_dropdown: gr.update(visible=(bg_type == "์‹ฌํ”Œ ๋ฐฐ๊ฒฝ")),
496
- studio_dropdown: gr.update(visible=(bg_type == "์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ")),
497
- nature_dropdown: gr.update(visible=(bg_type == "์ž์—ฐ ํ™˜๊ฒฝ")),
498
- indoor_dropdown: gr.update(visible=(bg_type == "์‹ค๋‚ด ํ™˜๊ฒฝ")),
499
- abstract_dropdown: gr.update(visible=(bg_type == "์ถ”์ƒ/ํŠน์ˆ˜ ๋ฐฐ๊ฒฝ"))
500
- }
501
-
502
- # ๋ฐฐ๊ฒฝ ์œ ํ˜• ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
503
- background_type.change(
504
- fn=update_dropdowns,
505
- inputs=[background_type],
506
- outputs=[simple_dropdown, studio_dropdown, nature_dropdown, indoor_dropdown, abstract_dropdown]
507
- )
508
-
509
-
510
- # ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ํ•จ์ˆ˜
511
- def generate_output(image, bg_type, simple, studio, nature, indoor, abstract, product_text, additional_text):
512
- if image is None:
513
- return "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”."
514
-
515
- product_text = product_text.strip() or "์ œํ’ˆ"
516
-
517
- # ๋ฐฐ๊ฒฝ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
518
- background_info = get_selected_background_info(bg_type, simple, studio, nature, indoor, abstract)
519
-
520
- try:
521
- prompt = generate_prompt_with_gemini(product_text, background_info, additional_text)
522
-
523
- if not GEMINI_API_KEY:
524
- return "Gemini API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ‚ค๋ฅผ ๋ฐœ๊ธ‰๋ฐ›์•„ ์„ค์ •ํ•ด์ฃผ์„ธ์š”."
525
-
526
- return prompt
527
- except Exception as e:
528
- error_msg = f"ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
529
- return error_msg
530
-
531
- # ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ๋ฒ„ํŠผ ํด๋ฆญ ์ด๋ฒคํŠธ
532
- generate_prompt_btn.click(
533
- fn=generate_output,
534
- inputs=[
535
- image1_input,
536
- background_type,
537
- simple_dropdown,
538
- studio_dropdown,
539
- nature_dropdown,
540
- indoor_dropdown,
541
- abstract_dropdown,
542
- product_name,
543
- additional_info
544
- ],
545
- outputs=prompt_output
546
- )
547
-
548
- # ๋‹จ์ผ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
549
- submit_single_btn.click(
550
- fn=lambda image1, prompt: process_images_with_prompt(image1, prompt, 0),
551
- inputs=[image1_input, prompt_output],
552
- outputs=[output_image1, output_text, prompt_display],
553
- )
554
-
555
- # 4์žฅ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
556
- submit_btn.click(
557
- fn=generate_multiple_images,
558
- inputs=[image1_input, prompt_output],
559
- outputs=[output_image1, output_image2, output_image3, output_image4, output_text, prompt_display],
560
- )
561
-
562
- demo.queue()
563
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import tempfile
 
 
3
  import logging
4
  import re
5
  import time
6
  import json
7
+ from PIL import Image
8
+ import gradio as gr
9
  from google import genai
10
  from google.genai import types
11
+ import google.generativeai as genai_generative
12
  from dotenv import load_dotenv
13
 
14
  load_dotenv()
15
 
16
+ # ------------------- ๋กœ๊น… ์„ค์ • -------------------
17
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
18
  logger = logging.getLogger(__name__)
19
 
20
+ # ------------------- ๊ธฐ๋ณธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๊ด€๋ จ ํ•จ์ˆ˜ (๊ธฐ๋ณธ์ฝ”๋“œ) -------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  def save_binary_file(file_name, data):
22
  with open(file_name, "wb") as f:
23
  f.write(data)
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  def translate_prompt_to_english(prompt):
26
  if not re.search("[๊ฐ€-ํžฃ]", prompt):
27
  return prompt
 
28
  prompt = prompt.replace("#1", "IMAGE_TAG_ONE")
 
29
  try:
30
+ api_key = os.environ.get("GEMINI_API_KEY")
31
+ if not api_key:
32
+ logger.error("Gemini API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
33
+ prompt = prompt.replace("IMAGE_TAG_ONE", "#1")
34
+ return prompt
35
+ client = genai.Client(api_key=api_key)
36
  translation_prompt = f"""
37
  Translate the following Korean text to English:
38
 
 
41
  IMPORTANT: The token IMAGE_TAG_ONE is a special tag
42
  and must be preserved exactly as is in your translation. Do not translate this token.
43
  """
 
44
  logger.info(f"Translation prompt: {translation_prompt}")
45
+ response = client.models.generate_content(
46
  model="gemini-2.0-flash",
47
  contents=[translation_prompt],
48
+ config=types.GenerateContentConfig(
49
+ response_modalities=['Text'],
50
  temperature=0.2,
51
  top_p=0.95,
52
  top_k=40,
53
  max_output_tokens=512
54
  )
55
  )
56
+ translated_text = ""
57
+ for part in response.candidates[0].content.parts:
58
+ if hasattr(part, 'text') and part.text:
59
+ translated_text += part.text
60
  if translated_text.strip():
61
  translated_text = translated_text.replace("IMAGE_TAG_ONE", "#1")
62
  logger.info(f"Translated text: {translated_text.strip()}")
 
70
  prompt = prompt.replace("IMAGE_TAG_ONE", "#1")
71
  return prompt
72
 
73
+ def preprocess_prompt(prompt, image1):
74
+ has_img1 = image1 is not None
75
+ if "#1" in prompt and not has_img1:
76
+ prompt = prompt.replace("#1", "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€(์—†์Œ)")
77
+ else:
78
+ prompt = prompt.replace("#1", "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€")
79
+ prompt += " ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”. ์ด๋ฏธ์ง€์— ํ…์ŠคํŠธ๋‚˜ ๊ธ€์ž๋ฅผ ํฌํ•จํ•˜์ง€ ๋งˆ์„ธ์š”."
80
+ return prompt
81
+
82
  def generate_with_images(prompt, images, variation_index=0):
83
  try:
84
+ api_key = os.environ.get("GEMINI_API_KEY")
85
+ if not api_key:
86
  return None, "API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”."
87
+ client = genai.Client(api_key=api_key)
88
  logger.info(f"Gemini API ์š”์ฒญ ์‹œ์ž‘ - ํ”„๋กฌํ”„ํŠธ: {prompt}, ๋ณ€ํ˜• ์ธ๋ฑ์Šค: {variation_index}")
 
89
  variation_suffixes = [
90
  " Create this as the first variation. Do not add any text, watermarks, or labels to the image.",
91
  " Create this as the second variation with more vivid colors. Do not add any text, watermarks, or labels to the image.",
92
  " Create this as the third variation with a more creative style. Do not add any text, watermarks, or labels to the image.",
93
  " Create this as the fourth variation with enhanced details. Do not add any text, watermarks, or labels to the image."
94
  ]
 
95
  if variation_index < len(variation_suffixes):
96
  prompt = prompt + variation_suffixes[variation_index]
97
  else:
98
  prompt = prompt + " Do not add any text, watermarks, or labels to the image."
 
99
  contents = [prompt]
100
  for idx, img in enumerate(images, 1):
101
  if img is not None:
 
102
  contents.append(img)
103
  logger.info(f"์ด๋ฏธ์ง€ #{idx} ์ถ”๊ฐ€๋จ")
104
+ response = client.models.generate_content(
 
105
  model="gemini-2.0-flash-exp-image-generation",
106
  contents=contents,
107
+ config=types.GenerateContentConfig(
108
+ response_modalities=['Text', 'Image'],
109
  temperature=1,
110
  top_p=0.95,
111
  top_k=40,
112
  max_output_tokens=8192
113
  )
114
  )
 
 
115
  with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
116
  temp_path = tmp.name
117
  result_text = ""
118
  image_found = False
 
119
  for part in response.candidates[0].content.parts:
120
  if hasattr(part, 'text') and part.text:
121
  result_text += part.text
 
124
  save_binary_file(temp_path, part.inline_data.data)
125
  image_found = True
126
  logger.info("์‘๋‹ต์—์„œ ์ด๋ฏธ์ง€ ์ถ”์ถœ ์„ฑ๊ณต")
 
127
  if not image_found:
128
  return None, f"API์—์„œ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. ์‘๋‹ต ํ…์ŠคํŠธ: {result_text}"
 
129
  result_img = Image.open(temp_path)
130
  if result_img.mode == "RGBA":
131
+ result_img = result_img.convert("RGB")
 
 
132
  result_img.save(temp_path, format="JPEG", quality=95)
 
 
133
  return temp_path, f"์ด๋ฏธ์ง€๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. {result_text}"
134
  except Exception as e:
135
  logger.exception("์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
 
138
  def process_images_with_prompt(image1, prompt, variation_index=0, max_retries=3):
139
  retry_count = 0
140
  last_error = None
 
141
  while retry_count < max_retries:
142
  try:
143
  images = [image1]
144
  valid_images = [img for img in images if img is not None]
145
  if not valid_images:
146
  return None, "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.", ""
 
147
  if prompt and prompt.strip():
148
+ processed_prompt = preprocess_prompt(prompt, image1)
149
+ if re.search("[๊ฐ€-ํžฃ]", processed_prompt):
150
+ final_prompt = translate_prompt_to_english(processed_prompt)
151
+ else:
152
+ final_prompt = processed_prompt
153
  else:
154
  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."
155
  logger.info("Default prompt generated for single image")
 
156
  result_img, status = generate_with_images(final_prompt, valid_images, variation_index)
157
  if result_img is not None:
158
  return result_img, status, final_prompt
 
166
  retry_count += 1
167
  logger.exception(f"์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ, ์žฌ์‹œ๋„ {retry_count}/{max_retries}:")
168
  time.sleep(1)
 
169
  return None, f"์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜({max_retries}ํšŒ) ์ดˆ๊ณผ ํ›„ ์‹คํŒจ: {last_error}", prompt
170
 
171
  def generate_multiple_images(image1, prompt, progress=gr.Progress()):
172
  results = []
173
  statuses = []
174
  prompts = []
 
175
  num_images = 4
176
  max_retries = 3
 
177
  progress(0, desc="์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค€๋น„ ์ค‘...")
 
178
  for i in range(num_images):
179
  progress((i / num_images), desc=f"{i+1}/{num_images} ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘...")
180
  result_img, status, final_prompt = process_images_with_prompt(image1, prompt, i, max_retries)
 
181
  if result_img is not None:
182
  results.append(result_img)
183
  statuses.append(f"์ด๋ฏธ์ง€ #{i+1}: {status}")
 
186
  results.append(None)
187
  statuses.append(f"์ด๋ฏธ์ง€ #{i+1} ์ƒ์„ฑ ์‹คํŒจ: {status}")
188
  prompts.append(f"์ด๋ฏธ์ง€ #{i+1}: {final_prompt}")
 
189
  time.sleep(1)
 
190
  progress(1.0, desc="์ด๋ฏธ์ง€ ์ƒ์„ฑ ์™„๋ฃŒ!")
 
191
  while len(results) < 4:
192
  results.append(None)
 
193
  combined_status = "\n".join(statuses)
194
  combined_prompts = "\n".join(prompts)
 
195
  return results[0], results[1], results[2], results[3], combined_status, combined_prompts
196
 
197
+ # ------------------- ์ƒํ’ˆ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ๊ด€๋ จ ํ•จ์ˆ˜ (์ฐธ์กฐ์ฝ”๋“œ) -------------------
198
+
199
+ # ๋ฐฐ๊ฒฝ JSON ํŒŒ์ผ ๊ฒฝ๋กœ ์„ค์ •
200
+ BACKGROUNDS_DIR = "./background"
201
+ if not os.path.exists(BACKGROUNDS_DIR):
202
+ os.makedirs(BACKGROUNDS_DIR)
203
+ logger.info(f"๋ฐฐ๊ฒฝ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค: {BACKGROUNDS_DIR}")
204
+ else:
205
+ logger.info(f"๋ฐฐ๊ฒฝ ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค: {BACKGROUNDS_DIR}")
206
+
207
+ def load_background_json(filename):
208
+ file_path = os.path.join(BACKGROUNDS_DIR, filename)
209
+ try:
210
+ with open(file_path, 'r', encoding='utf-8') as f:
211
+ data = json.load(f)
212
+ logger.info(f"{filename} ํŒŒ์ผ์„ ์„ฑ๊ณต์ ์œผ๋กœ ๋กœ๋“œํ–ˆ์Šต๋‹ˆ๋‹ค. {len(data)} ํ•ญ๋ชฉ ํฌํ•จ.")
213
+ return data
214
+ except Exception as e:
215
+ logger.warning(f"{filename} ํŒŒ์ผ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}. ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.")
216
+ return {}
217
+
218
+ SIMPLE_BACKGROUNDS = load_background_json("simple_backgrounds.json")
219
+ STUDIO_BACKGROUNDS = load_background_json("studio_backgrounds.json")
220
+ NATURE_BACKGROUNDS = load_background_json("nature_backgrounds.json")
221
+ INDOOR_BACKGROUNDS = load_background_json("indoor_backgrounds.json")
222
+ ABSTRACT_BACKGROUNDS = load_background_json("abstract_backgrounds.json")
223
+
224
+ # ๋ฐฐ๊ฒฝ์ด ๋กœ๋“œ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •
225
+ if not SIMPLE_BACKGROUNDS:
226
+ SIMPLE_BACKGROUNDS = {"ํ™”์ดํŠธ ๋ฐฐ๊ฒฝ": "white background"}
227
+ if not STUDIO_BACKGROUNDS:
228
+ STUDIO_BACKGROUNDS = {"์ œํ’ˆ ์‚ฌ์ง„ ์ŠคํŠœ๋””์˜ค": "product photography studio"}
229
+ if not NATURE_BACKGROUNDS:
230
+ NATURE_BACKGROUNDS = {"์—ด๋Œ€ ํ•ด๋ณ€": "tropical beach"}
231
+ if not INDOOR_BACKGROUNDS:
232
+ INDOOR_BACKGROUNDS = {"๋ชจ๋˜ ๋ฆฌ๋น™๋ฃธ": "modern living room"}
233
+ if not ABSTRACT_BACKGROUNDS:
234
+ ABSTRACT_BACKGROUNDS = {"๋„ค์˜จ ์กฐ๋ช…": "neon lights"}
235
+
236
+ def generate_system_instruction():
237
+ return """๋‹น์‹ ์€ ์ƒํ’ˆ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ์„ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•œ ๊ณ ํ’ˆ์งˆ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.
238
+ ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ƒํ’ˆ๋ช…, ๋ฐฐ๊ฒฝ ์œ ํ˜•, ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ์„ ๋ฐ”ํƒ•์œผ๋กœ ๋ฏธ๋“œ์ €๋‹ˆ(Midjourney)์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”
239
+ ์ƒ์„ธํ•˜๊ณ  ์ „๋ฌธ์ ์ธ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์˜์–ด๋กœ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”.
240
+ ๋‹ค์Œ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ๋ฐ˜๋“œ์‹œ ๋”ฐ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค:
241
+ 1. ์ƒํ’ˆ์„ "#1"๋กœ ์ง€์ •ํ•˜์—ฌ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: "skincare tube (#1)")
242
+ 2. *** ๋งค์šฐ ์ค‘์š”: ์ƒํ’ˆ์˜ ์›๋ž˜ ํŠน์„ฑ(๋””์ž์ธ, ์ƒ‰์ƒ, ํ˜•ํƒœ, ๋กœ๊ณ , ํŒจํ‚ค์ง€ ๋“ฑ)์€ ์–ด๋–ค ์ƒํ™ฉ์—์„œ๋„ ์ ˆ๋Œ€ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ***
243
+ 3. *** ์ƒํ’ˆ์˜ ๋ณธ์งˆ์  ํŠน์„ฑ์€ ์œ ์ง€ํ•˜๋˜, ์ž์—ฐ์Šค๋Ÿฌ์šด ํ™˜๊ฒฝ ํ†ตํ•ฉ์„ ์œ„ํ•œ ์กฐ๋ช…๊ณผ ๊ทธ๋ฆผ์ž๋Š” ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค: ***
244
+ - ์ƒํ’ˆ ์ž์ฒด์˜ ์ƒ‰์ƒ, ๋””์ž์ธ, ํ˜•ํƒœ, ํ…์Šค์ฒ˜๋Š” ์ ˆ๋Œ€ ์ˆ˜์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
245
+ - ํ™˜๊ฒฝ๊ณผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์–ด์šธ๋ฆฌ๋Š” ๊ทธ๋ฆผ์ž, ์ฃผ๋ณ€ ์กฐ๋ช… ํšจ๊ณผ๋Š” ํ—ˆ์šฉ๋ฉ๋‹ˆ๋‹ค.
246
+ - ์ƒํ’ˆ์— ๋ฌผ๋ฐฉ์šธ, ์‘์ถ•, ๊ธˆ, ์€๊ณผ ๊ฐ™์€ ์ถ”๊ฐ€ ์š”์†Œ๋‚˜ ๋ฌผ๋ฆฌ์  ํšจ๊ณผ๋Š” ์ ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
247
+ - ํ™˜๊ฒฝ์— ์–ด์šธ๋ฆฌ๋Š” ์ž์—ฐ์Šค๋Ÿฌ์šด ๋น› ๋ฐ˜์‚ฌ, ์ฃผ๋ณ€ ์กฐ๋ช…, ๊ทธ๋ฆผ์ž๋Š” ์‚ฌ์‹ค์  ํ†ตํ•ฉ๊ฐ์„ ์œ„ํ•ด ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
248
+ 4. ์ด๋ฏธ์ง€ ๋น„์œจ์€ ์ •ํ™•ํžˆ 1:1(์ •์‚ฌ๊ฐํ˜•) ํ˜•์‹์œผ๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กฌํ”„ํŠธ์— "square format", "1:1 ratio" ๋˜๋Š” "aspect ratio 1:1"์„ ๋ช…์‹œ์ ์œผ๋กœ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
249
+ 5. ์ƒํ’ˆ์€ ๋ฐ˜๋“œ์‹œ ์ •์‚ฌ๊ฐํ˜• ๊ตฌ๋„์˜ ์ •์ค‘์•™์— ๋ฐฐ์น˜๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
250
+ 6. ์ƒํ’ˆ์„ ์ด๋ฏธ์ง€์˜ ์ฃผ์š” ์ดˆ์ ์œผ๋กœ ๋ถ€๊ฐ์‹œํ‚ค๊ณ , ์ƒํ’ˆ์˜ ๋น„์œจ์ด ์ „์ฒด ์ด๋ฏธ์ง€์—์„œ ํฌ๊ฒŒ ์ฐจ์ง€ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
251
+ 7. ์ƒํ’ˆ ์ด๋ฏธ์ง€ ์ปท์•„์›ƒ(#1)์˜ ๊ธฐ๋ณธ ํ˜•ํƒœ์™€ ์ƒ‰์ƒ์€ ์œ ์ง€ํ•˜๋ฉด์„œ, ์„ ํƒํ•œ ํ™˜๊ฒฝ์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ†ตํ•ฉ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
252
+ 8. ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ์ƒ์—…์  ์ด๋ฏธ์ง€๋ฅผ ์œ„ํ•œ ๋‹ค์Œ ํ™˜๊ฒฝ ์š”์†Œ๋“ค์„ ํฌํ•จํ•˜์„ธ์š”:
253
+ - ์ƒํ’ˆ๊ณผ ์–ด์šธ๋ฆฌ๋Š” ์ฃผ๋ณ€ ํ™˜๊ฒฝ/๋ฐฐ๊ฒฝ ์š”์†Œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ™”์žฅํ’ˆ ์ฃผ๋ณ€์— ๊ฝƒ์ด๋‚˜ ํ—ˆ๋ธŒ, ์Œ๋ฃŒ ์ œํ’ˆ ์˜†์— ๊ณผ์ผ, ์ „์ž์ œํ’ˆ ๊ทผ์ฒ˜์— ํ˜„๋Œ€์  ์†Œํ’ˆ ๋“ฑ.
254
+ - ํ™˜๊ฒฝ์˜ ์กฐ๋ช… ํšจ๊ณผ(๋ฆผ ๋ผ์ดํŠธ, ๋ฐฑ๋ผ์ดํŠธ, ์†Œํ”„ํŠธ๋ฐ•์Šค ๋“ฑ)๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.
255
+ - ์ƒํ’ˆ์ด ํ™˜๊ฒฝ์— ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์กด์žฌํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๋„๋ก ์ ์ ˆํ•œ ๊ทธ๋ฆผ์ž์™€ ๋น› ํ‘œํ˜„์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
256
+ - ์ƒํ’ˆ์˜ ์šฉ๋„๋‚˜ ์žฅ์ ์„ ๊ฐ„์ ‘์ ์œผ๋กœ ์•”์‹œํ•˜๋Š” ๋ฐฐ๊ฒฝ ์š”์†Œ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
257
+ - ํ”„๋กœํŽ˜์…”๋„ํ•œ ์ƒ์—… ์‚ฌ์ง„ ํšจ๊ณผ(์„ ํƒ์  ํ”ผ์‚ฌ๊ณ„ ์‹ฌ๋„, ์†Œํ”„ํŠธ ํฌ์ปค์Šค, ์ŠคํŠœ๋””์˜ค ์กฐ๋ช… ๋“ฑ)๋ฅผ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค.
258
+ 9. ํ”„๋กฌํ”„ํŠธ์— ๋‹ค์Œ ์š”์†Œ๋“ค์„ ๋ช…์‹œ์ ์œผ๋กœ ํฌํ•จํ•˜์„ธ์š”:
259
+ - "highly detailed commercial photography"
260
+ - "award-winning product photography"
261
+ - "professional advertising imagery"
262
+ - "studio quality"
263
+ - "magazine advertisement quality"
264
+ 10. ๋ฐฐ๊ฒฝ ํ™˜๊ฒฝ ์š”์†Œ๋ฅผ ์ƒํ’ˆ ์นดํ…Œ๊ณ ๋ฆฌ์— ๋งž๊ฒŒ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค:
265
+ - ์Šคํ‚จ์ผ€์–ด ์ œํ’ˆ: ๊นจ๋—ํ•œ ์š•์‹ค ์„ ๋ฐ˜, ์šฐ์•„ํ•œ ํ™”์žฅ๋Œ€, ์ŠคํŒŒ ๊ฐ™์€ ํ™˜๊ฒฝ ๋“ฑ
266
+ - ์Œ๋ฃŒ ์ œํ’ˆ: ์„ธ๋ จ๋œ ํ…Œ์ด๋ธ”, ํŒŒํ‹ฐ ํ™˜๊ฒฝ, ์•ผ์™ธ ํ”ผํฌ๋‹‰ ์žฅ๋ฉด ๋“ฑ
267
+ - ์ „์ž ์ œํ’ˆ: ์„ธ๋ จ๋œ ์ž‘์—… ๊ณต๊ฐ„, ํ˜„๋Œ€์ ์ธ ๊ฑฐ์‹ค, ๋ฏธ๋‹ˆ๋ฉ€ํ•œ ์ฑ…์ƒ ๋“ฑ
268
+ - ํŒจ์…˜/์˜๋ฅ˜: ์„ธ๋ จ๋œ ์‡ผ๋ฃธ, ๋„์‹œ ๊ฑฐ๋ฆฌ, ์—˜๋ ˆ๊ฐ•์Šคํ•œ ๋ผ์ดํ”„์Šคํƒ€์ผ ํ™˜๊ฒฝ ๋“ฑ
269
+ - ์‹ํ’ˆ ์ œํ’ˆ: ๊น”๋”ํ•œ ์ฃผ๋ฐฉ, ์‹ํƒ, ์š”๋ฆฌ ํ™˜๊ฒฝ ๋“ฑ
270
+ 11. ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ ๊ตฌ์ฒด์ ์ธ ๋ฐฐ๊ฒฝ๊ณผ ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ์„ ์ •ํ™•ํžˆ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.
271
+ 12. ํ”„๋กฌํ”„ํŠธ๋Š” ๋ฏธ๋“œ์ €๋‹ˆ AI์— ์ตœ์ ํ™”๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
272
+ 13. ํ”„๋กฌํ”„ํŠธ ๋์— "--ar 1:1 --s 750 --q 2" ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฏธ๋“œ์ €๋‹ˆ์—์„œ ๊ณ ํ’ˆ์งˆ ์ •์‚ฌ๊ฐํ˜• ๋น„์œจ์„ ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค.
273
+ ์ถœ๋ ฅ ํ˜•์‹์€ ์˜์–ด๋กœ ๋œ ๋‹จ์ผ ๋‹จ๋ฝ์˜ ์ƒ์„ธํ•œ ํ”„๋กฌํ”„ํŠธ์—ฌ์•ผ ํ•˜๋ฉฐ, ๋์— ๋ฏธ๋“œ์ €๋‹ˆ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
274
+ """
275
+
276
+ def generate_prompt_with_gemini(product_name, background_info, additional_info=""):
277
+ GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
278
+ if not GEMINI_API_KEY:
279
+ return "Gemini API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ GEMINI_API_KEY๋ฅผ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ ์ฝ”๋“œ์— ์ง์ ‘ ์ž…๋ ฅํ•˜์„ธ์š”."
280
+ try:
281
+ genai_generative.configure(api_key=GEMINI_API_KEY)
282
+ prompt_request = f"""
283
+ ์ƒํ’ˆ๋ช…: {product_name}
284
+ ๋ฐฐ๊ฒฝ ์œ ํ˜•: {background_info.get('english', 'studio')}
285
+ ๋ฐฐ๊ฒฝ ์นดํ…Œ๊ณ ๋ฆฌ: {background_info.get('category', '')}
286
+ ๋ฐฐ๊ฒฝ ์ด๋ฆ„: {background_info.get('name', '')}
287
+ ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ: {additional_info}
288
+ ์ค‘์š” ์š”๊ตฌ์‚ฌํ•ญ:
289
+ 1. ์ƒํ’ˆ์ด ํฌ๊ฒŒ ๋ถ€๊ฐ๋˜๊ณ  ์ด๋ฏธ์ง€์—์„œ ์ค‘์‹ฌ์ ์ธ ์œ„์น˜๋ฅผ ์ฐจ์ง€ํ•˜๋„๋ก ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”.
290
+ 2. ์ด๋ฏธ์ง€๋Š” ์ •ํ™•ํžˆ 1:1 ๋น„์œจ(์ •์‚ฌ๊ฐํ˜•)์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
291
+ 3. ์ƒํ’ˆ์€ ์ •์‚ฌ๊ฐํ˜• ํ”„๋ ˆ์ž„์˜ ์ •์ค‘์•™์— ์œ„์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
292
+ 4. ์ƒํ’ˆ์˜ ๋””์ž์ธ, ์ƒ‰์ƒ, ํ˜•ํƒœ, ๋กœ๊ณ  ๋“ฑ ๋ณธ์งˆ์  ํŠน์„ฑ์€ ์ ˆ๋Œ€ ์ˆ˜์ •ํ•˜์ง€ ๋งˆ์„ธ์š”.
293
+ 5. ํ™˜๊ฒฝ๊ณผ์˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ํ†ตํ•ฉ์„ ์œ„ํ•œ ์กฐ๋ช… ํšจ๊ณผ์™€ ๊ทธ๋ฆผ์ž๋Š” ํฌํ•จํ•ด์ฃผ์„ธ์š”.
294
+ 6. ์ƒํ’ˆ์„ ๋” ๋‹๋ณด์ด๊ฒŒ ํ•˜๋Š” ๋ฐฐ๊ฒฝ ํ™˜๊ฒฝ์„ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.
295
+ 7. ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ์ƒ์—… ๊ด‘๊ณ  ํ’ˆ์งˆ์˜ ์ด๋ฏธ๏ฟฝ๏ฟฝ๏ฟฝ๊ฐ€ ๋˜๋„๋ก ํ™˜๊ฒฝ ์„ค๋ช…์„ ํ•ด์ฃผ์„ธ์š”.
296
+ 8. ํ”„๋กฌํ”„ํŠธ ๋์— ๋ฏธ๋“œ์ €๋‹ˆ ํŒŒ๋ผ๋ฏธํ„ฐ "--ar 1:1 --s 750 --q 2"๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”.
297
+ ํ•œ๊ตญ์–ด ์ž…๋ ฅ ๋‚ด์šฉ์„ ์˜์–ด๋กœ ์ ์ ˆํžˆ ๋ฒˆ์—ญํ•˜์—ฌ ๋ฐ˜์˜ํ•ด์ฃผ์„ธ์š”.
298
+ """
299
+ model = genai_generative.GenerativeModel(
300
+ 'gemini-2.0-flash',
301
+ system_instruction=generate_system_instruction()
302
+ )
303
+ response = model.generate_content(
304
+ prompt_request,
305
+ generation_config=genai_generative.types.GenerationConfig(
306
+ temperature=0.7,
307
+ top_p=0.95,
308
+ top_k=64,
309
+ max_output_tokens=1024,
310
+ )
311
+ )
312
+ response_text = response.text.strip()
313
+ if "--ar 1:1" not in response_text:
314
+ response_text = response_text.rstrip(".") + ". --ar 1:1 --s 750 --q 2"
315
+ return response_text
316
+ except Exception as e:
317
+ return f"ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
318
+
319
  def get_selected_background_info(bg_type, simple, studio, nature, indoor, abstract):
320
  if bg_type == "์‹ฌํ”Œ ๋ฐฐ๊ฒฝ":
321
  return {
 
354
  "english": "white background"
355
  }
356
 
357
+ def generate_product_prompt_output(image, bg_type, simple, studio, nature, indoor, abstract, product_name, additional_info):
358
+ if image is None:
359
+ gr.Warning("์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.")
360
+ return "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.", None, "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œ ํ›„ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”."
361
+ product_name = product_name.strip() or "์ œํ’ˆ"
362
+ background_info = get_selected_background_info(bg_type, simple, studio, nature, indoor, abstract)
363
+ try:
364
+ prompt = generate_prompt_with_gemini(product_name, background_info, additional_info)
365
+ if "Gemini API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค" in prompt:
366
+ gr.Warning("Gemini API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ‚ค๋ฅผ ๋ฐœ๊ธ‰๋ฐ›์•„ ์„ค์ •ํ•ด์ฃผ์„ธ์š”.")
367
+ prompt = """
368
+ [Gemini API ํ‚ค ๋ˆ„๋ฝ]
369
+ API ํ‚ค ์„ค์ • ๋ฐฉ๋ฒ•:
370
+ 1. ํ™˜๊ฒฝ ๋ณ€์ˆ˜: export GEMINI_API_KEY="your-api-key"
371
+ 2. ์ฝ”๋“œ ๋‚ด ์ง์ ‘ ์ž…๋ ฅ: GEMINI_API_KEY = "your-api-key"
372
+ ํ‚ค ๋ฐœ๊ธ‰: https://makersuite.google.com/
373
+ """
374
+ return prompt, image, "API ํ‚ค๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค."
375
+ preview = f"""
376
+ <div style="padding:10px; border:1px solid #ddd; border-radius:8px; margin-top:10px;">
377
+ <h3>ํ”„๋กฌํ”„ํŠธ ์š”์•ฝ</h3>
378
+ <p><strong>์ด ๊ธธ์ด:</strong> {len(prompt)} ๊ธ€์ž</p>
379
+ <p><strong>๋ฐฐ๊ฒฝ:</strong> {background_info['category']} &gt; {background_info['name']}</p>
380
+ <p><strong>๋ฏธ๋“œ์ €๋‹ˆ ํŒŒ๋ผ๋ฏธํ„ฐ:</strong> {" ".join([param for param in ["--ar 1:1", "--s 750", "--q 2"] if param in prompt])}</p>
381
+ </div>
382
+ """
383
+ return prompt, image, preview
384
+ except Exception as e:
385
+ error_msg = f"ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
386
+ gr.Error(error_msg)
387
+ return error_msg, image, "์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
 
389
+ # ------------------- Gradio ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌ์„ฑ -------------------
390
+
391
+ def create_basic_app():
392
+ with gr.Blocks() as basic_app:
393
+ with gr.Column():
394
+ with gr.Group():
395
+ with gr.Column():
396
+ with gr.Row():
397
+ image1_input = gr.Image(type="pil", label="์ด๋ฏธ์ง€ ์—…๋กœ๋“œ", image_mode="RGB")
398
+ prompt_input = gr.Textbox(
399
+ lines=3,
400
+ placeholder="ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”. '#1'์œผ๋กœ ์—…๋กœ๋“œํ•œ ์ด๋ฏธ์ง€๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
401
+ label="ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ"
402
+ )
403
+ with gr.Row():
404
+ submit_single_btn = gr.Button('์ด๋ฏธ์ง€ ์ƒ์„ฑ (1์žฅ)')
405
+ submit_btn = gr.Button('์ด๋ฏธ์ง€ ์ƒ์„ฑ (4์žฅ)')
406
+ with gr.Column():
407
+ with gr.Row():
408
+ with gr.Column():
409
+ output_image1 = gr.Image(label="์ด๋ฏธ์ง€ #1", type="filepath")
410
+ output_image3 = gr.Image(label="์ด๋ฏธ์ง€ #3", type="filepath")
411
+ with gr.Column():
412
+ output_image2 = gr.Image(label="์ด๋ฏธ์ง€ #2", type="filepath")
413
+ output_image4 = gr.Image(label="์ด๋ฏธ์ง€ #4", type="filepath")
414
+ output_text = gr.Textbox(label="๊ฒฐ๊ณผ ์ •๋ณด", lines=2)
415
+ prompt_display = gr.Textbox(label="์‚ฌ์šฉ๋œ ํ”„๋กฌํ”„ํŠธ (์˜์–ด)", lines=2)
416
+ submit_single_btn.click(
417
+ fn=lambda image1, prompt: process_images_with_prompt(image1, prompt, 0),
418
+ inputs=[image1_input, prompt_input],
419
+ outputs=[output_image1, output_text, prompt_display],
420
+ )
421
+ submit_btn.click(
422
+ fn=generate_multiple_images,
423
+ inputs=[image1_input, prompt_input],
424
+ outputs=[output_image1, output_image2, output_image3, output_image4, output_text, prompt_display],
425
+ )
426
+ return basic_app
427
+
428
+ def create_product_prompt_app():
429
+ with gr.Blocks(title="๊ณ ๊ธ‰ ์ƒํ’ˆ ์ด๋ฏธ์ง€ ๋ฐฐ๊ฒฝ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ๊ธฐ") as product_app:
430
+ gr.Markdown("# ๊ณ ๊ธ‰ ์ƒํ’ˆ ์ด๋ฏธ์ง€ ๋ฐฐ๊ฒฝ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ๊ธฐ")
431
+ gr.Markdown("์ƒํ’ˆ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ณ  ์˜ต์…˜์„ ์„ ํƒํ•˜๋ฉด ๊ณ ํ’ˆ์งˆ ์ƒ์—…์šฉ ํ”„๋กฌํ”„ํŠธ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.")
432
+ with gr.Row():
433
+ product_name = gr.Textbox(label="์ƒํ’ˆ๋ช… (ํ•œ๊ตญ์–ด ์ž…๋ ฅ)", placeholder="์˜ˆ: ์Šคํ‚จ์ผ€์–ด ํŠœ๋ธŒ, ํ…€๋ธ”๋Ÿฌ ๋“ฑ", interactive=True)
434
+ background_type = gr.Radio(
435
+ choices=["์‹ฌํ”Œ ๋ฐฐ๊ฒฝ", "์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ", "์ž์—ฐ ํ™˜๊ฒฝ", "์‹ค๋‚ด ํ™˜๊ฒฝ", "์ถ”์ƒ/ํŠน์ˆ˜ ๋ฐฐ๊ฒฝ"],
436
+ label="๋ฐฐ๊ฒฝ ์œ ํ˜•",
437
+ value="์‹ฌํ”Œ ๋ฐฐ๊ฒฝ"
438
+ )
439
+ with gr.Row():
440
+ with gr.Column(scale=1):
441
+ image_input = gr.Image(label="์ƒํ’ˆ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ", type="pil")
442
+ simple_dropdown = gr.Dropdown(
443
+ choices=list(SIMPLE_BACKGROUNDS.keys()),
444
+ value=list(SIMPLE_BACKGROUNDS.keys())[0] if SIMPLE_BACKGROUNDS else None,
445
+ label="์‹ฌํ”Œ ๋ฐฐ๊ฒฝ ์„ ํƒ",
446
+ visible=True,
447
+ interactive=True
448
+ )
449
+ studio_dropdown = gr.Dropdown(
450
+ choices=list(STUDIO_BACKGROUNDS.keys()),
451
+ value=list(STUDIO_BACKGROUNDS.keys())[0] if STUDIO_BACKGROUNDS else None,
452
+ label="์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ ์„ ํƒ",
453
+ visible=False,
454
+ interactive=True
455
+ )
456
+ nature_dropdown = gr.Dropdown(
457
+ choices=list(NATURE_BACKGROUNDS.keys()),
458
+ value=list(NATURE_BACKGROUNDS.keys())[0] if NATURE_BACKGROUNDS else None,
459
+ label="์ž๏ฟฝ๏ฟฝ๏ฟฝ ํ™˜๊ฒฝ ์„ ํƒ",
460
+ visible=False,
461
+ interactive=True
462
+ )
463
+ indoor_dropdown = gr.Dropdown(
464
+ choices=list(INDOOR_BACKGROUNDS.keys()),
465
+ value=list(INDOOR_BACKGROUNDS.keys())[0] if INDOOR_BACKGROUNDS else None,
466
+ label="์‹ค๋‚ด ํ™˜๊ฒฝ ์„ ํƒ",
467
+ visible=False,
468
+ interactive=True
469
+ )
470
+ abstract_dropdown = gr.Dropdown(
471
+ choices=list(ABSTRACT_BACKGROUNDS.keys()),
472
+ value=list(ABSTRACT_BACKGROUNDS.keys())[0] if ABSTRACT_BACKGROUNDS else None,
473
+ label="์ถ”์ƒ/ํŠน์ˆ˜ ๋ฐฐ๊ฒฝ ์„ ํƒ",
474
+ visible=False,
475
+ interactive=True
476
+ )
477
+ additional_info = gr.Textbox(
478
+ label="์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ (์„ ํƒ์‚ฌํ•ญ)",
479
+ placeholder="์˜ˆ: ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ๋А๋‚Œ, ๋ฐ์€ ์กฐ๋ช…, ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ณด์กฐ์ ์ธ ๊ฐ์ฒด๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š” ๋“ฑ",
480
+ lines=3,
481
+ interactive=True
482
+ )
483
+ submit_btn = gr.Button("ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ", variant="primary")
484
+ with gr.Column(scale=1):
485
+ prompt_output = gr.Textbox(label="์ƒ์„ฑ๋œ ํ”„๋กฌํ”„ํŠธ", lines=10)
486
+ image_preview = gr.Image(label="์—…๋กœ๋“œ๋œ ์ด๋ฏธ์ง€ (#1)", type="pil")
487
+ preview_html = gr.HTML("ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ์‹œ ์—ฌ๊ธฐ์— ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.")
488
+ def update_dropdowns(bg_type):
489
+ return {
490
+ simple_dropdown: gr.update(visible=(bg_type == "์‹ฌํ”Œ ๋ฐฐ๊ฒฝ")),
491
+ studio_dropdown: gr.update(visible=(bg_type == "์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ")),
492
+ nature_dropdown: gr.update(visible=(bg_type == "์ž์—ฐ ํ™˜๊ฒฝ")),
493
+ indoor_dropdown: gr.update(visible=(bg_type == "์‹ค๋‚ด ํ™˜๊ฒฝ")),
494
+ abstract_dropdown: gr.update(visible=(bg_type == "์ถ”์ƒ/ํŠน์ˆ˜ ๋ฐฐ๊ฒฝ"))
495
+ }
496
+ background_type.change(
497
+ fn=update_dropdowns,
498
+ inputs=[background_type],
499
+ outputs=[simple_dropdown, studio_dropdown, nature_dropdown, indoor_dropdown, abstract_dropdown]
500
+ )
501
+ def generate_output(image, bg_type, simple, studio, nature, indoor, abstract, product_name, additional_text):
502
+ return generate_product_prompt_output(image, bg_type, simple, studio, nature, indoor, abstract, product_name, additional_text)
503
+ submit_btn.click(
504
+ fn=generate_output,
505
+ inputs=[
506
+ image_input,
507
+ background_type,
508
+ simple_dropdown,
509
+ studio_dropdown,
510
+ nature_dropdown,
511
+ indoor_dropdown,
512
+ abstract_dropdown,
513
+ product_name,
514
+ additional_info
515
+ ],
516
+ outputs=[
517
+ prompt_output,
518
+ image_preview,
519
+ preview_html
520
+ ]
521
+ )
522
+ return product_app
523
+
524
+ def main():
525
+ basic_app = create_basic_app()
526
+ product_app = create_product_prompt_app()
527
+ with gr.Blocks() as demo:
528
+ gr.Markdown("# ํ†ตํ•ฉ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฐ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ์•ฑ")
529
+ with gr.Tabs():
530
+ with gr.TabItem("์ผ๋ฐ˜ ์ด๋ฏธ์ง€ ์ƒ์„ฑ"):
531
+ basic_app.render()
532
+ with gr.TabItem("์ƒํ’ˆ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ๋ฐ ์ด๋ฏธ์ง€ ์ƒ์„ฑ"):
533
+ product_app.render()
534
+ demo.queue()
535
+ demo.launch()
536
+
537
+ if __name__ == "__main__":
538
+ main()