ssboost commited on
Commit
f7d1828
ยท
verified ยท
1 Parent(s): da240c7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1126 -1
app.py CHANGED
@@ -1,2 +1,1127 @@
1
  import os
2
- exec(os.environ.get('APP'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ from db_examples import product_background_examples
14
+
15
+ load_dotenv()
16
+
17
+ # ------------------- ๋กœ๊น… ์„ค์ • -------------------
18
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # ------------------- ๋ฐฐ๊ฒฝ ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ • -------------------
22
+ BACKGROUNDS_DIR = "./background"
23
+ if not os.path.exists(BACKGROUNDS_DIR):
24
+ os.makedirs(BACKGROUNDS_DIR)
25
+ logger.info(f"๋ฐฐ๊ฒฝ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค: {BACKGROUNDS_DIR}")
26
+
27
+ # ------------------- API ํ‚ค ์ˆœํ™˜ ์‹œ์Šคํ…œ -------------------
28
+ API_KEYS = [] # API ํ‚ค ๋ชฉ๋ก
29
+ current_key_index = 0 # ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ธ ํ‚ค ์ธ๋ฑ์Šค
30
+
31
+ def initialize_api_keys():
32
+ """API ํ‚ค ๋ชฉ๋ก์„ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ํ•จ์ˆ˜"""
33
+ global API_KEYS
34
+ # ํ™˜๊ฒฝ ๋ณ€์ˆ˜์—์„œ API ํ‚ค ๊ฐ€์ ธ์˜ค๊ธฐ
35
+ key1 = os.environ.get("GEMINI_API_KEY_1", "")
36
+ key2 = os.environ.get("GEMINI_API_KEY_2", "")
37
+ key3 = os.environ.get("GEMINI_API_KEY_3", "")
38
+
39
+ # ๋นˆ ๋ฌธ์ž์—ด์ด ์•„๋‹Œ ํ‚ค๋งŒ ์ถ”๊ฐ€
40
+ if key1:
41
+ API_KEYS.append(key1)
42
+ if key2:
43
+ API_KEYS.append(key2)
44
+ if key3:
45
+ API_KEYS.append(key3)
46
+
47
+ # ๊ธฐ์กด GEMINI_API_KEY๊ฐ€ ์žˆ์œผ๋ฉด ์ถ”๊ฐ€
48
+ default_key = os.environ.get("GEMINI_API_KEY", "")
49
+ if default_key and default_key not in API_KEYS:
50
+ API_KEYS.append(default_key)
51
+
52
+ logger.info(f"API ํ‚ค {len(API_KEYS)}๊ฐœ๊ฐ€ ๋กœ๋“œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
53
+
54
+ def get_next_api_key():
55
+ """๋‹ค์Œ API ํ‚ค๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ํ•จ์ˆ˜"""
56
+ global current_key_index
57
+
58
+ if not API_KEYS:
59
+ return None
60
+
61
+ # ํ˜„์žฌ ํ‚ค ๊ฐ€์ ธ์˜ค๊ธฐ
62
+ api_key = API_KEYS[current_key_index]
63
+
64
+ # ๋‹ค์Œ ํ‚ค ์ธ๋ฑ์Šค๋กœ ์—…๋ฐ์ดํŠธ
65
+ current_key_index = (current_key_index + 1) % len(API_KEYS)
66
+
67
+ return api_key
68
+
69
+ # ------------------- ์ „์—ญ ๋ณ€์ˆ˜ ์„ค์ • -------------------
70
+ SIMPLE_BACKGROUNDS = {}
71
+ STUDIO_BACKGROUNDS = {}
72
+ NATURE_BACKGROUNDS = {}
73
+ INDOOR_BACKGROUNDS = {}
74
+ SPECIAL_BACKGROUNDS = {} # ํŠน์ˆ˜ ๋ฐฐ๊ฒฝ ์ถ”๊ฐ€
75
+ IMAGE_CACHE = {} # ์ด๋ฏธ์ง€ ์บ์‹œ ์ถ”๊ฐ€
76
+
77
+ # ์ปค์Šคํ…€ CSS ์Šคํƒ€์ผ - ๊ธฐ์กด ์Šคํƒ€์ผ ์œ ์ง€ ๋ฐ ์˜ˆ์‹œ ํƒญ์šฉ ์Šคํƒ€์ผ ์ถ”๊ฐ€
78
+ custom_css = """
79
+ :root {
80
+ --primary-color: #FB7F0D;
81
+ --secondary-color: #ff9a8b;
82
+ --accent-color: #FF6B6B;
83
+ --background-color: #FFF3E9;
84
+ --card-bg: #ffffff;
85
+ --text-color: #334155;
86
+ --border-radius: 18px;
87
+ --shadow: 0 8px 30px rgba(251, 127, 13, 0.08);
88
+ }
89
+
90
+ /* ์˜ˆ์‹œ ๊ฐค๋Ÿฌ๋ฆฌ ์Šคํƒ€์ผ */
91
+ .example-gallery {
92
+ display: grid;
93
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
94
+ gap: 20px;
95
+ padding: 20px;
96
+ }
97
+
98
+ .example-item {
99
+ cursor: pointer;
100
+ border: 1px solid rgba(0, 0, 0, 0.1);
101
+ border-radius: var(--border-radius);
102
+ overflow: hidden;
103
+ transition: all 0.3s ease;
104
+ background: white;
105
+ }
106
+
107
+ .example-item:hover {
108
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
109
+ transform: translateY(-2px);
110
+ }
111
+
112
+ .example-item img {
113
+ width: 100%;
114
+ height: 250px;
115
+ object-fit: cover;
116
+ }
117
+
118
+ .example-label {
119
+ padding: 10px;
120
+ text-align: center;
121
+ font-weight: bold;
122
+ background: rgba(251, 127, 13, 0.1);
123
+ }
124
+
125
+ .example-detail-view {
126
+ margin-bottom: 30px;
127
+ }
128
+
129
+ .example-params {
130
+ background: white;
131
+ padding: 20px;
132
+ border-radius: var(--border-radius);
133
+ box-shadow: var(--shadow);
134
+ }
135
+
136
+ .example-params p {
137
+ margin: 10px 0;
138
+ font-size: 16px;
139
+ }
140
+
141
+ .example-params strong {
142
+ color: var(--primary-color);
143
+ }
144
+
145
+ /* ์„ ํƒ๋œ ์˜ˆ์‹œ ํ•˜์ด๋ผ์ดํŠธ */
146
+ .example-item.selected {
147
+ border: 3px solid var(--primary-color);
148
+ }
149
+
150
+ /* โ”€โ”€ ํƒญ ๋‚ด๋ถ€ ํŒจ๋„ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ โ”€โ”€ */
151
+ .gr-tabs-panel {
152
+ background-color: var(--background-color) !important;
153
+ box-shadow: none !important;
154
+ }
155
+ .gr-tabs-panel::before,
156
+ .gr-tabs-panel::after {
157
+ display: none !important;
158
+ content: none !important;
159
+ }
160
+
161
+ /* โ”€โ”€ ๊ทธ๋ฃน ๋ž˜ํผ ๋ฐฐ๊ฒฝ ์™„์ „ ์ œ๊ฑฐ โ”€โ”€ */
162
+ .custom-section-group,
163
+ .gr-block.gr-group {
164
+ background-color: var(--background-color) !important;
165
+ box-shadow: none !important;
166
+ }
167
+ .custom-section-group::before,
168
+ .custom-section-group::after,
169
+ .gr-block.gr-group::before,
170
+ .gr-block.gr-group::after {
171
+ display: none !important;
172
+ content: none !important;
173
+ }
174
+
175
+ /* ๊ทธ๋ฃน ์ปจํ…Œ์ด๋„ˆ ๋ฐฐ๊ฒฝ์„ ์•„์ด๋ณด๋ฆฌ๋กœ, ๊ทธ๋ฆผ์ž ์ œ๊ฑฐ */
176
+ .custom-section-group {
177
+ background-color: var(--background-color) !important;
178
+ box-shadow: none !important;
179
+ }
180
+ /* ์ƒ๋‹จยทํ•˜๋‹จ์— ๊ทธ๋ ค์ง€๋Š” ํšŒ์ƒ‰ ์บก(๋‘ฅ๊ทผ ๋ชจ์„œ๋ฆฌ) ์ œ๊ฑฐ */
181
+ .custom-section-group::before,
182
+ .custom-section-group::after {
183
+ display: none !important;
184
+ content: none !important;
185
+ }
186
+
187
+ body {
188
+ font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
189
+ background-color: var(--background-color);
190
+ color: var(--text-color);
191
+ line-height: 1.6;
192
+ margin: 0;
193
+ padding: 0;
194
+ }
195
+
196
+ .gradio-container {
197
+ width: 100%; /* ์ „์ฒด ๋„ˆ๋น„ 100% ๊ณ ์ • */
198
+ margin: 0 auto;
199
+ padding: 20px;
200
+ background-color: var(--background-color);
201
+ }
202
+
203
+ /* ์ฝ˜ํ…์ธ  ๋ฐ•์Šค (ํ”„๋ ˆ์ž„) ์Šคํƒ€์ผ */
204
+ .custom-frame {
205
+ background-color: var(--card-bg);
206
+ border: 1px solid rgba(0, 0, 0, 0.04);
207
+ border-radius: var(--border-radius);
208
+ padding: 20px;
209
+ margin: 10px 0;
210
+ box-shadow: var(--shadow);
211
+ }
212
+
213
+ /* ์„น์…˜ ๊ทธ๋ฃน ์Šคํƒ€์ผ - ํšŒ์ƒ‰ ๋ฐฐ๊ฒฝ ์™„์ „ ์ œ๊ฑฐ */
214
+ .custom-section-group {
215
+ margin-top: 20px;
216
+ padding: 0;
217
+ border: none;
218
+ border-radius: 0;
219
+ background-color: var(--background-color); /* ํšŒ์ƒ‰ โ†’ ์•„์ด๋ณด๋ฆฌ(์ „์ฒด ๋ฐฐ๊ฒฝ์ƒ‰) */
220
+ box-shadow: none !important; /* ํ˜น์‹œ ๋‚จ์•„์žˆ๋Š” ๊ทธ๋ฆผ์ž๋„ ๊ฐ™์ด ์ œ๊ฑฐ */
221
+ }
222
+
223
+ /* ๋ฒ„ํŠผ ์Šคํƒ€์ผ - ๊ธ€์ž ํฌ๊ธฐ 18px */
224
+ .custom-button {
225
+ border-radius: 30px !important;
226
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important;
227
+ color: white !important;
228
+ font-size: 18px !important;
229
+ padding: 10px 20px !important;
230
+ border: none;
231
+ box-shadow: 0 4px 8px rgba(251, 127, 13, 0.25);
232
+ transition: transform 0.3s ease;
233
+ }
234
+ .custom-button:hover {
235
+ transform: translateY(-2px);
236
+ box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3);
237
+ }
238
+
239
+ /* ์ œ๋ชฉ ์Šคํƒ€์ผ (๋ชจ๋“  ํ•ญ๋ชฉ๋ช…์ด ๋™์ผํ•˜๊ฒŒ custom-title ํด๋ž˜์Šค๋กœ) */
240
+ .custom-title {
241
+ font-size: 28px;
242
+ font-weight: bold;
243
+ margin-bottom: 10px;
244
+ color: var(--text-color);
245
+ border-bottom: 2px solid var(--primary-color);
246
+ padding-bottom: 5px;
247
+ }
248
+
249
+ /* ์ด๋ฏธ์ง€ ์ปจํ…Œ์ด๋„ˆ */
250
+ .image-container {
251
+ border-radius: var(--border-radius);
252
+ overflow: hidden;
253
+ border: 1px solid rgba(0, 0, 0, 0.08);
254
+ transition: all 0.3s ease;
255
+ background-color: white;
256
+ aspect-ratio: 1 / 1; /* ์ •์‚ฌ๊ฐํ˜• ๋น„์œจ ๊ฐ•์ œ */
257
+ }
258
+
259
+ .image-container:hover {
260
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
261
+ }
262
+
263
+ .image-container img {
264
+ width: 100%;
265
+ height: 100%;
266
+ object-fit: contain; /* ์ด๋ฏธ์ง€ ๋น„์œจ ์œ ์ง€ํ•˜๋ฉด์„œ ์ปจํ…Œ์ด๋„ˆ์— ๋งž์ถค */
267
+ }
268
+
269
+ /* ์ž…๋ ฅ ํ•„๋“œ ์Šคํƒ€์ผ */
270
+ .gr-input, .gr-text-input, .gr-sample-inputs {
271
+ border-radius: var(--border-radius) !important;
272
+ border: 1px solid #dddddd !important;
273
+ padding: 12px !important;
274
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05) !important;
275
+ transition: all 0.3s ease !important;
276
+ }
277
+
278
+ .gr-input:focus, .gr-text-input:focus {
279
+ border-color: var(--primary-color) !important;
280
+ outline: none !important;
281
+ box-shadow: 0 0 0 2px rgba(251, 127, 13, 0.2) !important;
282
+ }
283
+
284
+ /* ๋ฉ”์ธ ์ปจํ…์ธ  ์Šคํฌ๋กค๋ฐ” */
285
+ ::-webkit-scrollbar {
286
+ width: 8px;
287
+ height: 8px;
288
+ }
289
+
290
+ ::-webkit-scrollbar-track {
291
+ background: rgba(0, 0, 0, 0.05);
292
+ border-radius: 10px;
293
+ }
294
+
295
+ ::-webkit-scrollbar-thumb {
296
+ background: var(--primary-color);
297
+ border-radius: 10px;
298
+ }
299
+
300
+ /* ์• ๋‹ˆ๋ฉ”์ด์…˜ ์Šคํƒ€์ผ */
301
+ @keyframes fadeIn {
302
+ from { opacity: 0; transform: translateY(10px); }
303
+ to { opacity: 1; transform: translateY(0); }
304
+ }
305
+
306
+ .fade-in {
307
+ animation: fadeIn 0.5s ease-out;
308
+ }
309
+
310
+ /* ๋ฐ˜์‘ํ˜• */
311
+ @media (max-width: 768px) {
312
+ .button-grid {
313
+ grid-template-columns: repeat(2, 1fr);
314
+ }
315
+ }
316
+
317
+ /* ์„น์…˜ ์ œ๋ชฉ ์Šคํƒ€์ผ - ์ฐธ์กฐ์ฝ”๋“œ ์Šคํƒ€์ผ๊ณผ ๋™์ผํ•˜๊ฒŒ ์ ์šฉ */
318
+ .section-title {
319
+ display: flex;
320
+ align-items: center;
321
+ font-size: 20px;
322
+ font-weight: 700;
323
+ color: #333333;
324
+ margin-bottom: 10px;
325
+ padding-bottom: 5px;
326
+ border-bottom: 2px solid #FB7F0D;
327
+ font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
328
+ }
329
+
330
+ .section-title img {
331
+ margin-right: 10px;
332
+ width: 24px;
333
+ height: 24px;
334
+ }
335
+ """
336
+
337
+ # FontAwesome ์•„์ด์ฝ˜ ํฌํ•จ
338
+ fontawesome_link = """
339
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
340
+ """
341
+
342
+ # ์ œ๋ชฉ๊ณผ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ HTML ์ œ๊ฑฐ
343
+ header_html = ""
344
+ guide_html = ""
345
+
346
+ # ------------------- ๋ฐฐ๊ฒฝ JSON ํŒŒ์ผ ๋กœ๋“œ ํ•จ์ˆ˜ -------------------
347
+ def load_background_json(filename):
348
+ """๋ฐฐ๊ฒฝ JSON ํŒŒ์ผ ๋กœ๋“œ ํ•จ์ˆ˜"""
349
+ file_path = os.path.join(BACKGROUNDS_DIR, filename)
350
+ try:
351
+ with open(file_path, 'r', encoding='utf-8') as f:
352
+ data = json.load(f)
353
+ logger.info(f"{filename} ํŒŒ์ผ์„ ์„ฑ๊ณต์ ์œผ๋กœ ๋กœ๋“œํ–ˆ์Šต๋‹ˆ๋‹ค. {len(data)} ํ•ญ๋ชฉ ํฌํ•จ.")
354
+ return data
355
+ except FileNotFoundError:
356
+ logger.info(f"{filename} ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค.")
357
+ return {}
358
+ except Exception as e:
359
+ logger.warning(f"{filename} ํŒŒ์ผ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}.")
360
+ return {}
361
+
362
+ # ------------------- ๋ฐฐ๊ฒฝ ์˜ต์…˜ ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜ -------------------
363
+ def initialize_backgrounds():
364
+ """๋ชจ๋“  ๋ฐฐ๊ฒฝ ์˜ต์…˜ ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜"""
365
+ global SIMPLE_BACKGROUNDS, STUDIO_BACKGROUNDS, NATURE_BACKGROUNDS, INDOOR_BACKGROUNDS
366
+ global SPECIAL_BACKGROUNDS
367
+
368
+ logger.info(f"Backgrounds ๋””๋ ‰ํ† ๋ฆฌ ๊ฒฝ๋กœ: {BACKGROUNDS_DIR}")
369
+ logger.info(f"๋””๋ ‰ํ† ๋ฆฌ ๋‚ด ํŒŒ์ผ ๋ชฉ๋ก: {os.listdir(BACKGROUNDS_DIR)}")
370
+
371
+ SIMPLE_BACKGROUNDS = load_background_json("simple_backgrounds.json")
372
+ STUDIO_BACKGROUNDS = load_background_json("studio_backgrounds.json")
373
+ NATURE_BACKGROUNDS = load_background_json("nature_backgrounds.json")
374
+ INDOOR_BACKGROUNDS = load_background_json("indoor_backgrounds.json")
375
+ SPECIAL_BACKGROUNDS = load_background_json("special_backgrounds.json")
376
+
377
+ # ๊ธฐ๋ณธ๊ฐ’ ์„ค์ • (ํŒŒ์ผ์ด ์—†๊ฑฐ๋‚˜ ๋น„์–ด์žˆ๋Š” ๊ฒฝ์šฐ)
378
+ if not SIMPLE_BACKGROUNDS:
379
+ SIMPLE_BACKGROUNDS = {"ํด๋ž˜์‹ ํ™”์ดํŠธ": "clean white background with soft even lighting"}
380
+ if not STUDIO_BACKGROUNDS:
381
+ STUDIO_BACKGROUNDS = {"๋ฏธ๋‹ˆ๋ฉ€ ํ”Œ๋žซ๋ ˆ์ด": "minimalist flat lay with clean white background"}
382
+ if not NATURE_BACKGROUNDS:
383
+ NATURE_BACKGROUNDS = {"์—ด๋Œ€ ํ•ด๋ณ€": "tropical beach with crystal clear water"}
384
+ if not INDOOR_BACKGROUNDS:
385
+ INDOOR_BACKGROUNDS = {"๋ฏธ๋‹ˆ๋ฉ€ ์Šค์นธ๋””๋‚˜๋น„์•ˆ ๊ฑฐ์‹ค": "minimalist Scandinavian living room"}
386
+ if not SPECIAL_BACKGROUNDS:
387
+ SPECIAL_BACKGROUNDS = {"๋„ค์˜จ ๋ผ์ดํŠธ": "neon light background with vibrant glowing elements"}
388
+
389
+ logger.info("๋ชจ๋“  ๋ฐฐ๊ฒฝ ์˜ต์…˜ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
390
+
391
+ # ๋ฐฐ๊ฒฝ ๋“œ๋กญ๋‹ค์šด ์ดˆ๊ธฐํ™”๋ฅผ ์œ„ํ•œ ํ•จ์ˆ˜
392
+ def initialize_dropdowns():
393
+ """๋“œ๋กญ๋‹ค์šด ๋ฉ”๋‰ด ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜"""
394
+ simple_choices = list(SIMPLE_BACKGROUNDS.keys())
395
+ studio_choices = list(STUDIO_BACKGROUNDS.keys())
396
+ nature_choices = list(NATURE_BACKGROUNDS.keys())
397
+ indoor_choices = list(INDOOR_BACKGROUNDS.keys())
398
+ special_choices = list(SPECIAL_BACKGROUNDS.keys())
399
+
400
+ return {
401
+ "simple": simple_choices,
402
+ "studio": studio_choices,
403
+ "nature": nature_choices,
404
+ "indoor": indoor_choices,
405
+ "special": special_choices,
406
+ }
407
+
408
+ # ------------------- ๊ธฐ๋ณธ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ -------------------
409
+
410
+ def get_api_key(user_input_key=None):
411
+ """API ํ‚ค๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ํ•จ์ˆ˜ (์ˆœํ™˜ ์‹œ์Šคํ…œ ์ ์šฉ)"""
412
+ return get_next_api_key()
413
+
414
+ def save_binary_file(file_name, data):
415
+ with open(file_name, "wb") as f:
416
+ f.write(data)
417
+
418
+ def translate_prompt_to_english(prompt):
419
+ if not re.search("[๊ฐ€-ํžฃ]", prompt):
420
+ return prompt
421
+ prompt = prompt.replace("#1", "IMAGE_TAG_ONE")
422
+ try:
423
+ api_key = get_api_key()
424
+ if not api_key:
425
+ logger.error("Gemini API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
426
+ prompt = prompt.replace("IMAGE_TAG_ONE", "#1")
427
+ return prompt
428
+ client = genai.Client(api_key=api_key)
429
+ translation_prompt = f"""
430
+ Translate the following Korean text to English:
431
+
432
+ {prompt}
433
+
434
+ IMPORTANT: The token IMAGE_TAG_ONE is a special tag
435
+ and must be preserved exactly as is in your translation. Do not translate this token.
436
+ """
437
+ logger.info(f"Translation prompt: {translation_prompt}")
438
+ response = client.models.generate_content(
439
+ model="gemini-2.0-flash",
440
+ contents=[translation_prompt],
441
+ config=types.GenerateContentConfig(
442
+ response_modalities=['Text'],
443
+ temperature=0.2,
444
+ top_p=0.95,
445
+ top_k=40,
446
+ max_output_tokens=512
447
+ )
448
+ )
449
+ translated_text = ""
450
+ for part in response.candidates[0].content.parts:
451
+ if hasattr(part, 'text') and part.text:
452
+ translated_text += part.text
453
+ if translated_text.strip():
454
+ translated_text = translated_text.replace("IMAGE_TAG_ONE", "#1")
455
+ logger.info(f"Translated text: {translated_text.strip()}")
456
+ return translated_text.strip()
457
+ else:
458
+ logger.warning("๋ฒˆ์—ญ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์›๋ณธ ํ”„๋กฌํ”„ํŠธ ์‚ฌ์šฉ")
459
+ prompt = prompt.replace("IMAGE_TAG_ONE", "#1")
460
+ return prompt
461
+ except Exception as e:
462
+ logger.exception("๋ฒˆ์—ญ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
463
+ prompt = prompt.replace("IMAGE_TAG_ONE", "#1")
464
+ return prompt
465
+
466
+ def preprocess_prompt(prompt, image1):
467
+ has_img1 = image1 is not None
468
+ if "#1" in prompt and not has_img1:
469
+ prompt = prompt.replace("#1", "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€(์—†์Œ)")
470
+ else:
471
+ prompt = prompt.replace("#1", "์ฒซ ๋ฒˆ์งธ ์ด๋ฏธ์ง€")
472
+ prompt += " ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”. ์ด๋ฏธ์ง€์— ํ…์ŠคํŠธ๋‚˜ ๊ธ€์ž๋ฅผ ํฌํ•จํ•˜์ง€ ๋งˆ์„ธ์š”."
473
+ return prompt
474
+
475
+ # ------------------- ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ•จ์ˆ˜ -------------------
476
+ def generate_with_images(prompt, images, variation_index=0):
477
+ try:
478
+ api_key = get_api_key()
479
+ if not api_key:
480
+ return None, "API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— GEMINI_API_KEY_1, GEMINI_API_KEY_2, GEMINI_API_KEY_3 ์ค‘ ํ•˜๋‚˜ ์ด์ƒ์„ ์„ค์ •ํ•ด์ฃผ์„ธ์š”."
481
+ client = genai.Client(api_key=api_key)
482
+ logger.info(f"Gemini API ์š”์ฒญ ์‹œ์ž‘ - ํ”„๋กฌํ”„ํŠธ: {prompt}, ๋ณ€ํ˜• ์ธ๋ฑ์Šค: {variation_index}")
483
+
484
+ variation_suffixes = [
485
+ " Create this as a professional studio product shot with precise focus on the product details. Do not add any text, watermarks, or labels to the image.",
486
+ " Create this as a high-contrast artistic studio shot with dramatic lighting and shadows. Do not add any text, watermarks, or labels to the image.",
487
+ " Create this as a soft-lit elegantly styled product shot with complementary elements. Do not add any text, watermarks, or labels to the image.",
488
+ " Create this as a high-definition product photography with perfect color accuracy and detail preservation. Do not add any text, watermarks, or labels to the image."
489
+ ]
490
+
491
+ if variation_index < len(variation_suffixes):
492
+ prompt = prompt + variation_suffixes[variation_index]
493
+ else:
494
+ prompt = prompt + " Create as high-end commercial product photography. Do not add any text, watermarks, or labels to the image."
495
+
496
+ contents = [prompt]
497
+ for idx, img in enumerate(images, 1):
498
+ if img is not None:
499
+ contents.append(img)
500
+ logger.info(f"์ด๋ฏธ์ง€ #{idx} ์ถ”๊ฐ€๋จ")
501
+
502
+ response = client.models.generate_content(
503
+ model="gemini-2.0-flash-exp-image-generation",
504
+ contents=contents,
505
+ config=types.GenerateContentConfig(
506
+ response_modalities=['Text', 'Image'],
507
+ temperature=1.05,
508
+ top_p=0.97,
509
+ top_k=50,
510
+ max_output_tokens=10240
511
+ )
512
+ )
513
+
514
+ with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
515
+ temp_path = tmp.name
516
+ result_text = ""
517
+ image_found = False
518
+ for part in response.candidates[0].content.parts:
519
+ if hasattr(part, 'text') and part.text:
520
+ result_text += part.text
521
+ logger.info(f"์‘๋‹ต ํ…์ŠคํŠธ: {part.text}")
522
+ elif hasattr(part, 'inline_data') and part.inline_data:
523
+ save_binary_file(temp_path, part.inline_data.data)
524
+ image_found = True
525
+ logger.info("์‘๋‹ต์—์„œ ์ด๋ฏธ์ง€ ์ถ”์ถœ ์„ฑ๊ณต")
526
+ if not image_found:
527
+ return None, f"API์—์„œ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค."
528
+ result_img = Image.open(temp_path)
529
+ if result_img.mode == "RGBA":
530
+ result_img = result_img.convert("RGB")
531
+ result_img.save(temp_path, format="JPEG", quality=95)
532
+ return temp_path, f"์ด๋ฏธ์ง€๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
533
+ except Exception as e:
534
+ logger.exception("์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
535
+ return None, f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}"
536
+
537
+
538
+ def process_images_with_prompt(image1, prompt, variation_index=0, max_retries=3):
539
+ retry_count = 0
540
+ last_error = None
541
+ while retry_count < max_retries:
542
+ try:
543
+ images = [image1]
544
+ valid_images = [img for img in images if img is not None]
545
+ if not valid_images:
546
+ return None, "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.", ""
547
+ final_prompt = prompt.strip()
548
+ result_img, status = generate_with_images(final_prompt, valid_images, variation_index)
549
+
550
+ # ์ƒํƒœ ์ •๋ณด์—์„œ ํ”„๋กฌํ”„ํŠธ ์ •๋ณด ์ œ๊ฑฐ
551
+ if result_img is not None:
552
+ if "์ด๋ฏธ์ง€๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค" in status:
553
+ status = "์ด๋ฏธ์ง€๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
554
+ return result_img, status, final_prompt
555
+ else:
556
+ last_error = status
557
+ retry_count += 1
558
+ logger.warning(f"์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹คํŒจ, ์žฌ์‹œ๋„ {retry_count}/{max_retries}: {status}")
559
+ time.sleep(1)
560
+ except Exception as e:
561
+ last_error = str(e)
562
+ retry_count += 1
563
+ logger.exception(f"์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ, ์žฌ์‹œ๋„ {retry_count}/{max_retries}:")
564
+ time.sleep(1)
565
+ return None, f"์ตœ๋Œ€ ์žฌ์‹œ๋„ ํšŸ์ˆ˜({max_retries}ํšŒ) ์ดˆ๊ณผ ํ›„ ์‹คํŒจ: {last_error}", prompt
566
+
567
+ # ------------------- ํ”„๋กฌํ”„ํŠธ ๊ด€๋ จ ํ•จ์ˆ˜ -------------------
568
+ def filter_prompt_only(prompt):
569
+ """Gemini์˜ ์„ค๋ช… ๋ฐ ๋ถˆํ•„์š”ํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  ์‹ค์ œ ํ”„๋กฌํ”„ํŠธ๋งŒ ์ถ”์ถœํ•˜๋Š” ํ•จ์ˆ˜"""
570
+ code_block_pattern = r"```\s*(.*?)```"
571
+ code_match = re.search(code_block_pattern, prompt, re.DOTALL)
572
+ if code_match:
573
+ return code_match.group(1).strip()
574
+
575
+ if "--ar 1:1" in prompt:
576
+ lines = prompt.split('\n')
577
+ prompt_lines = []
578
+ in_prompt = False
579
+ for line in lines:
580
+ if (not in_prompt and
581
+ ("product" in line.lower() or
582
+ "magazine" in line.lower() or
583
+ "commercial" in line.lower() or
584
+ "photography" in line.lower())):
585
+ in_prompt = True
586
+ prompt_lines.append(line)
587
+ elif in_prompt:
588
+ if "explanation" in line.lower() or "let me know" in line.lower():
589
+ break
590
+ prompt_lines.append(line)
591
+ if prompt_lines:
592
+ return '\n'.join(prompt_lines).strip()
593
+
594
+ return prompt.strip()
595
+
596
+ def get_selected_background_info(bg_type, simple, studio, nature, indoor, special):
597
+ """์„ ํƒ๋œ ๋ฐฐ๊ฒฝ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ํ•จ์ˆ˜"""
598
+ if bg_type == "์‹ฌํ”Œ ๋ฐฐ๊ฒฝ":
599
+ return {
600
+ "category": "์‹ฌํ”Œ ๋ฐฐ๊ฒฝ",
601
+ "name": simple,
602
+ "english": SIMPLE_BACKGROUNDS.get(simple, "white background")
603
+ }
604
+ elif bg_type == "์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ":
605
+ return {
606
+ "category": "์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ",
607
+ "name": studio,
608
+ "english": STUDIO_BACKGROUNDS.get(studio, "product photography studio")
609
+ }
610
+ elif bg_type == "์ž์—ฐ ํ™˜๊ฒฝ":
611
+ return {
612
+ "category": "์ž์—ฐ ํ™˜๊ฒฝ",
613
+ "name": nature,
614
+ "english": NATURE_BACKGROUNDS.get(nature, "natural environment")
615
+ }
616
+ elif bg_type == "์‹ค๋‚ด ํ™˜๊ฒฝ":
617
+ return {
618
+ "category": "์‹ค๋‚ด ํ™˜๊ฒฝ",
619
+ "name": indoor,
620
+ "english": INDOOR_BACKGROUNDS.get(indoor, "indoor environment")
621
+ }
622
+ elif bg_type == "ํŠน์ˆ˜๋ฐฐ๊ฒฝ":
623
+ return {
624
+ "category": "ํŠน์ˆ˜๋ฐฐ๊ฒฝ",
625
+ "name": special,
626
+ "english": SPECIAL_BACKGROUNDS.get(special, "special background")
627
+ }
628
+ else:
629
+ return {
630
+ "category": "๊ธฐ๋ณธ ๋ฐฐ๊ฒฝ",
631
+ "name": "ํ™”์ดํŠธ ๋ฐฐ๊ฒฝ",
632
+ "english": "white background"
633
+ }
634
+
635
+ def generate_enhanced_system_instruction():
636
+ """ํ–ฅ์ƒ๋œ ์‹œ์Šคํ…œ ์ธ์ŠคํŠธ๋Ÿญ์…˜ ์ƒ์„ฑ ํ•จ์ˆ˜"""
637
+ return """๋‹น์‹ ์€ ์ƒํ’ˆ ์ด๋ฏธ์ง€์˜ ๋ฐฐ๊ฒฝ์„ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•œ ์ตœ๊ณ  ํ’ˆ์งˆ์˜ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.
638
+ ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ƒํ’ˆ๋ช…, ๋ฐฐ๊ฒฝ ์œ ํ˜•, ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ์„ ๋ฐ”ํƒ•์œผ๋กœ ๋ฏธ๋“œ์ €๋‹ˆ(Midjourney)์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ƒ์„ธํ•˜๊ณ  ์ „๋ฌธ์ ์ธ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์˜์–ด๋กœ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”.
639
+ ๋‹ค์Œ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ๋ฐ˜๋“œ์‹œ ์ค€์ˆ˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:
640
+ 1. ์ƒํ’ˆ์„ "#1"๋กœ ์ง€์ •ํ•˜์—ฌ ์ฐธ์กฐํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: "skincare tube (#1)")
641
+ 2. *** ๋งค์šฐ ์ค‘์š”: ์ƒํ’ˆ์˜ ์›๋ž˜ ํŠน์„ฑ(๋””์ž์ธ, ์ƒ‰์ƒ, ํ˜•ํƒœ, ๋กœ๊ณ , ํŒจํ‚ค์ง€ ๋“ฑ)์€ ์–ด๋–ค ์ƒํ™ฉ์—์„œ๋„ ์ ˆ๋Œ€ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ***
642
+ 3. *** ์ƒํ’ˆ์˜ ๋ณธ์งˆ์  ํŠน์„ฑ์„ ์œ ์ง€ํ•˜๋˜, ์ƒํ’ˆ์— ํฌ์ปค์Šค๋ฅผ ๋งž์ถฐ ๋ชจ๋“  ์„ธ๋ถ€ ์‚ฌํ•ญ์ด ์„ ๋ช…ํ•˜๊ฒŒ ๋“œ๋Ÿฌ๋‚˜๋„๋ก ํ•˜๋ฉฐ,
643
+ 8K ํ•ด์ƒ๋„(8K resolution), ์˜ค๋ฒ„์ƒคํ”„๋‹ ์—†๋Š” ์ดˆ๊ณ ํ™”์งˆ(ultra high definition without oversharpening)๋กœ ๋ Œ๋”๋ง๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ***
644
+ 4. ์ด๋ฏธ์ง€ ๋น„์œจ์€ ์ •ํ™•ํžˆ 1:1(์ •์‚ฌ๊ฐํ˜•) ํ˜•์‹์œผ๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กฌํ”„ํŠธ์— "square format", "1:1 ratio" ๋˜๋Š” "aspect ratio 1:1"์„ ๋ช…์‹œ์ ์œผ๋กœ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
645
+ 5. ์ƒํ’ˆ์€ ๋ฐ˜๋“œ์‹œ ์ •์‚ฌ๊ฐํ˜• ๊ตฌ๋„์˜ ์ •์ค‘์•™์— ๋ฐฐ์น˜๋˜์–ด์•ผ ํ•˜๋ฉฐ, ์ ์ ˆํ•œ ํฌ๊ธฐ๋กœ ํ‘œํ˜„ํ•˜์—ฌ ๋””ํ…Œ์ผ์ด ์™„๋ฒฝํ•˜๊ฒŒ ๋ณด์ด๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
646
+ 6. ์ƒํ’ˆ์„ ์ด๋ฏธ์ง€์˜ ์ฃผ์š” ์ดˆ์ ์œผ๋กœ ๋ถ€๊ฐ์‹œํ‚ค๊ณ , ์ƒํ’ˆ์˜ ๋น„์œจ์ด ์ „์ฒด ์ด๋ฏธ์ง€์—์„œ 60-70% ์ด์ƒ ์ฐจ์ง€ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
647
+ 7. ์กฐ๋ช… ์„ค๋ช…์„ ๋งค์šฐ ๊ตฌ์ฒด์ ์œผ๋กœ ํ•ด์ฃผ์„ธ์š”. ์˜ˆ: "soft directional lighting from left side", "dramatic rim lighting", "diffused natural light through windows"
648
+ 8. ๋ฐฐ๊ฒฝ์˜ ์žฌ์งˆ๊ณผ ์งˆ๊ฐ์„ ์ƒ์„ธํžˆ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”. ์˜ˆ: "polished marble surface", "rustic wooden table with visible grain", "matte concrete wall with subtle texture"
649
+ 9. ํ”„๋กฌํ”„ํŠธ์— ๋‹ค์Œ ์š”์†Œ๋“ค์„ ๋ช…์‹œ์ ์œผ๋กœ ํฌํ•จํ•˜๋˜, ์‚ฌ์šฉ ๋งฅ๋ฝ์— ์ ์ ˆํ•˜๊ฒŒ ๋ณ€ํ˜•ํ•˜์„ธ์š”:
650
+ - "award-winning product photography"
651
+ - "magazine-worthy commercial product shot"
652
+ - "professional advertising imagery with perfect exposure"
653
+ - "studio lighting with color-accurate rendering"
654
+ - "8K ultra high definition product showcase"
655
+ - "commercial product photography with precise detail rendering"
656
+ - "ultra high definition"
657
+ - "crystal clear details"
658
+ 10. ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ ๊ตฌ์ฒด์ ์ธ ๋ฐฐ๊ฒฝ๊ณผ ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ์„ ํ”„๋กฌํ”„ํŠธ์— ์ •ํ™•ํžˆ ๋ฐ˜์˜ํ•˜๊ณ  ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค.
659
+ 11. ํ”„๋กฌํ”„ํŠธ ๋์— ๋ฏธ๋“œ์ €๋‹ˆ ํŒŒ๋ผ๋ฏธํ„ฐ "--ar 1:1 --s 750 --q 2 --v 5.2" ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฏธ๋“œ์ €๋‹ˆ์—์„œ ๊ณ ํ’ˆ์งˆ ์ •์‚ฌ๊ฐํ˜• ๋น„์œจ์„ ๊ฐ•์ œํ•ฉ๋‹ˆ๋‹ค.
660
+ 12. ๋งค์šฐ ์ค‘์š”: ํ”„๋กฌํ”„ํŠธ ์™ธ์— ๋‹ค๋ฅธ ์„ค๋ช…์ด๋‚˜ ๋ฉ”ํƒ€ ํ…์ŠคํŠธ๋ฅผ ํฌํ•จํ•˜์ง€ ๋งˆ์„ธ์š”. ์˜ค์ง ํ”„๋กฌํ”„ํŠธ ์ž์ฒด๋งŒ ์ œ๊ณตํ•˜์„ธ์š”.
661
+ """
662
+
663
+ def generate_prompt_with_gemini(product_name, background_info, additional_info=""):
664
+ """ํ–ฅ์ƒ๋œ ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ํ•จ์ˆ˜"""
665
+ api_key = get_api_key()
666
+ if not api_key:
667
+ return "Gemini API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค."
668
+
669
+ try:
670
+ genai_generative.configure(api_key=api_key)
671
+
672
+ prompt_request = f"""
673
+ ์ƒํ’ˆ๋ช…: {product_name}
674
+ ๋ฐฐ๊ฒฝ ์œ ํ˜•: {background_info.get('english', 'studio')}
675
+ ๋ฐฐ๊ฒฝ ์นดํ…Œ๊ณ ๋ฆฌ: {background_info.get('category', '')}
676
+ ๋ฐฐ๊ฒฝ ์ด๋ฆ„: {background_info.get('name', '')}
677
+ ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ: {additional_info}
678
+ ์ค‘์š” ์š”๊ตฌ์‚ฌํ•ญ:
679
+ 1. ์ƒํ’ˆ(#1)์ด ์ด๋ฏธ์ง€ ๊ตฌ๋„์—์„œ ์ค‘์‹ฌ์ ์ธ ์œ„์น˜๋ฅผ ์ฐจ์ง€ํ•˜๋ฉฐ ์ ์ ˆํ•œ ํฌ๊ธฐ(์ด๋ฏธ์ง€์˜ 60-70%)๋กœ ํ‘œํ˜„๋˜๋„๋ก ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”.
680
+ 2. ์ด๋ฏธ์ง€๋Š” ์ •ํ™•ํžˆ 1:1 ๋น„์œจ(์ •์‚ฌ๊ฐํ˜•)์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
681
+ 3. ์ƒํ’ˆ์˜ ๋””์ž์ธ, ์ƒ‰์ƒ, ํ˜•ํƒœ, ๋กœ๊ณ  ๋“ฑ ๋ณธ์งˆ์  ํŠน์„ฑ์€ ์ ˆ๋Œ€ ์ˆ˜์ •ํ•˜์ง€ ๋งˆ์„ธ์š”.
682
+ 4. ๊ตฌ์ฒด์ ์ธ ์กฐ๋ช… ๊ธฐ๋ฒ•์„ ์ƒ์„ธํžˆ ๋ช…์‹œํ•ด์ฃผ์„ธ์š”:
683
+ - ์ •ํ™•ํ•œ ์กฐ๋ช… ์œ„์น˜ (์˜ˆ: "45-degree key light from upper left")
684
+ - ์กฐ๋ช… ํ’ˆ์งˆ (์˜ˆ: "soft diffused light", "hard directional light")
685
+ - ์กฐ๋ช… ๊ฐ•๋„์™€ ์ƒ‰์˜จ๋„ (์˜ˆ: "warm tungsten key light with cool blue fill")
686
+ - ๋ฐ˜์‚ฌ์™€ ๊ทธ๋ฆผ์ž ์ฒ˜๋ฆฌ ๋ฐฉ์‹ (์˜ˆ: "controlled specular highlights with soft shadow transitions")
687
+ 5. ์ƒํ’ˆ์„ ๋” ๋‹๋ณด์ด๊ฒŒ ํ•˜๋Š” ๋ณด์กฐ ์š”์†Œ(props)๋ฅผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํ™œ์šฉํ•˜๋˜, ์ƒํ’ˆ์ด ํ•ญ์ƒ ์ฃผ์ธ๊ณต์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
688
+ 6. ๋ฐฐ๊ฒฝ ์žฌ์งˆ๊ณผ ํ‘œ๋ฉด ์งˆ๊ฐ์„ ๊ตฌ์ฒด์ ์œผ๋กœ ์„ค๋ช…ํ•˜๊ณ , ์ƒํ’ˆ๊ณผ์˜ ์ƒํ˜ธ์ž‘์šฉ ๋ฐฉ์‹์„ ๋ช…์‹œํ•ด์ฃผ์„ธ์š”.
689
+ 7. ์ƒ‰์ƒ ๊ตฌ์„ฑ(color palette, color harmonies)์„ ๋ช…ํ™•ํžˆ ํ•ด์ฃผ์„ธ์š”.
690
+ 8. ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ์ƒ์—… ๊ด‘๊ณ  ํ’ˆ์งˆ์˜ ์ด๋ฏธ์ง€๊ฐ€ ๋˜๋„๋ก ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.
691
+ 9. ํ”„๋กฌํ”„ํŠธ ๋์— ๋ฏธ๋“œ์ €๋‹ˆ ํŒŒ๋ผ๋ฏธํ„ฐ "--ar 1:1 --s 750 --q 2 --v 5.2"๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”.
692
+ ํ•œ๊ตญ์–ด ์ž…๋ ฅ ๋‚ด์šฉ์„ ์ „๋ฌธ์ ์ธ ์˜์–ด๋กœ ๋ฒˆ์—ญํ•˜์—ฌ ๋ฐ˜์˜ํ•ด์ฃผ์„ธ์š”.
693
+ """
694
+ model = genai_generative.GenerativeModel(
695
+ 'gemini-2.0-flash',
696
+ system_instruction=generate_enhanced_system_instruction()
697
+ )
698
+
699
+ response = model.generate_content(
700
+ prompt_request,
701
+ generation_config=genai_generative.types.GenerationConfig(
702
+ temperature=0.8,
703
+ top_p=0.97,
704
+ top_k=64,
705
+ max_output_tokens=1600,
706
+ )
707
+ )
708
+
709
+ response_text = response.text.strip()
710
+
711
+ if "--ar 1:1" not in response_text:
712
+ response_text = response_text.rstrip(".") + ". --ar 1:1 --s 750 --q 2 --v 5.2"
713
+
714
+ return response_text
715
+ except Exception as e:
716
+ return f"ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}"
717
+
718
+ # ------------------- ๋‹จ์ผ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ•จ์ˆ˜ -------------------
719
+ def generate_product_image(image, bg_type, simple, studio, nature, indoor, special, product_name, additional_info):
720
+ if image is None:
721
+ return None, "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.", "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œ ํ›„ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”."
722
+ product_name = product_name.strip() or "์ œํ’ˆ"
723
+ background_info = get_selected_background_info(bg_type, simple, studio, nature, indoor, special)
724
+ generated_prompt = generate_prompt_with_gemini(product_name, background_info, additional_info)
725
+ if "Gemini API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค" in generated_prompt:
726
+ warning_msg = (
727
+ "[Gemini API ํ‚ค ๋ˆ„๋ฝ]\n"
728
+ "API ํ‚ค ์„ค์ • ๋ฐฉ๋ฒ•:\n"
729
+ "1. ํ™˜๊ฒฝ ๋ณ€์ˆ˜: export GEMINI_API_KEY_1=\"your-api-key-1\"\n"
730
+ "2. ํ™˜๊ฒฝ ๋ณ€์ˆ˜: export GEMINI_API_KEY_2=\"your-api-key-2\"\n"
731
+ "3. ํ™˜๊ฒฝ ๋ณ€์ˆ˜: export GEMINI_API_KEY_3=\"your-api-key-3\"\n"
732
+ "ํ‚ค ๋ฐœ๊ธ‰: https://aistudio.google.com/apikey"
733
+ )
734
+ return None, warning_msg, warning_msg
735
+ final_prompt = filter_prompt_only(generated_prompt)
736
+ result_image, status, _ = process_images_with_prompt(image, final_prompt, 0)
737
+ return result_image, status, final_prompt
738
+
739
+ # ------------------- 4์žฅ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ•จ์ˆ˜ -------------------
740
+ def generate_product_images(image, bg_type, simple, studio, nature, indoor, special, product_name, additional_info):
741
+ if image is None:
742
+ return None, None, None, None, "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”.", "์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œ ํ›„ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”."
743
+ product_name = product_name.strip() or "์ œํ’ˆ"
744
+ background_info = get_selected_background_info(bg_type, simple, studio, nature, indoor, special)
745
+ generated_prompt = generate_prompt_with_gemini(product_name, background_info, additional_info)
746
+ if "Gemini API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค" in generated_prompt:
747
+ warning_msg = (
748
+ "[Gemini API ํ‚ค ๋ˆ„๋ฝ]\n"
749
+ "API ํ‚ค ์„ค์ • ๋ฐฉ๋ฒ•:\n"
750
+ "1. ํ™˜๊ฒฝ ๋ณ€์ˆ˜: export GEMINI_API_KEY_1=\"your-api-key-1\"\n"
751
+ "2. ํ™˜๊ฒฝ ๋ณ€์ˆ˜: export GEMINI_API_KEY_2=\"your-api-key-2\"\n"
752
+ "3. ํ™˜๊ฒฝ ๋ณ€์ˆ˜: export GEMINI_API_KEY_3=\"your-api-key-3\"\n"
753
+ "ํ‚ค ๋ฐœ๊ธ‰: https://aistudio.google.com/apikey"
754
+ )
755
+ return None, None, None, None, warning_msg, warning_msg
756
+ final_prompt = filter_prompt_only(generated_prompt)
757
+ images_list = []
758
+ statuses = []
759
+ for i in range(4):
760
+ result_img, status, _ = process_images_with_prompt(image, final_prompt, variation_index=i)
761
+ images_list.append(result_img)
762
+ statuses.append(f"์ด๋ฏธ์ง€ #{i+1}: {status}")
763
+ time.sleep(1)
764
+ combined_status = "\n".join(statuses)
765
+ return images_list[0], images_list[1], images_list[2], images_list[3], combined_status, final_prompt
766
+
767
+ # ------------------- ์˜ˆ์‹œ ํƒญ์„ ์œ„ํ•œ ํ•จ์ˆ˜ -------------------
768
+ def load_image_cached(image_path):
769
+ """์ด๋ฏธ์ง€๋ฅผ ์บ์‹œํ•˜์—ฌ ๋กœ๋“œํ•˜๋Š” ํ•จ์ˆ˜"""
770
+ global IMAGE_CACHE
771
+ if image_path not in IMAGE_CACHE:
772
+ try:
773
+ img = Image.open(image_path)
774
+ # ํฐ ์ด๋ฏธ์ง€๋Š” ๋ฏธ๋ฆฌ ๋ฆฌ์‚ฌ์ด์ฆˆํ•˜์—ฌ ์บ์‹œ
775
+ if max(img.size) > 1000:
776
+ ratio = 1000 / max(img.size)
777
+ new_size = (int(img.size[0] * ratio), int(img.size[1] * ratio))
778
+ img = img.resize(new_size, Image.Resampling.LANCZOS)
779
+ IMAGE_CACHE[image_path] = img
780
+ except Exception as e:
781
+ logger.error(f"์ด๋ฏธ์ง€ ๋กœ๋“œ ์‹คํŒจ: {image_path}, ์—๋Ÿฌ: {e}")
782
+ return None
783
+ return IMAGE_CACHE[image_path]
784
+
785
+ def preload_example_images():
786
+ """์˜ˆ์‹œ ์ด๋ฏธ์ง€๋“ค์„ ๋ฏธ๋ฆฌ ๋กœ๋“œํ•˜๋Š” ํ•จ์ˆ˜"""
787
+ for example in product_background_examples:
788
+ load_image_cached(example[0]) # ์ž…๋ ฅ ์ด๋ฏธ์ง€
789
+ load_image_cached(example[5]) # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€
790
+
791
+ def load_example(evt: gr.SelectData):
792
+ """์„ ํƒ๋œ ์˜ˆ์‹œ์˜ ์ •๋ณด๋ฅผ ๋กœ๋“œํ•˜๋Š” ํ•จ์ˆ˜"""
793
+ selected_example = product_background_examples[evt.index]
794
+ return (
795
+ selected_example[0], # ์ž…๋ ฅ ์ด๋ฏธ์ง€
796
+ selected_example[1], # ๋ฐฐ๊ฒฝ ์œ ํ˜•
797
+ selected_example[2], # ๋ฐฐ๊ฒฝ ์„ ํƒ
798
+ selected_example[3], # ์ƒํ’ˆ๋ช…
799
+ selected_example[4], # ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ
800
+ selected_example[5] # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€
801
+ )
802
+
803
+ # ------------------- Gradio ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌ์„ฑ -------------------
804
+ def create_app():
805
+ dropdown_options = initialize_dropdowns()
806
+
807
+ with gr.Blocks(css=custom_css, theme=gr.themes.Default(
808
+ primary_hue="orange",
809
+ secondary_hue="orange",
810
+ font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"]
811
+ )) as demo:
812
+ gr.HTML(fontawesome_link)
813
+ # ์ œ๋ชฉ๊ณผ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ ์ œ๊ฑฐ
814
+ # gr.HTML(header_html)
815
+ # gr.HTML(guide_html)
816
+
817
+ with gr.Tabs():
818
+ # ์ฒซ ๋ฒˆ์งธ ํƒญ: ์ด๋ฏธ์ง€ ์ƒ์„ฑ
819
+ with gr.TabItem("์ด๋ฏธ์ง€ ์ƒ์„ฑ"):
820
+ with gr.Row():
821
+ with gr.Column(scale=1):
822
+ # API ํ‚ค ์ž…๋ ฅ ์„น์…˜ ์ œ๊ฑฐ
823
+
824
+ # ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฐ ์„ค์ • ์„น์…˜
825
+ with gr.Column(elem_classes="custom-frame"):
826
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/3097/3097412.png"> ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฐ ์„ค์ •</div>')
827
+ product_name = gr.Textbox(
828
+ label="์ƒํ’ˆ๋ช… (ํ•œ๊ตญ์–ด ์ž…๋ ฅ)",
829
+ placeholder="์˜ˆ: ์Šคํ‚จ์ผ€์–ด ํŠœ๋ธŒ, ์Šค๋งˆํŠธ์›Œ์น˜, ํ–ฅ์ˆ˜, ์šด๋™ํ™” ๋“ฑ",
830
+ interactive=True
831
+ )
832
+
833
+ image_input = gr.Image(
834
+ label="์ƒํ’ˆ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ",
835
+ type="pil",
836
+ elem_classes="image-container"
837
+ )
838
+
839
+ # ๋ฐฐ๊ฒฝ ์˜ต์…˜ ์„น์…˜
840
+ with gr.Column(elem_classes="custom-frame"):
841
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/4297/4297825.png"> ๋ฐฐ๊ฒฝ ์˜ต์…˜</div>')
842
+ background_type = gr.Radio(
843
+ choices=["์‹ฌํ”Œ ๋ฐฐ๊ฒฝ", "์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ", "์ž์—ฐ ํ™˜๊ฒฝ", "์‹ค๋‚ด ํ™˜๊ฒฝ", "ํŠน์ˆ˜๋ฐฐ๊ฒฝ"],
844
+ label="๋ฐฐ๊ฒฝ ์œ ํ˜•",
845
+ value="์‹ฌํ”Œ ๋ฐฐ๊ฒฝ"
846
+ )
847
+
848
+ # ๋“œ๋กญ๋‹ค์šด ์ปดํฌ๋„ŒํŠธ๋“ค
849
+ simple_dropdown = gr.Dropdown(
850
+ choices=dropdown_options["simple"],
851
+ value=dropdown_options["simple"][0] if dropdown_options["simple"] else None,
852
+ label="์‹ฌํ”Œ ๋ฐฐ๊ฒฝ ์„ ํƒ",
853
+ visible=True,
854
+ interactive=True
855
+ )
856
+
857
+ studio_dropdown = gr.Dropdown(
858
+ choices=dropdown_options["studio"],
859
+ value=dropdown_options["studio"][0] if dropdown_options["studio"] else None,
860
+ label="์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ ์„ ํƒ",
861
+ visible=False,
862
+ interactive=True
863
+ )
864
+
865
+ nature_dropdown = gr.Dropdown(
866
+ choices=dropdown_options["nature"],
867
+ value=dropdown_options["nature"][0] if dropdown_options["nature"] else None,
868
+ label="์ž์—ฐ ํ™˜๊ฒฝ ์„ ํƒ",
869
+ visible=False,
870
+ interactive=True
871
+ )
872
+
873
+ indoor_dropdown = gr.Dropdown(
874
+ choices=dropdown_options["indoor"],
875
+ value=dropdown_options["indoor"][0] if dropdown_options["indoor"] else None,
876
+ label="์‹ค๋‚ด ํ™˜๊ฒฝ ์„ ํƒ",
877
+ visible=False,
878
+ interactive=True
879
+ )
880
+
881
+ special_dropdown = gr.Dropdown(
882
+ choices=dropdown_options["special"],
883
+ value=dropdown_options["special"][0] if dropdown_options["special"] else None,
884
+ label="ํŠน์ˆ˜๋ฐฐ๊ฒฝ ์„ ํƒ",
885
+ visible=False,
886
+ interactive=True
887
+ )
888
+
889
+ additional_info = gr.Textbox(
890
+ label="์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ (์„ ํƒ์‚ฌํ•ญ)",
891
+ placeholder="์˜ˆ: ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ๋А๋‚Œ, ๋ฐ์€ ์กฐ๋ช…, ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ณด์กฐ ๊ฐ์ฒด ๋“ฑ",
892
+ lines=3,
893
+ interactive=True
894
+ )
895
+
896
+ # ๋“œ๋กญ๋‹ค์šด ๋ณ€๊ฒฝ ํ•จ์ˆ˜
897
+ def update_dropdowns(bg_type):
898
+ return {
899
+ simple_dropdown: gr.update(visible=(bg_type == "์‹ฌํ”Œ ๋ฐฐ๊ฒฝ")),
900
+ studio_dropdown: gr.update(visible=(bg_type == "์ŠคํŠœ๋””์˜ค ๋ฐฐ๊ฒฝ")),
901
+ nature_dropdown: gr.update(visible=(bg_type == "์ž์—ฐ ํ™˜๊ฒฝ")),
902
+ indoor_dropdown: gr.update(visible=(bg_type == "์‹ค๋‚ด ํ™˜๊ฒฝ")),
903
+ special_dropdown: gr.update(visible=(bg_type == "ํŠน์ˆ˜๋ฐฐ๊ฒฝ"))
904
+ }
905
+
906
+ background_type.change(
907
+ fn=update_dropdowns,
908
+ inputs=[background_type],
909
+ outputs=[simple_dropdown, studio_dropdown, nature_dropdown, indoor_dropdown, special_dropdown]
910
+ )
911
+
912
+ # ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์„น์…˜
913
+ with gr.Column(elem_classes="custom-frame"):
914
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/1022/1022293.png"> ์ด๋ฏธ์ง€ ์ƒ์„ฑ</div>')
915
+ with gr.Row():
916
+ single_btn = gr.Button("ํ”„๋กฌํ”„ํŠธ ๋ฐ ๋‹จ์ผ ์ด๋ฏธ์ง€ ์ƒ์„ฑ", elem_classes="custom-button")
917
+ multi_btn = gr.Button("ํ”„๋กฌํ”„ํŠธ ๋ฐ 4์žฅ ์ด๋ฏธ์ง€ ์ƒ์„ฑ", elem_classes="custom-button")
918
+
919
+ with gr.Column(scale=1):
920
+ # ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€ ์„น์…˜
921
+ with gr.Column(elem_classes="custom-frame"):
922
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/1375/1375106.png"> ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€</div>')
923
+ with gr.Row():
924
+ with gr.Column():
925
+ image_output1 = gr.Image(label="์ด๋ฏธ์ง€ #1", elem_classes="image-container", height=400, width=400)
926
+ image_output3 = gr.Image(label="์ด๋ฏธ์ง€ #3", elem_classes="image-container", height=400, width=400)
927
+ with gr.Column():
928
+ image_output2 = gr.Image(label="์ด๋ฏธ์ง€ #2", elem_classes="image-container", height=400, width=400)
929
+ image_output4 = gr.Image(label="์ด๋ฏธ์ง€ #4", elem_classes="image-container", height=400, width=400)
930
+
931
+ # ๊ฒฐ๊ณผ ์ •๋ณด ์„น์…˜
932
+ with gr.Column(elem_classes="custom-frame"):
933
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/3153/3153376.png"> ๊ฒฐ๊ณผ ์ •๋ณด</div>')
934
+ status_output = gr.Textbox(label="๊ฒฐ๊ณผ ์ •๋ณด", lines=3)
935
+
936
+ # ๋‘ ๋ฒˆ์งธ ํƒญ: ์˜ˆ์‹œ ๊ฒฐ๊ณผ ๋ณด๊ธฐ
937
+ with gr.TabItem("์˜ˆ์‹œ ๊ฒฐ๊ณผ ๋ณด๊ธฐ"):
938
+ # ์ƒํ’ˆ ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์˜ˆ์‹œ ๊ฐค๋Ÿฌ๋ฆฌ ์„น์…˜
939
+ with gr.Column(elem_classes="custom-frame"):
940
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/681/681443.png"> ์ƒํ’ˆ ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์˜ˆ์‹œ ๊ฐค๋Ÿฌ๋ฆฌ</div>')
941
+
942
+ # ์ƒ๋‹จ ๋ฉ”์ธ ๋ทฐ ์˜์—ญ
943
+ with gr.Row():
944
+ example_input_image = gr.Image(
945
+ label="์ž…๋ ฅ ์ด๋ฏธ์ง€",
946
+ height=400,
947
+ width=400,
948
+ elem_classes="image-container",
949
+ show_label=True,
950
+ show_download_button=True,
951
+ container=True,
952
+ scale=1
953
+ )
954
+ with gr.Column(elem_classes="example-params"):
955
+ example_bg_type = gr.Textbox(label="๋ฐฐ๊ฒฝ ์œ ํ˜•", interactive=False)
956
+ example_bg_option = gr.Textbox(label="๋ฐฐ๊ฒฝ ์„ ํƒ", interactive=False)
957
+ example_product_name = gr.Textbox(label="์ƒํ’ˆ๋ช…", interactive=False)
958
+ example_additional_info = gr.Textbox(label="์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ", interactive=False)
959
+ example_output_image = gr.Image(
960
+ label="๊ฒฐ๊ณผ ์ด๋ฏธ์ง€",
961
+ height=400,
962
+ width=400,
963
+ elem_classes="image-container",
964
+ show_label=True,
965
+ show_download_button=True,
966
+ container=True,
967
+ scale=1
968
+ )
969
+
970
+ # ๋ฐฐ๊ฒฝ ์œ ํ˜•๋ณ„ ๊ฐค๋Ÿฌ๋ฆฌ ์„น์…˜
971
+ with gr.Column(elem_classes="custom-frame"):
972
+ gr.HTML('<div class="section-title"><img src="https://cdn-icons-png.flaticon.com/512/3068/3068327.png"> ๋ฐฐ๊ฒฝ ์œ ํ˜•๋ณ„ ์˜ˆ์‹œ</div>')
973
+
974
+ # ์˜ˆ์‹œ๋“ค์„ ๋ฐฐ๊ฒฝ ์œ ํ˜•๋ณ„๋กœ ๊ทธ๋ฃนํ™”
975
+ examples_by_type = {}
976
+ for example in product_background_examples:
977
+ bg_type = example[1] # ๋ฐฐ๊ฒฝ ์œ ํ˜•
978
+ if bg_type not in examples_by_type:
979
+ examples_by_type[bg_type] = []
980
+ examples_by_type[bg_type].append(example)
981
+
982
+ # ๋ฐฐ๊ฒฝ ์œ ํ˜•๋ณ„ ๊ฐค๋Ÿฌ๋ฆฌ ์„น์…˜ ์ƒ์„ฑ
983
+ for bg_type, examples in examples_by_type.items():
984
+ gr.Markdown(f"### {bg_type}")
985
+ # 10๊ฐœ์”ฉ ํ•œ ์ค„๋กœ ํ‘œ์‹œ
986
+ for i in range(0, len(examples), 10):
987
+ with gr.Row():
988
+ for j in range(10):
989
+ if i + j < len(examples):
990
+ example = examples[i + j]
991
+ with gr.Column(scale=1, min_width=100):
992
+ # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€๋งŒ ํ‘œ์‹œ
993
+ display_img = gr.Image(
994
+ value=example[5],
995
+ label=None, # ๋ ˆ์ด๋ธ” ์ œ๊ฑฐ
996
+ elem_classes="example-item",
997
+ height=120, # ํฌ๊ธฐ ์ถ•์†Œ
998
+ width=120, # ํฌ๊ธฐ ์ถ•์†Œ
999
+ show_label=False, # ๋ ˆ์ด๋ธ” ์ˆจ๊ธฐ๊ธฐ
1000
+ show_download_button=False, # ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ ์ˆจ๊ธฐ๊ธฐ
1001
+ show_share_button=False, # ๊ณต์œ  ๋ฒ„ํŠผ ์ˆจ๊ธฐ๊ธฐ
1002
+ container=False # ์ปจํ…Œ์ด๋„ˆ ํ…Œ๋‘๋ฆฌ ์ œ๊ฑฐ
1003
+ )
1004
+
1005
+ def make_example_handler(ex):
1006
+ def handler():
1007
+ # ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ๋ฅผ ์œ„ํ•œ ์ค‘๊ฐ„ ์—…๋ฐ์ดํŠธ
1008
+ yield (
1009
+ gr.update(value=None), # ์ž„์‹œ๋กœ ์ด๋ฏธ์ง€ ๋น„์šฐ๊ธฐ
1010
+ gr.update(value="๋กœ๋”ฉ ์ค‘..."),
1011
+ gr.update(value="๋กœ๋”ฉ ์ค‘..."),
1012
+ gr.update(value="๋กœ๋”ฉ ์ค‘..."),
1013
+ gr.update(value="๋กœ๋”ฉ ์ค‘..."),
1014
+ gr.update(value=None)
1015
+ )
1016
+
1017
+ # ์‹ค์ œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ
1018
+ yield (
1019
+ ex[0], # ์ž…๋ ฅ ์ด๋ฏธ์ง€
1020
+ ex[1], # ๋ฐฐ๊ฒฝ ์œ ํ˜•
1021
+ ex[2], # ๋ฐฐ๊ฒฝ ์„ ํƒ
1022
+ ex[3], # ์ƒํ’ˆ๋ช…
1023
+ ex[4] if ex[4] else "(์—†์Œ)", # ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ
1024
+ ex[5] # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€
1025
+ )
1026
+ return handler
1027
+
1028
+ display_img.select(
1029
+ fn=make_example_handler(example),
1030
+ outputs=[
1031
+ example_input_image,
1032
+ example_bg_type,
1033
+ example_bg_option,
1034
+ example_product_name,
1035
+ example_additional_info,
1036
+ example_output_image
1037
+ ],
1038
+ queue=True # ์š”์ฒญ์„ ํ์— ๋„ฃ์–ด ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ
1039
+ )
1040
+ else:
1041
+ # ๋นˆ ๊ณต๊ฐ„ ์œ ์ง€
1042
+ with gr.Column(scale=1, min_width=100):
1043
+ pass
1044
+
1045
+ # ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ์ฒซ ๋ฒˆ์งธ ์˜ˆ์‹œ ์ž๋™ ํ‘œ์‹œ
1046
+ def load_first_example():
1047
+ if product_background_examples:
1048
+ first_example = product_background_examples[0]
1049
+ return (
1050
+ first_example[0], # ์ž…๋ ฅ ์ด๋ฏธ์ง€
1051
+ first_example[1], # ๋ฐฐ๊ฒฝ ์œ ํ˜•
1052
+ first_example[2], # ๋ฐฐ๊ฒฝ ์„ ํƒ
1053
+ first_example[3], # ์ƒํ’ˆ๋ช…
1054
+ first_example[4] if first_example[4] else "(์—†์Œ)", # ์ถ”๊ฐ€ ์š”์ฒญ์‚ฌํ•ญ
1055
+ first_example[5] # ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€
1056
+ )
1057
+ return None, "", "", "", "", None
1058
+
1059
+ demo.load(
1060
+ fn=load_first_example,
1061
+ outputs=[
1062
+ example_input_image,
1063
+ example_bg_type,
1064
+ example_bg_option,
1065
+ example_product_name,
1066
+ example_additional_info,
1067
+ example_output_image
1068
+ ]
1069
+ )
1070
+
1071
+ # ๋‹จ์ผ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ•จ์ˆ˜ (API ํ‚ค ํŒŒ๋ผ๋ฏธํ„ฐ ์ œ๊ฑฐ)
1072
+ def modified_single_image_gen(image, bg_type, simple, studio, nature, indoor, special, product_name, additional_info):
1073
+ actual_api_key = get_api_key()
1074
+ if actual_api_key:
1075
+ os.environ["GEMINI_API_KEY"] = actual_api_key # ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์—…๋ฐ์ดํŠธ
1076
+ else:
1077
+ return None, None, None, None, "API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— GEMINI_API_KEY_1, GEMINI_API_KEY_2, GEMINI_API_KEY_3 ์ค‘ ํ•˜๋‚˜ ์ด์ƒ์„ ์„ค์ •ํ•ด์ฃผ์„ธ์š”."
1078
+ result_img, status, _ = generate_product_image(image, bg_type, simple, studio, nature, indoor, special, product_name, additional_info)
1079
+ return result_img, None, None, None, status
1080
+
1081
+ # ๋‹จ์ผ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ
1082
+ single_btn.click(
1083
+ fn=modified_single_image_gen,
1084
+ inputs=[image_input, background_type, simple_dropdown, studio_dropdown, nature_dropdown, indoor_dropdown, special_dropdown, product_name, additional_info],
1085
+ outputs=[image_output1, image_output2, image_output3, image_output4, status_output]
1086
+ )
1087
+
1088
+ # 4์žฅ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ•จ์ˆ˜ (API ํ‚ค ํŒŒ๋ผ๋ฏธํ„ฐ ์ œ๊ฑฐ)
1089
+ def modified_multi_image_gen(image, bg_type, simple, studio, nature, indoor, special, product_name, additional_info):
1090
+ actual_api_key = get_api_key()
1091
+ if actual_api_key:
1092
+ os.environ["GEMINI_API_KEY"] = actual_api_key # ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์—…๋ฐ์ดํŠธ
1093
+ else:
1094
+ return None, None, None, None, "API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— GEMINI_API_KEY_1, GEMINI_API_KEY_2, GEMINI_API_KEY_3 ์ค‘ ํ•˜๋‚˜ ์ด์ƒ์„ ์„ค์ •ํ•ด์ฃผ์„ธ์š”."
1095
+
1096
+ img1, img2, img3, img4, combined_status, _ = generate_product_images(image, bg_type, simple, studio, nature, indoor, special, product_name, additional_info)
1097
+
1098
+ # ์ƒํƒœ ์ •๋ณด์—์„œ ํ”„๋กฌํ”„ํŠธ ์ •๋ณด ์ œ๊ฑฐ
1099
+ cleaned_statuses = []
1100
+ for status_line in combined_status.split('\n'):
1101
+ if "์ด๋ฏธ์ง€ #" in status_line:
1102
+ if "์ด๋ฏธ์ง€๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค" in status_line:
1103
+ cleaned_statuses.append(status_line.split(":")[0] + ": ์ด๋ฏธ์ง€๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
1104
+ else:
1105
+ cleaned_statuses.append(status_line)
1106
+
1107
+ cleaned_combined_status = "\n".join(cleaned_statuses)
1108
+
1109
+ return img1, img2, img3, img4, cleaned_combined_status
1110
+
1111
+ # 4์žฅ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ
1112
+ multi_btn.click(
1113
+ fn=modified_multi_image_gen,
1114
+ inputs=[image_input, background_type, simple_dropdown, studio_dropdown, nature_dropdown, indoor_dropdown, special_dropdown, product_name, additional_info],
1115
+ outputs=[image_output1, image_output2, image_output3, image_output4, status_output]
1116
+ )
1117
+
1118
+ return demo
1119
+
1120
+ # ------------------- ๋ฉ”์ธ ์‹คํ–‰ ํ•จ์ˆ˜ -------------------
1121
+ if __name__ == "__main__":
1122
+ initialize_backgrounds()
1123
+ initialize_api_keys() # API ํ‚ค ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜ ํ˜ธ์ถœ ์ถ”๊ฐ€
1124
+ preload_example_images() # ์˜ˆ์‹œ ์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ ๋กœ๋“œ
1125
+ app = create_app()
1126
+ app.queue(max_size=10) # ์š”์ฒญ์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ ์„ค์ •
1127
+ app.launch(share=False, inbrowser=True, width="100%")