Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
# 레퍼런스 데이터 로드
|
| 35 |
-
REFERENCES_DATA = load_references()
|
| 36 |
-
CLOTHING_REFERENCES = REFERENCES_DATA.get("clothing_references", [])
|
| 37 |
|
| 38 |
-
# -------------------
|
| 39 |
-
def
|
| 40 |
-
"""
|
|
|
|
| 41 |
try:
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 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.
|
| 129 |
-
return
|
| 130 |
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 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 |
-
|
| 184 |
-
|
| 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 |
-
|
| 243 |
-
|
| 244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
|
| 267 |
-
|
| 268 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
|
|
|
| 279 |
|
| 280 |
-
#
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
|
| 287 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
|
| 289 |
-
|
| 290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
4.
|
| 311 |
-
-
|
| 312 |
-
|
| 313 |
-
-
|
| 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-
|
| 332 |
-
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=
|
| 342 |
)
|
| 343 |
)
|
| 344 |
|
| 345 |
-
|
| 346 |
|
| 347 |
-
#
|
| 348 |
-
if
|
| 349 |
-
|
|
|
|
| 350 |
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 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 |
-
|
| 415 |
-
|
| 416 |
-
|
| 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 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 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 |
-
|
| 500 |
-
|
|
|
|
|
|
|
|
|
|
| 501 |
gr.Markdown(
|
| 502 |
-
"
|
| 503 |
)
|
| 504 |
-
|
| 505 |
with gr.Row():
|
| 506 |
with gr.Column(scale=1):
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 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 |
-
|
| 527 |
-
label="
|
| 528 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 529 |
interactive=True
|
| 530 |
)
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
label="
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
interactive=True,
|
| 538 |
-
value=""
|
| 539 |
)
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 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 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
)
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
inputs=prompt_output,
|
| 590 |
-
outputs=copy_status
|
| 591 |
)
|
| 592 |
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 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 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 620 |
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 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=
|
| 665 |
-
inputs=[
|
| 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()
|