whispersound commited on
Commit
32f62d1
·
verified ·
1 Parent(s): aee06f8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +388 -587
app.py CHANGED
@@ -1,16 +1,12 @@
1
  import os
2
- import json
3
  import logging
4
  import re
 
5
  import gradio as gr
6
  from google import genai
 
7
  import google.generativeai as genai_generative
8
  from dotenv import load_dotenv
9
- import random
10
- from PIL import Image
11
- import numpy as np
12
- from typing import List, Dict, Any, Optional, Tuple
13
- import colorsys
14
 
15
  load_dotenv()
16
 
@@ -18,276 +14,240 @@ load_dotenv()
18
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
19
  logger = logging.getLogger(__name__)
20
 
21
- # ------------------- 레퍼런스 데이터 로드 -------------------
22
- def load_references(json_path="references/style_references.json"):
23
- """레퍼런스 JSON 파일을 로드하는 함수"""
24
- try:
25
- with open(json_path, 'r', encoding='utf-8') as f:
26
- return json.load(f)
27
- except FileNotFoundError:
28
- logger.warning(f"레퍼런스 파일을 찾을 수 없습니다: {json_path}. 기본 설정을 사용합니다.")
29
- return {"clothing_references": []}
30
- except json.JSONDecodeError:
31
- logger.error(f"레퍼런스 파일 형식이 올바르지 않습니다: {json_path}")
32
- return {"clothing_references": []}
 
 
 
33
 
34
- # 레퍼런스 데이터 로드
35
- REFERENCES_DATA = load_references()
36
- CLOTHING_REFERENCES = REFERENCES_DATA.get("clothing_references", [])
37
 
38
- # ------------------- 이미지 분석 함수 -------------------
39
- def extract_dominant_colors(img, num_colors=3):
40
- """이미지에서 주요 색상을 추출하는 함수"""
 
41
  try:
42
- # 이미지를 numpy 배열로 변환
43
- img_array = np.array(img)
44
-
45
- # 이미지 크기 줄이기 (성능 향상을 위함)
46
- height, width = img_array.shape[0], img_array.shape[1]
47
- scale = min(100 / width, 100 / height)
48
- new_width, new_height = int(width * scale), int(height * scale)
49
- img_resized = img.resize((new_width, new_height))
50
- img_array = np.array(img_resized)
51
-
52
- # 픽셀 배열 변환
53
- pixels = img_array.reshape(-1, 3)
54
-
55
- # 픽셀 샘플링 (성능 향상을 위함)
56
- pixel_sample = pixels[np.random.choice(len(pixels), min(5000, len(pixels)), replace=False)]
57
-
58
- # K-means 대신 간단한 색상 히스토그램 사용
59
- colors = {}
60
- for pixel in pixel_sample:
61
- # RGB 값을 정수 튜플로 변환하여 키로 사용
62
- color_key = tuple(map(int, pixel))
63
- if color_key in colors:
64
- colors[color_key] += 1
65
- else:
66
- colors[color_key] = 1
67
-
68
- # 가장 많이 등장한 색상 선택
69
- sorted_colors = sorted(colors.items(), key=lambda x: x[1], reverse=True)
70
- dominant_colors = [c[0] for c in sorted_colors[:num_colors]]
71
-
72
- # RGB 값을 색상 이름으로 변환
73
- color_names = []
74
- for color in dominant_colors:
75
- r, g, b = color
76
- # HSV로 변환
77
- h, s, v = colorsys.rgb_to_hsv(r/255, g/255, b/255)
78
-
79
- # 색상 이름 결정 (간단한 구현)
80
- if s < 0.1 and v > 0.8:
81
- color_names.append("white")
82
- elif s < 0.1 and v < 0.3:
83
- color_names.append("black")
84
- elif s < 0.1:
85
- if v < 0.5:
86
- color_names.append("gray")
87
- else:
88
- color_names.append("light_gray")
89
- elif h < 0.05 or h > 0.95:
90
- if s > 0.5 and v > 0.5:
91
- color_names.append("red")
92
- else:
93
- color_names.append("pink")
94
- elif 0.05 <= h < 0.11:
95
- if v > 0.7:
96
- color_names.append("orange")
97
- else:
98
- color_names.append("brown")
99
- elif 0.11 <= h < 0.2:
100
- if v > 0.7:
101
- color_names.append("yellow")
102
- else:
103
- color_names.append("olive")
104
- elif 0.2 <= h < 0.4:
105
- if v > 0.7:
106
- color_names.append("green")
107
- else:
108
- color_names.append("dark_green")
109
- elif 0.4 <= h < 0.5:
110
- color_names.append("mint")
111
- elif 0.5 <= h < 0.7:
112
- if s > 0.5 and v > 0.5:
113
- color_names.append("blue")
114
- else:
115
- color_names.append("light_blue")
116
- elif 0.7 <= h < 0.8:
117
- color_names.append("purple")
118
- elif 0.8 <= h < 0.95:
119
- if s > 0.5 and v > 0.5:
120
- color_names.append("magenta")
121
- else:
122
- color_names.append("pink")
123
- else:
124
- color_names.append("unknown")
125
-
126
- return color_names
127
  except Exception as e:
128
- logger.error(f"색상 추출 중 오류 발생: {e}")
129
- return ["unknown"]
130
 
131
- def identify_clothing_type(img):
132
- """이미지에서 의류 타입을 식별하는 함수 (간단한 구현)"""
133
- # 실제로는 이미지 인식 델을 사용하는 것이 좋지만,
134
- # 여기서는 간단한 휴리스틱을 사용합니다.
135
- img_array = np.array(img)
136
- h, w = img_array.shape[0], img_array.shape[1]
137
- aspect_ratio = h / w
138
-
139
- # 세로로 긴 이미지는 드레스나 코트로 추정
140
- if aspect_ratio > 1.8:
141
- return "dress"
142
- # 정사각형에 가까운 이미지는 상의로 추정
143
- elif 0.9 <= aspect_ratio <= 1.3:
144
- return "top"
145
- # 가로로 넓은 이미지는 바지나 스커트로 추정
146
- elif aspect_ratio < 0.8:
147
- return "bottom"
148
- else:
149
- return "outerwear"
150
 
151
- def find_similar_references(clothing_type, colors, num_results=3):
152
- """의류 타입과 색상을 기반으로 유사한 레퍼런스를 찾는 함수"""
153
- if not CLOTHING_REFERENCES:
154
- return []
155
-
156
- # 점수를 계산하여 가장 유사한 레퍼런스 찾기
157
- scored_references = []
158
- for ref in CLOTHING_REFERENCES:
159
- score = 0
160
-
161
- # 의류 타입 점수
162
- ref_type = ref.get("clothing_type", "").lower()
163
- if clothing_type in ref_type or ref_type in clothing_type:
164
- score += 3
165
- elif "top" in clothing_type and any(t in ref_type for t in ["blouse", "shirt", "sweater", "cardigan", "jacket"]):
166
- score += 2
167
- elif "bottom" in clothing_type and any(t in ref_type for t in ["skirt", "pants", "jeans", "shorts"]):
168
- score += 2
169
- elif "dress" in clothing_type and "dress" in ref_type:
170
- score += 3
171
- elif "outerwear" in clothing_type and any(t in ref_type for t in ["coat", "jacket", "cardigan"]):
172
- score += 2
173
-
174
- # 색상 점수
175
- ref_color = ref.get("color", "").lower()
176
- for color in colors:
177
- if color.lower() in ref_color:
178
- score += 2
179
-
180
- scored_references.append((ref, score))
181
 
182
- # 점수에 따라 정렬하고 상위 결과 반환
183
- sorted_references = sorted(scored_references, key=lambda x: x[1], reverse=True)
184
- return [ref[0] for ref in sorted_references[:num_results]]
185
-
186
- # ------------------- 프롬프트 생성 함수 -------------------
187
- def generate_reference_based_prompt(model_image, clothing_image, shoes_image, custom_prompt=""):
188
- """레퍼런스 기반의 프롬프트 생성 함수"""
189
- try:
190
- # 기본 이미지 검증
191
- if not clothing_image:
192
- return "오류: 의류 이미지를 업로드해주세요."
193
-
194
- # 의류 이미지 분석
195
- clothing_colors = extract_dominant_colors(clothing_image)
196
- clothing_type = identify_clothing_type(clothing_image)
197
-
198
- # 유사한 레퍼런스 찾기
199
- similar_references = find_similar_references(clothing_type, clothing_colors)
200
-
201
- if not similar_references:
202
- # 레퍼런스를 찾지 못한 경우 기본 프롬프트 생성
203
- return generate_basic_prompt(model_image, clothing_image, shoes_image, custom_prompt)
204
-
205
- # 가장 유사한 레퍼런스의 프롬프트 사용
206
- best_reference = similar_references[0]
207
- reference_prompt = best_reference.get("prompt", "")
208
-
209
- # 커스텀 프롬프트가 있는 경우, 레퍼런스의 배경/포즈 정보와 결합
210
- if custom_prompt.strip():
211
- # 배경 및 포즈 정보 추출
212
- ref_setting = best_reference.get("setting", "")
213
- ref_pose = best_reference.get("pose", "")
214
- ref_background = best_reference.get("background", "")
215
-
216
- # 기본 템플릿 생성
217
- template = """Hyperrealistic lifestyle portrait of the woman from image #1 wearing the {clothing_desc} from image #2 {shoes_desc}, {custom_setting}. Her face is exactly preserved from image #1, with identical features, expressions, and complexion. {additional_details} --ar 9:16 --face #1 --seed 123456 --q 3 --v 5.2 --style raw"""
218
-
219
- # 의류와 신발 설명
220
- clothing_desc = f"{', '.join(clothing_colors)} {clothing_type}"
221
- shoes_desc = ""
222
- if shoes_image:
223
- shoes_colors = extract_dominant_colors(shoes_image)
224
- shoes_desc = f"and {', '.join(shoes_colors)} shoes from image #3"
225
-
226
- # 추가 세부 사항
227
- additional_details = f"The setting features {ref_background}."
228
-
229
- # 템플릿 채우기
230
- prompt = template.format(
231
- clothing_desc=clothing_desc,
232
- shoes_desc=shoes_desc,
233
- custom_setting=custom_prompt,
234
- additional_details=additional_details
235
- )
236
- else:
237
- # 커스텀 프롬프트가 없는 경우, 레퍼런스 프롬프트 그대로 사용하되 의류 설명만 업데이트
238
- prompt = reference_prompt
239
-
240
- return prompt
241
 
242
- except Exception as e:
243
- logger.exception("레퍼런스 기반 프롬프트 생성 중 오류:")
244
- return f"프롬프트 생성 중 오류가 발생했습니다: {str(e)}"
 
 
 
 
 
 
245
 
246
- def generate_basic_prompt(model_image, clothing_image, shoes_image, custom_prompt):
247
- """기본적인 프롬프트 생성 함수"""
248
- # 랜덤으로 상황 선택 (커스텀 프롬프트가 비어있는 경우)
249
- SETTINGS = [
250
- "in a cozy cafe with wooden tables and warm lighting, sitting comfortably while enjoying a coffee",
251
- "walking down a vibrant city street with storefronts, passing by shop windows",
252
- "in a bright, modern home interior with minimal decor, relaxing on a comfortable sofa",
253
- "at an outdoor terrace cafe with sunlight filtering through trees, seated at a small table",
254
- "browsing through books in a warm-lit bookstore with wooden shelves",
255
- "in a park with autumn leaves, walking along a stone pathway",
256
- "in a modern kitchen with marble countertops, leaning against the island",
257
- "at a casual restaurant with ambient lighting, seated in a corner booth",
258
- "in a stylish apartment with large windows and city views, standing by the window",
259
- "at a quiet corner of a coffee shop with plants and soft lighting, reading a book"
260
- ]
261
 
262
- if not custom_prompt.strip():
263
- setting = random.choice(SETTINGS)
264
- else:
265
- setting = custom_prompt
 
 
 
 
 
 
 
 
 
 
 
 
 
266
 
267
- # 완성된 프롬프트 템플릿
268
- template = """Hyperrealistic lifestyle portrait of the woman from image #1 wearing the {clothing_desc} from image #2 and {shoes_desc} from image #3, {setting}. Her face is exactly preserved from image #1, with identical features, expressions, and complexion. The clothing fits her perfectly with natural draping, highlighting the fabric texture and design details. The scene is captured in warm, natural lighting that creates a soft, authentic ambience. The photograph has a shallow depth of field, focusing clearly on her and the outfit while gently blurring the background. This creates an intimate, editorial-quality lifestyle image that showcases both the model and the clothing in a natural, everyday moment. --ar 9:16 --face #1 --seed 123456 --q 3 --v 5.2 --style raw"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
 
270
- # 의류와 신발 설명 (간단한 형태)
271
- clothing_colors = extract_dominant_colors(clothing_image) if clothing_image else ["unknown"]
272
- clothing_type = identify_clothing_type(clothing_image) if clothing_image else "outfit"
273
- clothing_desc = f"{', '.join(clothing_colors)} {clothing_type}"
 
 
 
 
 
 
 
 
 
 
274
 
275
- shoes_desc = "complementary footwear"
276
- if shoes_image:
277
- shoes_colors = extract_dominant_colors(shoes_image)
278
- shoes_desc = f"{', '.join(shoes_colors)} shoes"
 
279
 
280
- # 템플릿 채우
281
- prompt = template.format(
282
- clothing_desc=clothing_desc,
283
- shoes_desc=shoes_desc,
284
- setting=setting
285
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
 
287
- return prompt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
 
289
- def generate_prompt_with_gemini(model_image, clothing_image, shoes_image, custom_prompt, reference_prompt):
290
- """Gemini를 사용한 완성된 프롬프트 생성 함수"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
  GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
292
  if not GEMINI_API_KEY:
293
  return "Gemini API 키가 설정되지 않았습니다. 환경 변수 GEMINI_API_KEY를 설정하거나 코드에 직접 입력하세요."
@@ -295,374 +255,212 @@ def generate_prompt_with_gemini(model_image, clothing_image, shoes_image, custom
295
  try:
296
  genai_generative.configure(api_key=GEMINI_API_KEY)
297
 
298
- # 시스템 인스럭션
299
- system_instruction = """당신은 가상 피팅 프롬프트 전문가입니다.
300
- 미드저니에서 사용할 완성된 하나의 프롬프트를 생성해야 합니다.
301
- 단순히 지시사항을 나열하는 것이 아니라, 하나의 구체적인 상황과 설정을 자연스러운 문장으로 묘사해야 합니다.
302
- 다음 원칙을 따르세요:
303
- 1. 완전한 문장으로 구성된 하나의 통합된 프롬프트를 작성하세요. bullet point나 개조식 나열은 절대 사용하지 마세요.
304
- 2. 구체적인 하나의, 그러나 상세한 상황을 설정하세요:
305
- - 구체적인 장소 (카페, 거리, 집 등)
306
- -체적인 행동/포즈 (앉아있기, 걷기, 책 읽기 등)
307
- -적인 조명과 분 (따뜻자연광, 부드러운 카페 조명 등)
308
- 3. 모델의 얼굴 유 프롬프트 내용에 자연스럽게 하세요.
309
- - "Her face is exactly preserved from image #1" 등의 표현을 포함
310
- 4. 의상과 신발에 대한 언급통합된 문장에 자연스럽게 포함하세요.
311
- - 단순히 "wearing clothing #2" 보다는 묘사적으로
312
- 5. 마지막에 미드저니 파라미터를 포함하세요:
313
- --ar 9:16 --face #1 --seed 123456 --q 3 --v 5.2 --style raw
314
- 최종 프롬프트는 하나의 완성된 문단으로, 람이 쉽게 읽을 있고 명확한 장면을 상상할 있게 작성되어야 합니다.
 
 
 
 
 
 
 
 
315
  """
 
 
316
 
317
- # 프롬프트 요청
318
- prompt_request = f"""
319
- 다음 가상 피팅 프롬프트를 분석하고, 완성된 하나의 상황을 묘사하는 통합된 문단으로 다시 작성해주세요:
320
- {reference_prompt}
321
- 사용자의 커스텀 프롬프트/상황: "{custom_prompt}"
322
- 개조식이나 bullet point 형태가 아닌, 완전한 문장들로 구성된 하나의 통합된 문단으로 작성해주세요.
323
- 구체적인 장소, 행동, 조명, 분위기를 포함하여 명확하게 상상할 수 있는 하나의 상황을 묘사해야 합니다.
324
- 얼굴 유지, 의상 착용, 환경 묘사가 모두 자연스럽게 통합되어야 합니다.
325
- 미드저니 파라미터는 그대로 유지하세요.
326
- 응답은 오직 최종 프롬프트 텍스트만 포함해야 합니다.
327
  """
328
 
329
- # 모델 설정 생성
330
  model = genai_generative.GenerativeModel(
331
- 'gemini-pro',
332
- system_instruction=system_instruction
333
  )
334
 
335
  response = model.generate_content(
336
  prompt_request,
337
  generation_config=genai_generative.types.GenerationConfig(
338
- temperature=0.7,
339
  top_p=0.95,
340
  top_k=40,
341
- max_output_tokens=800,
342
  )
343
  )
344
 
345
- enhanced_prompt = response.text.strip()
346
 
347
- # 미드저니 파라미터 확인 및 추가
348
- if "--ar 9:16" not in enhanced_prompt:
349
- enhanced_prompt += " --ar 9:16 --face #1 --seed 123456 --q 3 --v 5.2 --style raw"
 
350
 
351
- return enhanced_prompt
352
-
353
- except Exception as e:
354
- logger.exception("프롬프트 생성 중 오류 발생:")
355
- return f"프롬프트 생성 중 오류가 발생했습니다: {str(e)}"
356
-
357
- def filter_prompt_only(prompt):
358
- """Gemini의 설명 및 불필요한 메시지를 제거하고 실제 프롬프트만 추출하는 함수"""
359
-
360
- # 코드 블록 내부의 프롬프트 찾기
361
- code_block_pattern = r"```\s*(.*?)```"
362
- code_match = re.search(code_block_pattern, prompt, re.DOTALL)
363
- if code_match:
364
- return code_match.group(1).strip()
365
-
366
- # "I hope this helps" 등의 메시지 제거
367
- end_phrases = [
368
- "I hope this",
369
- "This enhanced prompt",
370
- "I've enhanced",
371
- "The above prompt",
372
- "Hope this helps",
373
- "This prompt will",
374
- "Best regards"
375
- ]
376
-
377
- for phrase in end_phrases:
378
- idx = prompt.find(phrase)
379
- if idx > 0:
380
- prompt = prompt[:idx].strip()
381
-
382
- # 시작 부분에 있을 수 있는 설명 제거
383
- start_phrases = [
384
- "Here's an enhanced",
385
- "Here is the improved",
386
- "I've refined",
387
- "Below is the",
388
- "The enhanced"
389
- ]
390
-
391
- for phrase in start_phrases:
392
- if prompt.startswith(phrase):
393
- lines = prompt.split('\n', 1)
394
- if len(lines) > 1:
395
- prompt = lines[1].strip()
396
-
397
- return prompt.strip()
398
-
399
- # ------------------- 프롬프트 생성 메인 함수 -------------------
400
- def generate_final_prompt(model_image, clothing_image, shoes_image, custom_prompt):
401
- """최종 프롬프트 생성 함수"""
402
- # 입력값 검증
403
- if not model_image or not clothing_image:
404
- return "오류: 필수 이미지(모델, 의류)를 모두 업로드해주세요."
405
-
406
- try:
407
- # 레퍼런스 기반 프롬프트 생성
408
- reference_prompt = generate_reference_based_prompt(model_image, clothing_image, shoes_image, custom_prompt)
409
-
410
- # Gemini API를 사용한 프롬프트 개선
411
- try:
412
- generated_prompt = generate_prompt_with_gemini(model_image, clothing_image, shoes_image, custom_prompt, reference_prompt)
413
 
414
- # API 오류 확인
415
- if "Gemini API 키가 설정되지 않았습니다" in generated_prompt:
416
- logger.warning("Gemini API 키가 없어 기본 레퍼런스 프롬프트를 사용합니다.")
417
- return reference_prompt
418
 
419
- # 프롬프트만 추출
420
- final_prompt = filter_prompt_only(generated_prompt)
421
- return final_prompt
422
- except Exception as e:
423
- logger.exception("Gemini 프롬프트 생성 중 오류:")
424
- # Gemini API 오류 시 레퍼런스 프롬프트 반환
425
- return reference_prompt
426
-
427
  except Exception as e:
428
- logger.exception("최종 프롬프트 생성 중 오류:")
429
- # 모든 오류 시 기본 프롬프트 생성
430
- return generate_basic_prompt(model_image, clothing_image, shoes_image, custom_prompt)
431
-
432
- # ------------------- 레퍼런스 필터링 함수 -------------------
433
- def filter_references_by_style(style=None):
434
- """스타일로 레퍼런스 필터링"""
435
- if not style or style == "모든 스타일":
436
- return CLOTHING_REFERENCES
437
-
438
- filtered_refs = [ref for ref in CLOTHING_REFERENCES if ref.get("style", "").lower() == style.lower()]
439
- return filtered_refs
440
-
441
- def filter_references_by_clothing_type(clothing_type=None):
442
- """의류 타입으로 레퍼런스 필터링"""
443
- if not clothing_type or clothing_type == "모든 타입":
444
- return CLOTHING_REFERENCES
445
-
446
- filtered_refs = []
447
- for ref in CLOTHING_REFERENCES:
448
- ref_type = ref.get("clothing_type", "").lower()
449
- if clothing_type.lower() in ref_type:
450
- filtered_refs.append(ref)
451
-
452
- return filtered_refs
453
-
454
- def get_available_styles():
455
- """사용 가능한 모든 스타일 목록 반환"""
456
- styles = set()
457
- for ref in CLOTHING_REFERENCES:
458
- style = ref.get("style", "")
459
- if style:
460
- styles.add(style)
461
-
462
- return ["모든 스타일"] + sorted(list(styles))
463
-
464
- def get_available_clothing_types():
465
- """사용 가능한 모든 의류 타입 목록 반환"""
466
- types = set()
467
- for ref in CLOTHING_REFERENCES:
468
- clothing_type = ref.get("clothing_type", "")
469
- if clothing_type:
470
- # 복합 타입 분리 (예: "니트 카디건" -> "니트", "카디건")
471
- for t in clothing_type.split():
472
- types.add(t)
473
-
474
- return ["모든 타입"] + sorted(list(types))
475
 
476
- def get_reference_examples(style=None, clothing_type=None, num_examples=5):
477
- """스타일과 의류 타입에 따른 레퍼런스 예시 반환"""
478
- filtered_by_style = filter_references_by_style(style)
479
- filtered_by_type = filter_references_by_clothing_type(clothing_type)
480
-
481
- # 필터 결과의 교집합
482
- if style and style != "모든 스타일" and clothing_type and clothing_type != "모든 타입":
483
- filtered = [ref for ref in filtered_by_style if ref in filtered_by_type]
484
- elif style and style != "모든 스타일":
485
- filtered = filtered_by_style
486
- elif clothing_type and clothing_type != "모든 타입":
487
- filtered = filtered_by_type
488
- else:
489
- filtered = CLOTHING_REFERENCES
490
-
491
- # 무작위로 선택
492
- if len(filtered) > num_examples:
493
- return random.sample(filtered, num_examples)
494
- else:
495
- return filtered
496
 
497
  # ------------------- Gradio 인터페이스 구성 -------------------
498
  def create_app():
499
- with gr.Blocks(title="가상 피팅 스튜디오") as demo:
500
- gr.Markdown("# 가상 피팅 스튜디오")
 
 
 
501
  gr.Markdown(
502
- "모델, 의류, 신발 이미지를 업로드하 자연스러운 피팅 프롬프트를 생성합니다."
503
  )
504
-
505
  with gr.Row():
506
  with gr.Column(scale=1):
507
- # ���미지 업로드 요소들
508
- model_image = gr.Image(label="모델 이미지 업로드 (#1참조됨)", type="pil")
509
- clothing_image = gr.Image(label="의류 이미지 업로드 (#2로 참조됨)", type="pil")
510
- shoes_image = gr.Image(label="신발 이미지 업로드 (#3으로 참조됨)", type="pil")
511
-
512
- # 필터링 옵션
513
- with gr.Row():
514
- style_dropdown = gr.Dropdown(
515
- choices=get_available_styles(),
516
- label="스타일 선택",
517
- value="모든 스타일"
518
- )
519
- clothing_type_dropdown = gr.Dropdown(
520
- choices=get_available_clothing_types(),
521
- label="의류 타입 선택",
522
- value="모든 타입"
523
- )
524
 
525
- # 레퍼런스 예시 표시
526
- reference_examples = gr.Dropdown(
527
- label="레퍼런스 예시 (선택하면 프롬프트가 자동으로 표시됩니다)",
528
- choices=[],
 
 
 
 
 
 
529
  interactive=True
530
  )
531
-
532
- # 커스텀 프롬프트 입력
533
- custom_prompt = gr.Textbox(
534
- label=" 상황 설명 (한국어로 입력 가능, 비워두면 레퍼런스 상황 적용)",
535
- placeholder="예: 따뜻한 조명의 카페에서 창가에 앉아 책을 읽고 있는 모습, 햇살이 비치는 거리를 걷고 있는 모습 등",
536
- lines=3,
537
- interactive=True,
538
- value=""
539
  )
540
-
541
- # 프롬프트 생성 버튼
542
- prompt_btn = gr.Button("프롬프트 생성", variant="primary")
543
-
544
- # 예시 상황 선택 드롭다운
545
- example_situations = [
546
- "카페에서 창가에 앉아 커피를 마시는 편안한 모습",
547
- "가을 낙엽이 있는 공원 산책로를 걷는 모습",
548
- "모던한 거실에서 소파에 편안하게 앉아 책을 읽는 모습",
549
- "오픈된 주방에서 아일랜드 식탁에 기대어 있는 모습",
550
- "서점에서 책장 사이를 걸으며 책을 고르는 모습",
551
- "야외 테라스 카페에서 햇살을 받으며 앉아있는 모습",
552
- "해변가 산책로를 걸으며 바다를 바라보는 모습",
553
- "도심 속 공원에서 벤치에 앉아 휴식을 취하는 모습",
554
- "갤러리에서 작품을 감상하는 우아한 모습",
555
- "쇼핑몰에서 쇼핑백을 들고 걷는 모습"
556
- ]
557
-
558
- example_dropdown = gr.Dropdown(
559
- choices=example_situations,
560
- label="예시 상황 (선택하면 자동으로 입력됩니다)",
561
  )
562
-
563
- # 드롭다운 선택 시 텍스트박스에 입력
564
- example_dropdown.change(
565
- fn=lambda x: x,
566
- inputs=example_dropdown,
567
- outputs=custom_prompt
568
  )
569
-
570
- with gr.Column(scale=1):
571
- # 프롬프트 출력 영역
572
- prompt_output = gr.Textbox(label="생성된 프롬프트", lines=8)
573
-
574
- # 레퍼런스 정보 표시
575
- reference_info = gr.Markdown(label="선택된 레퍼런스 정보")
576
-
577
- # 복사 버튼
578
- copy_btn = gr.Button("프롬프트 복사하기", variant="secondary")
579
-
580
- # 복사 상태 표시
581
- copy_status = gr.Markdown("")
582
-
583
- # 복사 버튼 기능 구현
584
- def copy_to_clipboard(text):
585
- return "📋 프롬프트가 준비되었습니다. 텍스트를 선택하고 Ctrl+C(또는 Cmd+C)로 복사하세요."
586
-
587
- copy_btn.click(
588
- fn=copy_to_clipboard,
589
- inputs=prompt_output,
590
- outputs=copy_status
591
  )
592
 
593
- # 주요 특징 설명
594
- gr.Markdown("""
595
- ### 주요 특징:
596
- - **레퍼런스 기반 프롬프트**: 55개 이상의 스타일 레퍼런스를 바탕으로 최적의 프롬프트 생성
597
- - **자동 의류 분석**: 업로드된 의류 이미지 색상과 타입을 자동 분석하여 최적의 레퍼런스 매칭
598
- - **얼굴 완벽 보존**: 모델의 얼굴 특징을 자연스럽게 유지하는 최적화된 프롬프트
599
- - **맞춤형 환경 설정**: 의류 스타일에 가장 어울리는 배경과 포즈 자동 추천
600
- - **AI 강화 프롬프트**: Gemini AI로 더욱 자연스럽고 상세한 프롬프트 생성
601
- """)
602
-
603
- # 레퍼런스 예시 업데이트 함수
604
- def update_reference_examples(style, clothing_type):
605
- examples = get_reference_examples(style, clothing_type)
606
- example_choices = [f"{ref.get('id', '?')}. {ref.get('style', '?')} - {ref.get('clothing_type', '?')} ({ref.get('color', '?')})" for ref in examples]
607
- return gr.Dropdown.update(choices=example_choices)
608
 
609
- # 레퍼런스 선택 시 정보 및 프롬프트 업데이트 함수
610
- def update_reference_info(selected_example):
611
- if not selected_example:
612
- return "", ""
613
-
614
- try:
615
- # ID 추출
616
- ref_id = int(selected_example.split('.')[0])
617
-
618
- # 해당 ID의 레퍼런찾기
619
- reference = next((ref for ref in CLOTHING_REFERENCES if ref.get("id") == ref_id), None)
 
 
 
 
 
 
 
 
 
 
 
 
620
 
621
- if reference:
622
- # 마크다운 형식의 레퍼런스 정보
623
- info = f"""
624
- ### 레퍼런스 #{reference.get('id')}
625
- - **스타일**: {reference.get('style', 'N/A')}
626
- - **의류 타입**: {reference.get('clothing_type', 'N/A')}
627
- - **색상**: {reference.get('color', 'N/A')}
628
- - **환경**: {reference.get('setting', 'N/A')}
629
- - **포즈**: {reference.get('pose', 'N/A')}
630
- - **배경**: {reference.get('background', 'N/A')}
631
- """
632
-
633
- # 프롬프트
634
- prompt = reference.get('prompt', '')
635
-
636
- return info, prompt
637
- else:
638
- return "레퍼런스를 찾을 수 없습니다.", ""
639
- except Exception as e:
640
- return f"오류 발생: {str(e)}", ""
641
-
642
- # 스타일과 의류 타입 선택 시 레퍼런스 예시 업데이트
643
- style_dropdown.change(
644
- fn=update_reference_examples,
645
- inputs=[style_dropdown, clothing_type_dropdown],
646
- outputs=reference_examples
647
- )
648
-
649
- clothing_type_dropdown.change(
650
- fn=update_reference_examples,
651
- inputs=[style_dropdown, clothing_type_dropdown],
652
- outputs=reference_examples
653
- )
654
-
655
- # 레퍼런스 예시 선택 시 정보 및 프롬프트 업데이트
656
- reference_examples.change(
657
- fn=update_reference_info,
658
- inputs=reference_examples,
659
- outputs=[reference_info, prompt_output]
660
- )
661
 
662
  # 프롬프트 생성 함수 연결
663
  prompt_btn.click(
664
- fn=generate_final_prompt,
665
- inputs=[model_image, clothing_image, shoes_image, custom_prompt],
666
  outputs=prompt_output
667
  )
668
 
@@ -670,6 +468,9 @@ def create_app():
670
 
671
  # ------------------- 메인 실행 함수 -------------------
672
  if __name__ == "__main__":
 
 
 
673
  # 앱 생성 및 실행
674
  app = create_app()
675
  app.queue()
 
1
  import os
 
2
  import logging
3
  import re
4
+ import json
5
  import gradio as gr
6
  from google import genai
7
+ from google.genai import types
8
  import google.generativeai as genai_generative
9
  from dotenv import load_dotenv
 
 
 
 
 
10
 
11
  load_dotenv()
12
 
 
14
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
15
  logger = logging.getLogger(__name__)
16
 
17
+ # ------------------- 배경 디렉토리 설정 -------------------
18
+ BACKGROUNDS_DIR = "./background"
19
+ if not os.path.exists(BACKGROUNDS_DIR):
20
+ os.makedirs(BACKGROUNDS_DIR)
21
+ logger.info(f"배경 디렉토리를 생성했습니다: {BACKGROUNDS_DIR}")
22
+
23
+ # ------------------- 전역 변수 설정 -------------------
24
+ SIMPLE_BACKGROUNDS = {}
25
+ STUDIO_BACKGROUNDS = {}
26
+ NATURE_BACKGROUNDS = {}
27
+ INDOOR_BACKGROUNDS = {}
28
+ TECHNOLOGY_BACKGROUNDS = {}
29
+ COLORFUL_PATTERN_BACKGROUNDS = {}
30
+ ABSTRACT_BACKGROUNDS = {}
31
+ JEWELRY_BACKGROUNDS = {} # 쥬얼리 배경 전역 변수 추가
32
 
 
 
 
33
 
34
+ # ------------------- 배경 JSON 파일 로드 함수 -------------------
35
+ def load_background_json(filename):
36
+ """배경 JSON 파일 로드 함수"""
37
+ file_path = os.path.join(BACKGROUNDS_DIR, filename)
38
  try:
39
+ with open(file_path, 'r', encoding='utf-8') as f:
40
+ data = json.load(f)
41
+ logger.info(f"{filename} 파일을 성공적으로 로드했습니다. {len(data)} 항목 포함.")
42
+ return data
43
+ except FileNotFoundError:
44
+ logger.info(f"{filename} 파일이 없습니다.")
45
+ return {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  except Exception as e:
47
+ logger.warning(f"{filename} 파일 로드 중 오류 발생: {str(e)}.")
48
+ return {}
49
 
50
+ # ------------------- 배경 옵션 초기화 함수 -------------------
51
+ def initialize_backgrounds():
52
+ """ 배경 옵션 초기화 함수"""
53
+ global SIMPLE_BACKGROUNDS, STUDIO_BACKGROUNDS, NATURE_BACKGROUNDS, INDOOR_BACKGROUNDS
54
+ global TECHNOLOGY_BACKGROUNDS, COLORFUL_PATTERN_BACKGROUNDS, ABSTRACT_BACKGROUNDS
55
+ global JEWELRY_BACKGROUNDS # 쥬얼리 배경 추가
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
+ # 디렉토리 모든 파일 로깅
59
+ logger.info(f"Backgrounds 디렉토리 경로: {BACKGROUNDS_DIR}")
60
+ logger.info(f"디렉토리 파일 목록: {os.listdir(BACKGROUNDS_DIR)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
+ # 배경 파일 로드
63
+ SIMPLE_BACKGROUNDS = load_background_json("simple_backgrounds.json")
64
+ STUDIO_BACKGROUNDS = load_background_json("studio_backgrounds.json")
65
+ NATURE_BACKGROUNDS = load_background_json("nature_backgrounds.json")
66
+ INDOOR_BACKGROUNDS = load_background_json("indoor_backgrounds.json")
67
+ TECHNOLOGY_BACKGROUNDS = load_background_json("tech-backgrounds-final.json") # 파일 이름 수정
68
+ COLORFUL_PATTERN_BACKGROUNDS = load_background_json("colorful-pattern-backgrounds.json") # 파일 이름 수정
69
+ ABSTRACT_BACKGROUNDS = load_background_json("abstract_backgrounds.json")
70
+ JEWELRY_BACKGROUNDS = load_background_json("jewelry_backgrounds.json")
71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
+ # 기본값 설정 (파일이 없거나 비어있는 경우)
74
+ if not SIMPLE_BACKGROUNDS:
75
+ SIMPLE_BACKGROUNDS = {"클래식 화이트": "clean white background with soft even lighting"}
76
+ if not STUDIO_BACKGROUNDS:
77
+ STUDIO_BACKGROUNDS = {"미니멀 플랫레이": "minimalist flat lay with clean white background"}
78
+ if not NATURE_BACKGROUNDS:
79
+ NATURE_BACKGROUNDS = {"열대 해변": "tropical beach with crystal clear water"}
80
+ if not INDOOR_BACKGROUNDS:
81
+ INDOOR_BACKGROUNDS = {"미니멀 스칸디나비안 거실": "minimalist Scandinavian living room"}
82
+ if not TECHNOLOGY_BACKGROUNDS:
83
+ TECHNOLOGY_BACKGROUNDS = {"다이나믹 스플래시": "dynamic water splash interaction with product"}
84
+ if not COLORFUL_PATTERN_BACKGROUNDS:
85
+ COLORFUL_PATTERN_BACKGROUNDS = {"화려한 꽃 패턴": "vibrant floral pattern backdrop"}
86
+ if not ABSTRACT_BACKGROUNDS:
87
+ ABSTRACT_BACKGROUNDS = {"네온 라이트": "neon light abstract background with vibrant glowing elements"}
88
+ if not JEWELRY_BACKGROUNDS:
89
+ JEWELRY_BACKGROUNDS = {"클래식 화이트 실크": "pristine white silk fabric backdrop"}
90
 
91
+ logger.info("모든 배경 옵션 초기화 완료")
92
+
93
+ # 배경 드롭다운 초기화를 위한 함수 추가
94
+ def initialize_dropdowns():
95
+ """드롭다운 메뉴 초기화 함수"""
96
+ # 각 배경 유형별 드롭다운 선택 목록 생성
97
+ simple_choices = list(SIMPLE_BACKGROUNDS.keys())
98
+ studio_choices = list(STUDIO_BACKGROUNDS.keys())
99
+ nature_choices = list(NATURE_BACKGROUNDS.keys())
100
+ indoor_choices = list(INDOOR_BACKGROUNDS.keys())
101
+ tech_choices = list(TECHNOLOGY_BACKGROUNDS.keys())
102
+ colorful_choices = list(COLORFUL_PATTERN_BACKGROUNDS.keys())
103
+ abstract_choices = list(ABSTRACT_BACKGROUNDS.keys())
104
+ jewelry_choices = list(JEWELRY_BACKGROUNDS.keys())
105
+
106
+
107
 
108
+ return {
109
+ "simple": simple_choices,
110
+ "studio": studio_choices,
111
+ "nature": nature_choices,
112
+ "indoor": indoor_choices,
113
+ "tech": tech_choices,
114
+ "colorful": colorful_choices,
115
+ "abstract": abstract_choices,
116
+ "jewelry": jewelry_choices, # 새로 추가
117
+ }
118
+
119
+ # ------------------- 프롬프트 관련 함수 -------------------
120
+ def filter_prompt_only(prompt):
121
+ """Gemini의 설명 및 불필요한 메시지를 제거하고 실제 프롬프트만 추출하는 함수"""
122
 
123
+ # 코드 블록 내부의 프롬프트 찾기
124
+ code_block_pattern = r"```\s*(.*?)```"
125
+ code_match = re.search(code_block_pattern, prompt, re.DOTALL)
126
+ if code_match:
127
+ return code_match.group(1).strip()
128
 
129
+ # Midjourney 파라미터를 포함하는 프롬프트 부분 찾
130
+ if "--ar 1:1" in prompt:
131
+ lines = prompt.split('\n')
132
+ prompt_lines = []
133
+ in_prompt = False
134
+
135
+ for line in lines:
136
+ # 프롬프트 시작 부분 인식 (일반적인 제품 설명이나 'Magazine-worthy' 같은 키워드로 시작)
137
+ if (not in_prompt and
138
+ ("product" in line.lower() or
139
+ "magazine" in line.lower() or
140
+ "commercial" in line.lower() or
141
+ "photography" in line.lower())):
142
+ in_prompt = True
143
+ prompt_lines.append(line)
144
+ # 이미 프롬프트 영역에 있는 경우 계속 추가
145
+ elif in_prompt:
146
+ # 설명이나 메타 텍스트가 시작되면 중단
147
+ if "explanation" in line.lower() or "let me know" in line.lower():
148
+ break
149
+ prompt_lines.append(line)
150
+
151
+ # 프롬프트 라인 합치기
152
+ if prompt_lines:
153
+ return '\n'.join(prompt_lines).strip()
154
 
155
+ # 위 방법으로 찾지 못한 경우 원본 반환
156
+ return prompt.strip()
157
+
158
+ def get_selected_background_info(bg_type, simple, studio, nature, indoor, tech, colorful, abstract, jewelry):
159
+ """선택된 배경 정보를 가져오는 함수"""
160
+ if bg_type == "심플 배경":
161
+ return {
162
+ "category": "심플 배경",
163
+ "name": simple,
164
+ "english": SIMPLE_BACKGROUNDS.get(simple, "white background")
165
+ }
166
+ elif bg_type == "스튜디오 배경":
167
+ return {
168
+ "category": "스튜디오 배경",
169
+ "name": studio,
170
+ "english": STUDIO_BACKGROUNDS.get(studio, "product photography studio")
171
+ }
172
+ elif bg_type == "자연 환경":
173
+ return {
174
+ "category": "자연 환경",
175
+ "name": nature,
176
+ "english": NATURE_BACKGROUNDS.get(nature, "natural environment")
177
+ }
178
+ elif bg_type == "실내 환경":
179
+ return {
180
+ "category": "실내 환경",
181
+ "name": indoor,
182
+ "english": INDOOR_BACKGROUNDS.get(indoor, "indoor environment")
183
+ }
184
+ elif bg_type == "테크놀로지 배경":
185
+ return {
186
+ "category": "테크놀로지 배경",
187
+ "name": tech,
188
+ "english": TECHNOLOGY_BACKGROUNDS.get(tech, "technology environment")
189
+ }
190
+ elif bg_type == "컬러풀 패턴 배경":
191
+ return {
192
+ "category": "컬러풀 패턴 배경",
193
+ "name": colorful,
194
+ "english": COLORFUL_PATTERN_BACKGROUNDS.get(colorful, "colorful pattern background")
195
+ }
196
+ elif bg_type == "추상/특수 배경":
197
+ return {
198
+ "category": "추상/특수 배경",
199
+ "name": abstract,
200
+ "english": ABSTRACT_BACKGROUNDS.get(abstract, "abstract background")
201
+ }
202
+
203
+ elif bg_type == "쥬얼리 배경":
204
+ return {
205
+ "category": "쥬얼리 배경",
206
+ "name": jewelry,
207
+ "english": JEWELRY_BACKGROUNDS.get(jewelry, "jewelry backdrop")
208
+ }
209
+
210
+ else:
211
+ return {
212
+ "category": "기본 배경",
213
+ "name": "화이트 배경",
214
+ "english": "white background"
215
+ }
216
 
217
+ # ------------------- 프롬프트 생성 함수 -------------------
218
+ def generate_enhanced_system_instruction():
219
+ """향상된 시스템 인스트럭션 생성 함수"""
220
+ return """당신은 상품 이미지의 배경을 변경하기 위한 최고 품질의 프롬프트를 생성하는 전문가입니다.
221
+ 사용자가 제공하는 상품명, 배경 유형, 추가 요청사항을 바탕으로 미드저니(Midjourney)에 사용할 수 있는 상세하고 전문적인 프롬프트를 영어로 생성해주세요.
222
+
223
+ 다음 가이드라인을 반드시 준수해야 합니다:
224
+
225
+ 1. 상품을 "#1"로 지정하여 참조합니다. (예: "skincare tube (#1)")
226
+ 2. *** 매우 중요: 상품의 원래 특성(디자인, 색상, 형태, 로고, 패키지 등)은 어떤 상황에서도 절대 변경하지 않습니다. ***
227
+ 3. *** 상품의 본질적 특성을 유지하되, 상품에 포커스를 맞춰 모든 세부 사항이 선명하게 드러나도록 하며,
228
+ 8K 해상도(8K resolution), 오버샤프닝 없는 초고화질(ultra high definition without oversharpening)로 렌더링되어야 합니다. ***
229
+ 4. 이미지 비율은 정확히 1:1(정사각형) 형식으로 지정합니다. 프롬프트에 "square format", "1:1 ratio" 또는 "aspect ratio 1:1"을 명시적으로 포함합니다.
230
+ 5. 상품은 반드시 정사각형 구도의 정중앙에 배치되어야 하며, 적절한 크기로 표현하여 디테일이 완벽하게 보이도록 합니다.
231
+ 6. 상품을 이미지의 주요 초점으로 부각시키고, 상품의 비율이 전체 이미지에서 60-70% 이상 차지하도록 합니다.
232
+ 7. 조명 설명을 매우 구체적으로 해주세요. 예: "soft directional lighting from left side", "dramatic rim lighting", "diffused natural light through windows"
233
+ 8. 배경의 재질과 질감을 상세히 설명해주세요. 예: "polished marble surface", "rustic wooden table with visible grain", "matte concrete wall with subtle texture"
234
+ 9. 프롬프트에 다음 요소들을 명시적으로 포함하되, 사용 맥락에 적절하게 변형하세요:
235
+ - "award-winning product photography"
236
+ - "magazine-worthy commercial product shot"
237
+ - "professional advertising imagery with perfect exposure"
238
+ - "studio lighting with color-accurate rendering"
239
+ - "8K ultra high definition product showcase"
240
+ - "commercial product photography with precise detail rendering"
241
+ - "ultra high definition"
242
+ - "crystal clear details"
243
+ 10. *** 최우선 지침: 사용자가 제공한 추가 요청사항을 완벽하게 프롬프트에 반영해야 합니다. 사용자의 요청사항 각각을 중요한 요소로 취급하여 프롬프트에 명시적으로 반영하고, 관련 디테일을 구체적으로 설명하세요. 절대로 사용자의 추가 요청사항을 생략하거나 무시하지 마세요. ***
244
+ 11. 사용자가 제공한 구체적인 배경과 추가 요청사항을 프롬프트에 정확히 반영하고 확장합니다.
245
+ 12. 프롬프트 끝에 "--ar 1:1 --s 750 --q 2 --v 5.2" 파라미터를 추가하여 미드저니에서 고품질 정사각형 비율을 강제합니다.
246
+ 13. 매우 중요: 프롬프트 외에 다른 설명이나 메타 텍스트를 포함하지 마세요. 예를 들어 "Here's a prompt for you" 또는 "Let me know if you need adjustments" 같은 메시지나 설명을 포함하지 마세요. 오직 프롬프트 자체만 제공하세요.
247
+ """
248
+
249
+ def generate_prompt_with_gemini(product_name, background_info, additional_info=""):
250
+ """향상된 프롬프트 생성 함수"""
251
  GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
252
  if not GEMINI_API_KEY:
253
  return "Gemini API 키가 설정되지 않았습니다. 환경 변수 GEMINI_API_KEY를 설정하거나 코드에 직접 입력하세요."
 
255
  try:
256
  genai_generative.configure(api_key=GEMINI_API_KEY)
257
 
258
+ # 추가 요청사항을 더 강조한 프롬프 요청 템플릿
259
+ prompt_request = f"""
260
+ 상품명: {product_name}
261
+ 배경 유형: {background_info.get('english', 'studio')}
262
+ 배경 카테고리: {background_info.get('category', '')}
263
+ 배경 이름: {background_info.get('name', '')}
264
+ 추가청사항: {additional_info}
265
+
266
+ 중요 사항:
267
+ 1. 상품(#1)이 이미지 도에서 중심적인 위치를 차지하며 적절크기(이미지의 60-70%)로 표현되도록 프롬프트를 생성해주세요.
268
+ 2. 이미 정확히 1:1 비율(정사각형)이어야니다.
269
+ 3. 상품의 디자인, 색상, 형태, 로고 본질적 특성은 절대 수정하지 마세요.
270
+ 4. 구체적인 조명 기법상세히 명시해주세요:
271
+ - 정확한 조명 위치 (예: "45-degree key light from upper left")
272
+ - 조명 품질 (예: "soft diffused light", "hard directional light")
273
+ - 조명 강도와 색온도 (예: "warm tungsten key light with cool blue fill")
274
+ - 그림자 처리 방식 (예: "controlled specular highlights with soft shadow transitions")
275
+ 5. 상품을 더 돋보이게 하는 보조 요소(props)를 자연스럽게 활용하되, 상품이 항상 주인공이어야 합니다.
276
+ 6. 배경 재질과 표면 질감을 구체적으로 설명하고, 상품과의 상호작용 방식을 명시해주세요.
277
+ 7. 색상 구성(color palette, color harmonies)을 명확히 해주세요.
278
+ 8. 고급스러운 상업 광고 품질의 이미지가 되도록 프롬프트를 작성해주세요.
279
+ 9. 프롬프트 끝에 미드저니 파라미터 "--ar 1:1 --s 750 --q 2 --v 5.2"를 추가해주세요.
280
+ 10. **매우 중요**: 사용자가 제공한 추가 요청사항("{additional_info}")을 프롬프트에 완벽하게 반영해야 합니다. 각 요청사항을 프롬프트의 주요 부분에 명시적으로 포함시키고, 관련 디테일을 자세히 기술해주세요.
281
+
282
+ 한국어 입력 내용을 전문적인 영어로 번역하여 반영해주세요.
283
  """
284
+ # 추가 요청사항을 강조한 시스템 인스트럭션 생성
285
+ enhanced_system_instruction = generate_enhanced_system_instruction() + f"""
286
 
287
+ 특별 지침: 사용자의 추가 요청사항은 최우선 순위로 처리해야 합니다. 추가 요청사항("{additional_info}")의 각 항목을 프롬프트 명시적으로 포함시키고, 해당 요청에 관련된 구체적인 설명을 추���하세요. 사용자의 요청사항을 무시하거나 생략하지 마세요.
 
 
 
 
 
 
 
 
 
288
  """
289
 
290
+ # 창의적인 결과를 위한 생성 설정 조정
291
  model = genai_generative.GenerativeModel(
292
+ 'gemini-2.0-flash',
293
+ system_instruction=enhanced_system_instruction
294
  )
295
 
296
  response = model.generate_content(
297
  prompt_request,
298
  generation_config=genai_generative.types.GenerationConfig(
299
+ temperature=0.7, # 정확성을 위해 약간 낮춤
300
  top_p=0.95,
301
  top_k=40,
302
+ max_output_tokens=1600, # 더 상세한 프롬프트 허용
303
  )
304
  )
305
 
306
+ response_text = response.text.strip()
307
 
308
+ # 추가 요청사항이 프롬프트에 제대로 반영되었는지 확인하는 로직
309
+ if additional_info and not any(keyword.lower() in response_text.lower() for keyword in additional_info.split(',')):
310
+ # 추가 요청사항이 반영되지 않았다면 추가
311
+ logger.warning("추가 요청사항이 프롬프트에 반영되지 않았습니다. 강제로 추가합니다.")
312
 
313
+ # 요청사항을 직접 프롬프트에 추가 (메타 텍스트 없이)
314
+ if "--ar 1:1" in response_text:
315
+ # 미드저니 파라미터 앞에 추가
316
+ parts = response_text.split("--ar 1:1")
317
+ if len(parts) >= 2:
318
+ # 추가 요청사항 직접 삽입
319
+ response_text = parts[0].rstrip(" ,") + ", " + additional_info + ". --ar 1:1" + parts[1]
320
+ else:
321
+ # 미드저니 파라미터가 없는 경우 끝에 추가
322
+ response_text = response_text.rstrip(" .") + ". " + additional_info
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
+ # 미드저니 파라미터가 없을 경우 추가
325
+ if "--ar 1:1" not in response_text:
326
+ response_text = response_text.rstrip(" .") + ". --ar 1:1 --s 750 --q 2 --v 5.2"
 
327
 
328
+ return response_text
 
 
 
 
 
 
 
329
  except Exception as e:
330
+ logger.exception("프롬프트 생성 중 오류 발생:")
331
+ return f"프롬프트 생성 중 오류가 발생했습니다: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
 
333
+ # ------------------- 프롬프트 생성 전용 함수 -------------------
334
+ def generate_prompt_only(image, bg_type, simple, studio, nature, indoor, tech, colorful, abstract, jewelry, product_name, additional_info):
335
+ product_name = product_name.strip() or "제품"
336
+ background_info = get_selected_background_info(bg_type, simple, studio, nature, indoor, tech, colorful, abstract, jewelry)
337
+ generated_prompt = generate_prompt_with_gemini(product_name, background_info, additional_info)
338
+ if "Gemini API 키가 설정되지 않았습니다" in generated_prompt:
339
+ warning_msg = (
340
+ "[Gemini API 누락]\n"
341
+ "API 설정 방법:\n"
342
+ "1. 환경 변수: export GEMINI_API_KEY=\"your-api-key\"\n"
343
+ "2. 코드 직접 입력: GEMINI_API_KEY = \"your-api-key\"\n"
344
+ "키 발급: https://makersuite.google.com/"
345
+ )
346
+ return warning_msg
347
+ final_prompt = filter_prompt_only(generated_prompt)
348
+ return final_prompt
 
 
 
 
349
 
350
  # ------------------- Gradio 인터페이스 구성 -------------------
351
  def create_app():
352
+ # 드롭다운 옵션 초기화
353
+ dropdown_options = initialize_dropdowns()
354
+
355
+ with gr.Blocks(title="고급 상품 이미지 배경 프롬프트 생성") as demo:
356
+ gr.Markdown("# 고급 상품 이미지 배경 프롬프트 생성")
357
  gr.Markdown(
358
+ "상품 이미지를 업로드하고, 제품명, 배경 옵션, 추요청사항을 입력하면 Gemini API를 통해 영어 프롬프트를 생성합니다."
359
  )
 
360
  with gr.Row():
361
  with gr.Column(scale=1):
362
+ product_name = gr.Textbox(label="상품명 (한국어 입력)", placeholder="예: 스킨케어 튜브, 스마트워치, 향수, 운동화 등", interactive=True)
363
+ # 상품 이미지 업로드 상품명 아래이동
364
+ image_input = gr.Image(label="상품 이미지 업로드", type="pil")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
 
366
+ background_type = gr.Radio(
367
+ choices=["심플 배경", "스튜디오 배경", "자연 환경", "실내 환경", "테크놀로지 배경", "컬러풀 패턴 배경", "추상/특수 배경", "쥬얼리 배경"],
368
+ label="배경 유형",
369
+ value="심플 배경"
370
+ )
371
+ simple_dropdown = gr.Dropdown(
372
+ choices=dropdown_options["simple"],
373
+ value=dropdown_options["simple"][0] if dropdown_options["simple"] else None,
374
+ label="심플 배경 선택",
375
+ visible=True,
376
  interactive=True
377
  )
378
+ studio_dropdown = gr.Dropdown(
379
+ choices=dropdown_options["studio"],
380
+ value=dropdown_options["studio"][0] if dropdown_options["studio"] else None,
381
+ label="스튜디오 배경 선택",
382
+ visible=False,
383
+ interactive=True
 
 
384
  )
385
+ nature_dropdown = gr.Dropdown(
386
+ choices=dropdown_options["nature"],
387
+ value=dropdown_options["nature"][0] if dropdown_options["nature"] else None,
388
+ label="자연 환경 선택",
389
+ visible=False,
390
+ interactive=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  )
392
+ indoor_dropdown = gr.Dropdown(
393
+ choices=dropdown_options["indoor"],
394
+ value=dropdown_options["indoor"][0] if dropdown_options["indoor"] else None,
395
+ label="실내 환경 선택",
396
+ visible=False,
397
+ interactive=True
398
  )
399
+ tech_dropdown = gr.Dropdown(
400
+ choices=dropdown_options["tech"],
401
+ value=dropdown_options["tech"][0] if dropdown_options["tech"] else None,
402
+ label="테크놀로지 배경 선택",
403
+ visible=False,
404
+ interactive=True
405
+ )
406
+ colorful_dropdown = gr.Dropdown(
407
+ choices=dropdown_options["colorful"],
408
+ value=dropdown_options["colorful"][0] if dropdown_options["colorful"] else None,
409
+ label="컬러풀 패턴 배경 선택",
410
+ visible=False,
411
+ interactive=True
412
+ )
413
+ abstract_dropdown = gr.Dropdown(
414
+ choices=dropdown_options["abstract"],
415
+ value=dropdown_options["abstract"][0] if dropdown_options["abstract"] else None,
416
+ label="추상/특수 배경 선택",
417
+ visible=False,
418
+ interactive=True
 
 
419
  )
420
 
421
+ jewelry_dropdown = gr.Dropdown(
422
+ choices=dropdown_options["jewelry"],
423
+ value=dropdown_options["jewelry"][0] if dropdown_options["jewelry"] else None,
424
+ label="쥬얼리 배경 선택",
425
+ visible=False,
426
+ interactive=True
427
+ )
 
 
 
 
 
 
 
 
428
 
429
+ additional_info = gr.Textbox(
430
+ label="추가 요청사항 (선택사항)",
431
+ placeholder="예: 고급스러운 느낌, 밝은 조명, 자연스러운 보조 객체 등",
432
+ lines=3,
433
+ interactive=True
434
+ )
435
+ def update_dropdowns(bg_type):
436
+ return {
437
+ simple_dropdown: gr.update(visible=(bg_type == "심플 배경")),
438
+ studio_dropdown: gr.update(visible=(bg_type == "튜디오 배경")),
439
+ nature_dropdown: gr.update(visible=(bg_type == "자연 환경")),
440
+ indoor_dropdown: gr.update(visible=(bg_type == "실내 환경")),
441
+ tech_dropdown: gr.update(visible=(bg_type == "테크놀로지 배경")),
442
+ colorful_dropdown: gr.update(visible=(bg_type == "컬러풀 패턴 배경")),
443
+ abstract_dropdown: gr.update(visible=(bg_type == "추상/특수 배경")),
444
+ jewelry_dropdown: gr.update(visible=(bg_type == "쥬얼리 배경")) # 쥬얼리 드롭다운 추가
445
+
446
+ }
447
+ background_type.change(
448
+ fn=update_dropdowns,
449
+ inputs=[background_type],
450
+ outputs=[simple_dropdown, studio_dropdown, nature_dropdown, indoor_dropdown, tech_dropdown, colorful_dropdown, abstract_dropdown, jewelry_dropdown]
451
+ )
452
 
453
+ # 프롬프트 생성 버튼으로 변경
454
+ prompt_btn = gr.Button("프롬프트 생성", variant="primary")
455
+
456
+ with gr.Column(scale=1):
457
+ # 프롬프트 출력 영역만 유지
458
+ prompt_output = gr.Textbox(label="생성된 프롬프트 (영어)", lines=10)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
 
460
  # 프롬프트 생성 함수 연결
461
  prompt_btn.click(
462
+ fn=generate_prompt_only,
463
+ inputs=[image_input, background_type, simple_dropdown, studio_dropdown, nature_dropdown, indoor_dropdown, tech_dropdown, colorful_dropdown, abstract_dropdown, jewelry_dropdown, product_name, additional_info],
464
  outputs=prompt_output
465
  )
466
 
 
468
 
469
  # ------------------- 메인 실행 함수 -------------------
470
  if __name__ == "__main__":
471
+ # 배경 옵션 초기화 - JSON 파일에서 로드
472
+ initialize_backgrounds()
473
+
474
  # 앱 생성 및 실행
475
  app = create_app()
476
  app.queue()