Kims12 commited on
Commit
5ff01f8
ยท
verified ยท
1 Parent(s): ddd42e5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +78 -229
app.py CHANGED
@@ -28,7 +28,6 @@ def save_binary_file(file_name, data):
28
 
29
 
30
  def generate_image_from_prompt(prompt, model="gemini-2.0-flash-exp-image-generation"):
31
- """ํ…์ŠคํŠธ ํ”„๋กฌํ”„ํŠธ๋งŒ์œผ๋กœ ์ด๋ฏธ์ง€ ์ƒ์„ฑ"""
32
  logger.debug(f"generate_image_from_prompt ํ•จ์ˆ˜ ์‹œ์ž‘ - ํ”„๋กฌํ”„ํŠธ: '{prompt}'")
33
 
34
  try:
@@ -127,224 +126,17 @@ def generate_image_from_prompt(prompt, model="gemini-2.0-flash-exp-image-generat
127
  return None, f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}" # ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ None๊ณผ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ๋ฐ˜ํ™˜
128
 
129
 
130
- def merge_images(person_img_path, product_img_path, background_img_path, prompt, model="gemini-2.0-flash-exp-image-generation"):
131
- """๊ธฐ์กด ์ด๋ฏธ์ง€ ํ•ฉ์„ฑ ํ•จ์ˆ˜ (์ด์ „ ์ฝ”๋“œ์™€ ๋™์ผ)"""
132
- logger.debug(f"merge_images ํ•จ์ˆ˜ ์‹œ์ž‘ - ํ”„๋กฌํ”„ํŠธ: '{prompt}'")
133
-
134
  try:
135
- # API ํ‚ค๋Š” ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ๋ถˆ๋Ÿฌ์˜ด
136
- effective_api_key = os.environ.get("GEMINI_API_KEY")
137
- if effective_api_key:
138
- logger.debug("ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ API ํ‚ค ๋ถˆ๋Ÿฌ์˜ด")
139
- else:
140
- logger.error("API ํ‚ค๊ฐ€ ํ™˜๊ฒฝ๋ณ€์ˆ˜์— ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
141
- raise ValueError("API ํ‚ค๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.")
142
-
143
- client = genai.Client(api_key=effective_api_key)
144
- logger.debug("Gemini ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ.")
145
-
146
- # ์ด๋ฏธ์ง€ ํŒŒ์ผ ์—…๋กœ๋“œ
147
- person_file = client.files.upload(file=person_img_path)
148
- logger.debug(f"์‚ฌ๋žŒ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์™„๋ฃŒ: {person_file.uri}")
149
-
150
- product_file = client.files.upload(file=product_img_path)
151
- logger.debug(f"์ƒํ’ˆ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์™„๋ฃŒ: {product_file.uri}")
152
-
153
- # ์ปจํ…์ธ  ๋ฆฌ์ŠคํŠธ ์ƒ์„ฑ
154
- parts = []
155
 
156
- # ์‚ฌ๋žŒ ์ด๋ฏธ์ง€ ์ถ”๊ฐ€
157
- parts.append(
158
- types.Part.from_uri(
159
- file_uri=person_file.uri,
160
- mime_type=person_file.mime_type
161
- )
162
- )
163
-
164
- # ์ƒํ’ˆ ์ด๋ฏธ์ง€ ์ถ”๊ฐ€
165
- parts.append(
166
- types.Part.from_uri(
167
- file_uri=product_file.uri,
168
- mime_type=product_file.mime_type
169
- )
170
- )
171
-
172
- # ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€๊ฐ€ ์žˆ์œผ๋ฉด ์ถ”๊ฐ€
173
- if background_img_path:
174
- background_file = client.files.upload(file=background_img_path)
175
- logger.debug(f"๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์™„๋ฃŒ: {background_file.uri}")
176
- parts.append(
177
- types.Part.from_uri(
178
- file_uri=background_file.uri,
179
- mime_type=background_file.mime_type
180
- )
181
- )
182
- logger.debug("๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์ถ”๊ฐ€๋จ")
183
-
184
- # ๋งˆ์ง€๋ง‰์— ํ”„๋กฌํ”„ํŠธ ์ถ”๊ฐ€
185
- parts.append(
186
- types.Part.from_text(text=prompt)
187
- )
188
-
189
- logger.debug(f"์ปจํ…์ธ  ๊ฐ์ฒด ์ƒ์„ฑ ์™„๋ฃŒ: {len(parts)} ์•„์ดํ…œ")
190
-
191
- # ์ƒ์„ฑ ์„ค์ •
192
- generate_content_config = types.GenerateContentConfig(
193
- temperature=1,
194
- top_p=0.95,
195
- top_k=40,
196
- max_output_tokens=8192,
197
- response_modalities=["image", "text"],
198
- )
199
- logger.debug(f"์ƒ์„ฑ ์„ค์ •: {generate_content_config}")
200
 
201
- with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
202
- temp_path = tmp.name
203
- logger.debug(f"์ž„์‹œ ํŒŒ์ผ ์ƒ์„ฑ๋จ: {temp_path}")
204
-
205
- # ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฐฉ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ ์ˆ˜์‹ 
206
- response_stream = client.models.generate_content_stream(
207
- model=model,
208
- contents=[
209
- types.Content(
210
- role="user",
211
- parts=parts,
212
- ),
213
- ],
214
- config=generate_content_config,
215
- )
216
-
217
- logger.debug("์‘๋‹ต ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ ์‹œ์ž‘...")
218
-
219
- # ์‘๋‹ต์—์„œ ์ด๋ฏธ์ง€์™€ ํ…์ŠคํŠธ ์ถ”์ถœ
220
- image_saved = False
221
- response_text = ""
222
-
223
- for chunk in response_stream:
224
- logger.debug(f"chunk ์ˆ˜์‹ : {chunk}")
225
-
226
- # ์‘๋‹ต ๊ฒ€์ฆ
227
- if not hasattr(chunk, 'candidates') or not chunk.candidates or len(chunk.candidates) == 0:
228
- logger.warning("chunk์— candidates๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.")
229
- continue
230
-
231
- if not hasattr(chunk.candidates[0], 'content') or chunk.candidates[0].content is None:
232
- logger.warning("chunk.candidates[0]์— content๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.")
233
- continue
234
-
235
- if not hasattr(chunk.candidates[0].content, 'parts') or not chunk.candidates[0].content.parts:
236
- logger.warning("chunk.candidates[0].content์— parts๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.")
237
- continue
238
-
239
- for part in chunk.candidates[0].content.parts:
240
- if hasattr(part, 'text') and part.text:
241
- response_text += part.text
242
- logger.info(f"์ˆ˜์‹ ๋œ ํ…์ŠคํŠธ: {part.text}")
243
- elif hasattr(part, 'inline_data') and part.inline_data:
244
- save_binary_file(temp_path, part.inline_data.data)
245
- logger.info(f"MIME ํƒ€์ž… {part.inline_data.mime_type}์˜ ํŒŒ์ผ์ด ์ €์žฅ๋จ: {temp_path}")
246
- image_saved = True
247
-
248
- if not image_saved:
249
- logger.warning("์ด๋ฏธ์ง€๊ฐ€ ์ƒ์„ฑ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
250
- return None, response_text or "์ด๋ฏธ์ง€๊ฐ€ ์ƒ์„ฑ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ํ”„๋กฌํ”„ํŠธ๋‚˜ ์ด๋ฏธ์ง€๋กœ ์‹œ๋„ํ•ด๋ณด์„ธ์š”."
251
-
252
- logger.debug("์ด๋ฏธ์ง€ ์ƒ์„ฑ ์™„๋ฃŒ.")
253
- return temp_path, response_text
254
-
255
- except Exception as e:
256
- logger.exception("์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
257
- return None, f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}" # ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ None๊ณผ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ๋ฐ˜ํ™˜
258
-
259
-
260
- def process_images_and_prompt(person_pil, product_pil, background_pil, prompt):
261
- """
262
- ์ด๋ฏธ์ง€ ๋ฐ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜์—ฌ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜
263
-
264
- ์ด๋ฏธ์ง€ ์ž…๋ ฅ์ด ์—†๋Š” ๊ฒฝ์šฐ ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ์ด๋ฏธ์ง€ ์ƒ์„ฑ์œผ๋กœ ์ „ํ™˜
265
- ์ด๋ฏธ์ง€ ์ž…๋ ฅ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๊ธฐ์กด์˜ ์ด๋ฏธ์ง€ ํ•ฉ์„ฑ ๋กœ์ง ์œ ์ง€
266
- """
267
- logger.debug(f"process_images_and_prompt ํ•จ์ˆ˜ ์‹œ์ž‘ - ํ”„๋กฌํ”„ํŠธ: '{prompt}'")
268
-
269
- try:
270
- # ์ด๋ฏธ์ง€ ์ž…๋ ฅ์ด ๋ชจ๋‘ ์—†๋Š” ๊ฒฝ์šฐ ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ์ด๋ฏธ์ง€ ์ƒ์„ฑ
271
- if person_pil is None and product_pil is None and background_pil is None:
272
- logger.debug("์ด๋ฏธ์ง€ ์—†์Œ. ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ์ด๋ฏธ์ง€ ์ƒ์„ฑ์œผ๋กœ ์ „ํ™˜")
273
- result_path, response_text = generate_image_from_prompt(prompt)
274
-
275
- if result_path:
276
- try:
277
- result_img = Image.open(result_path)
278
- if result_img.mode == "RGBA":
279
- result_img = result_img.convert("RGB")
280
-
281
- return [result_img], response_text or "์ด๋ฏธ์ง€๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
282
- except Exception as e:
283
- logger.exception(f"๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜: {str(e)}")
284
- return [], f"๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜: {str(e)}"
285
- else:
286
- return [], response_text or "์ด๋ฏธ์ง€ ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."
287
-
288
- # ํ•„์ˆ˜ ์ด๋ฏธ์ง€ ํ™•์ธ (์ด๋ฏธ์ง€ ํ•ฉ์„ฑ ๋ชจ๋“œ)
289
- if person_pil is None:
290
- return [], "์‚ฌ๋žŒ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”."
291
- if product_pil is None:
292
- return [], "์ƒํ’ˆ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ด์ฃผ์„ธ์š”."
293
-
294
- # ๊ธฐ๋ณธ ํ”„๋กฌํ”„ํŠธ ์„ค์ • (๋น„์–ด์žˆ๋Š” ๊ฒฝ์šฐ)
295
- if not prompt or not prompt.strip():
296
- if background_pil:
297
- prompt = "์ด ๋ฐฐ๊ฒฝ์— ์ด ์‚ฌ๋žŒ์ด ์ด ์ƒํ’ˆ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ์Šต์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ณด์—ฌ์ฃผ์„ธ์š”. ์ƒํ’ˆ์„ ์ž˜ ๋ณด์ด๊ฒŒ ํ•ด์ฃผ์„ธ์š”. Create a natural composite image showing this person using this product in this background setting. Make sure the product is clearly visible."
298
- else:
299
- prompt = "์ด ์‚ฌ๋žŒ์ด ์ด ์ƒํ’ˆ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ์Šต์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ณด์—ฌ์ฃผ์„ธ์š”. ์ƒํ’ˆ์„ ์ž˜ ๋ณด์ด๊ฒŒ ํ•ด์ฃผ์„ธ์š”. Create a natural composite image showing this person using this product. Make sure the product is clearly visible."
300
-
301
- # ํ”„๋กฌํ”„ํŠธ์— ์˜์–ด๊ฐ€ ์—†์œผ๋ฉด ์˜์–ด ํ”„๋กฌํ”„ํŠธ ์ถ”๊ฐ€ (๋” ๋‚˜์€ ๊ฒฐ๊ณผ๋ฅผ ์œ„ํ•ด)
302
- if not any(ord(c) < 128 for c in prompt):
303
- if background_pil:
304
- prompt += " Create a realistic composite image of this person with this product in this background."
305
- else:
306
- prompt += " Create a realistic composite image of this person with this product."
307
-
308
- # ์ด๋ฏธ์ง€ ์ €์žฅ
309
- with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp_person:
310
- person_path = tmp_person.name
311
- # RGB๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ €์žฅ
312
- person_img = person_pil
313
- if person_img.mode != 'RGB':
314
- person_img = person_img.convert('RGB')
315
- person_img.save(person_path, 'JPEG')
316
- logger.debug(f"์‚ฌ๋žŒ ์ด๋ฏธ์ง€ ์ €์žฅ ์™„๋ฃŒ: {person_path}")
317
-
318
- with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp_product:
319
- product_path = tmp_product.name
320
- # RGB๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ €์žฅ
321
- product_img = product_pil
322
- if product_img.mode != 'RGB':
323
- product_img = product_img.convert('RGB')
324
- product_img.save(product_path, 'JPEG')
325
- logger.debug(f"์ƒํ’ˆ ์ด๋ฏธ์ง€ ์ €์žฅ ์™„๋ฃŒ: {product_path}")
326
-
327
- # ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์ €์žฅ (์žˆ๋Š” ๊ฒฝ์šฐ)
328
- background_path = None
329
- if background_pil is not None:
330
- with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp_bg:
331
- background_path = tmp_bg.name
332
- # RGB๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ €์žฅ
333
- background_img = background_pil
334
- if background_img.mode != 'RGB':
335
- background_img = background_img.convert('RGB')
336
- background_img.save(background_path, 'JPEG')
337
- logger.debug(f"๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ์ €์žฅ ์™„๋ฃŒ: {background_path}")
338
-
339
- # ์ด๋ฏธ์ง€ ํ•ฉ์„ฑ ์‹คํ–‰
340
- result_path, response_text = merge_images(
341
- person_img_path=person_path,
342
- product_img_path=product_path,
343
- background_img_path=background_path,
344
- prompt=prompt
345
- )
346
-
347
- # ์ด๋ฏธ์ง€ ๋ฐ˜ํ™˜ ๋ฐ ์ž„์‹œ ํŒŒ์ผ ์ •๋ฆฌ
348
  if result_path:
349
  logger.debug(f"์ด๋ฏธ์ง€ ์ƒ์„ฑ ์™„๋ฃŒ. ๊ฒฝ๋กœ: {result_path}")
350
  try:
@@ -352,23 +144,80 @@ def process_images_and_prompt(person_pil, product_pil, background_pil, prompt):
352
  if result_img.mode == "RGBA":
353
  result_img = result_img.convert("RGB")
354
 
355
- # ์ž„์‹œ ํŒŒ์ผ ์ •๋ฆฌ
356
- try:
357
- os.unlink(person_path)
358
- os.unlink(product_path)
359
- if background_path:
360
- os.unlink(background_path)
361
- except Exception as e:
362
- logger.warning(f"์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜: {str(e)}")
363
-
364
  return [result_img], response_text or "์ด๋ฏธ์ง€๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
365
  except Exception as e:
366
  logger.exception(f"๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜: {str(e)}")
367
  return [], f"๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜: {str(e)}"
368
  else:
369
- logger.error("merge_images ํ•จ์ˆ˜์—์„œ None ๋ฐ˜ํ™˜๋จ.")
370
- return [], response_text or "์ด๋ฏธ์ง€ ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์ด๋ฏธ์ง€๋‚˜ ํ”„๋กฌํ”„ํŠธ๋กœ ์‹œ๋„ํ•ด๋ณด์„ธ์š”."
371
 
372
  except Exception as e:
373
- logger.exception("process_images_and_prompt ํ•จ์ˆ˜์—์„œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
374
- return [], f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}" # ์˜ค๋ฅ˜ ์‹œ ๋นˆ ๋ฆฌ์ŠคํŠธ์™€ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ๋ฐ˜ํ™˜
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
 
30
  def generate_image_from_prompt(prompt, model="gemini-2.0-flash-exp-image-generation"):
 
31
  logger.debug(f"generate_image_from_prompt ํ•จ์ˆ˜ ์‹œ์ž‘ - ํ”„๋กฌํ”„ํŠธ: '{prompt}'")
32
 
33
  try:
 
126
  return None, f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}" # ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ None๊ณผ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ๋ฐ˜ํ™˜
127
 
128
 
129
+ def process_image_generation(person_pil, product_pil, background_pil, prompt):
130
+ logger.debug(f"process_image_generation ํ•จ์ˆ˜ ์‹œ์ž‘ - ํ”„๋กฌํ”„ํŠธ: '{prompt}'")
 
 
131
  try:
132
+ # ์ด๋ฏธ์ง€๋“ค์ด ์ œ๊ณต๋˜์—ˆ๋‹ค๋ฉด ๊ธฐ์กด ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌ
133
+ if person_pil is not None or product_pil is not None or background_pil is not None:
134
+ return process_images_and_prompt(person_pil, product_pil, background_pil, prompt)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
+ # ์ด๋ฏธ์ง€ ์—†์ด ํ”„๋กฌํ”„ํŠธ๋งŒ ์žˆ๋Š” ๊ฒฝ์šฐ
137
+ result_path, response_text = generate_image_from_prompt(prompt)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
+ # ์ด๋ฏธ์ง€ ๋ฐ˜ํ™˜ ๋ฐ ์ฒ˜๋ฆฌ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  if result_path:
141
  logger.debug(f"์ด๋ฏธ์ง€ ์ƒ์„ฑ ์™„๋ฃŒ. ๊ฒฝ๋กœ: {result_path}")
142
  try:
 
144
  if result_img.mode == "RGBA":
145
  result_img = result_img.convert("RGB")
146
 
 
 
 
 
 
 
 
 
 
147
  return [result_img], response_text or "์ด๋ฏธ์ง€๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
148
  except Exception as e:
149
  logger.exception(f"๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜: {str(e)}")
150
  return [], f"๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜: {str(e)}"
151
  else:
152
+ logger.error("generate_image_from_prompt ํ•จ์ˆ˜์—์„œ None ๋ฐ˜ํ™˜๋จ.")
153
+ return [], response_text or "์ด๋ฏธ์ง€ ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ํ”„๋กฌํ”„ํŠธ๋กœ ์‹œ๋„ํ•ด๋ณด์„ธ์š”."
154
 
155
  except Exception as e:
156
+ logger.exception("process_image_generation ํ•จ์ˆ˜์—์„œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:")
157
+ return [], f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}" # ์˜ค๋ฅ˜ ์‹œ ๋นˆ ๋ฆฌ์ŠคํŠธ์™€ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ๋ฐ˜ํ™˜
158
+
159
+
160
+ # --- Gradio ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌ์„ฑ (๊ธฐ์กด ์ฝ”๋“œ๋Š” ๋™์ผํ•˜๊ฒŒ ์œ ์ง€) ---
161
+ with gr.Blocks() as demo:
162
+ gr.HTML(
163
+ """
164
+ <div style='display: flex; align-items: center; justify-content: center; gap: 20px'>
165
+ <div style="background-color: var(--block-background-fill); border-radius: 8px">
166
+ <img src="https://www.gstatic.com/lamda/images/gemini_favicon_f069958c85030456e93de685481c559f160ea06b.png" style="width: 100px; height: 100px;">
167
+ </div>
168
+ <div>
169
+ <h1>Gemini๋ฅผ ์ด์šฉํ•œ ์ด๋ฏธ์ง€ ์ƒ์„ฑ</h1>
170
+ <p>์‚ฌ๋žŒ, ์ƒํ’ˆ, ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€๋ฅผ ํ•ฉ์„ฑํ•˜๊ฑฐ๋‚˜ ํ…์ŠคํŠธ๋กœ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.</p>
171
+ </div>
172
+ </div>
173
+ """
174
+ )
175
+ gr.Markdown("์‚ฌ๋žŒ ์ด๋ฏธ์ง€, ์ƒํ’ˆ ์ด๋ฏธ์ง€, ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ฑฐ๋‚˜ ํ…์ŠคํŠธ๋กœ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.")
176
+
177
+ with gr.Row():
178
+ with gr.Column():
179
+ # ์ด๋ฏธ์ง€ ์ž…๋ ฅ ์„น์…˜ (์„ ํƒ ์‚ฌํ•ญ)
180
+ person_input = gr.Image(type="pil", label="์‚ฌ๋žŒ ์ด๋ฏธ์ง€ (์„ ํƒ ์‚ฌํ•ญ)", image_mode="RGB")
181
+ product_input = gr.Image(type="pil", label="์ƒํ’ˆ ์ด๋ฏธ์ง€ (์„ ํƒ ์‚ฌํ•ญ)", image_mode="RGB")
182
+ background_input = gr.Image(type="pil", label="๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ (์„ ํƒ ์‚ฌํ•ญ)", image_mode="RGB")
183
+
184
+ # ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ (ํ•„์ˆ˜)
185
+ prompt_input = gr.Textbox(
186
+ lines=3,
187
+ placeholder="์ด๋ฏธ์ง€ ์ƒ์„ฑ ์„ค๋ช…์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”. ์˜ˆ: '๊ฝƒ๋ฐญ์—์„œ ํ–‡๋น›์„ ๋ฐ›์œผ๋ฉฐ ๋ฏธ์†Œ ์ง“๋Š” ์ Š์€ ์—ฌ์„ฑ', 'ํ˜„๋Œ€์ ์ธ ๊ฑฐ์‹ค์—์„œ ๊ณ ๊ธ‰ ์Šค๋งˆํŠธํฐ์„ ์‚ฌ์šฉํ•˜๋Š” ๋น„์ฆˆ๋‹ˆ์Šค๋งจ'",
188
+ label="์ด๋ฏธ์ง€ ์ƒ์„ฑ ์„ค๋ช… (ํ•„์ˆ˜)"
189
+ )
190
+ submit_btn = gr.Button("์ด๋ฏธ์ง€ ์ƒ์„ฑ")
191
+
192
+ with gr.Column():
193
+ output_gallery = gr.Gallery(label="์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€")
194
+ output_text = gr.Textbox(label="AI ์‘๋‹ต ํ…์ŠคํŠธ", visible=True)
195
+
196
+ submit_btn.click(
197
+ fn=process_image_generation,
198
+ inputs=[person_input, product_input, background_input, prompt_input],
199
+ outputs=[output_gallery, output_text],
200
+ )
201
+
202
+ gr.HTML("""
203
+ <div style="margin-top: 20px; padding: 10px; background-color: #f8f9fa; border-radius: 8px;">
204
+ <h3>์‚ฌ์šฉ ๋ฐฉ๋ฒ•:</h3>
205
+ <ul>
206
+ <li><strong>ํ…์ŠคํŠธ๋กœ ์ด๋ฏธ์ง€ ์ƒ์„ฑ:</strong> ์„ค๋ช…๋งŒ ์ž…๋ ฅํ•˜๊ณ  ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด์„ธ์š”.</li>
207
+ <li><strong>์ด๋ฏธ์ง€ ํ•ฉ์„ฑ:</strong> ์‚ฌ๋žŒ, ์ƒํ’ˆ, ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€๋ฅผ ์„ ํƒ์ ์œผ๋กœ ์—…๋กœ๋“œํ•˜๊ณ  ํ•ฉ์„ฑ ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”.</li>
208
+ <li><strong>ํ”„๋กฌํ”„ํŠธ ํŒ:</strong> ๊ตฌ์ฒด์ ์ด๊ณ  ์ƒ์ƒํ•œ ์„ค๋ช…์ผ์ˆ˜๋ก ๋” ์ข‹์€ ์ด๋ฏธ์ง€๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.</li>
209
+ <li><strong>์˜ˆ์‹œ ํ”„๋กฌํ”„ํŠธ:</strong>
210
+ <ul>
211
+ <li>ํ•œ์ ํ•œ ์นดํŽ˜์—์„œ ๋…ธํŠธ๋ถ์œผ๋กœ ์ž‘์—…ํ•˜๋Š” ํฌ๋ฆฌ์—์ดํ„ฐ</li>
212
+ <li>ํ•ด๋ณ€๊ฐ€์—์„œ ๊ณ ๊ธ‰ ์„ ๊ธ€๋ผ์Šค๋ฅผ ๋ผ๊ณ  ํฌ์ฆˆ ์ทจํ•˜๋Š” ๋ชจ๋ธ</li>
213
+ <li>ํ˜„๋Œ€์ ์ธ ์ฃผ๋ฐฉ์—์„œ ์ตœ์‹  ๋ธ”๋ Œ๋”๋กœ ์Šค๋ฌด๋””๋ฅผ ๋งŒ๋“œ๋Š” ์š”๋ฆฌ์‚ฌ</li>
214
+ </ul>
215
+ </li>
216
+ <li><strong>์–ธ์–ด ํŒ:</strong> ํ•œ๊ตญ์–ด์™€ ์˜์–ด๋ฅผ ํ˜ผํ•ฉํ•ด ์‚ฌ์šฉํ•˜๋ฉด ๋” ์ •ํ™•ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.</li>
217
+ </ul>
218
+ </div>
219
+ """)
220
+
221
+ # --- ์‹คํ–‰ ---
222
+ if __name__ == "__main__":
223
+ demo.launch(share=True)