ssboost commited on
Commit
dad4dcc
·
verified ·
1 Parent(s): d62a2a1

Update app.py

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