Kims12 commited on
Commit
649e6f6
·
verified ·
1 Parent(s): fe6aa7a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +408 -460
app.py CHANGED
@@ -1,4 +1,410 @@
1
- import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import google.generativeai as genai
3
  from PIL import Image
4
  import os
@@ -263,462 +669,4 @@ def preprocess_prompt(prompt, image1, image2, image3):
263
  prompt = "첫 번째 이미지를 창의적으로 변형해주세요. 더 생생하고 예술적인 버전으로 만들어주세요."
264
 
265
  elif "2. 글자지우기" in prompt:
266
- text_match = re.search(r'#1에서 "(.*?)"를 지워라', prompt)
267
- if text_match:
268
- text_to_remove = text_match.group(1)
269
- prompt = f"첫 번째 이미지에서 '{text_to_remove}' 텍스트를 찾아 자연스럽게 제거해주세요. 텍스트가 있던 부분을 배경과 조화롭게 채워주세요."
270
- else:
271
- prompt = "첫 번째 이미지에서 모든 텍스트를 찾아 자연스럽게 제거해주세요. 깔끔한 이미지로 만들어주세요."
272
-
273
- elif "4. 옷바꾸기" in prompt:
274
- prompt = "첫 번째 이미지의 인물 의상을 두 번째 이미지의 의상으로 변경해주세요. 의상의 스타일과 색상은 두 번째 이미지를 따르되, 신체 비율과 포즈는 첫 번째 이미지를 유지해주세요."
275
-
276
- elif "5. 배경바꾸기" in prompt:
277
- prompt = "첫 번째 이미지의 배경을 두 번째 이미지의 배경으로 변경해주세요. 첫 번째 이미지의 주요 피사체는 유지하고, 두 번째 이미지의 배경과 조화롭게 합성해주세요."
278
-
279
- elif "6. 이미지 합성(상품포함)" in prompt:
280
- prompt = "첫 번째 이미지와 두 번째 이미지(또는 세 번째 이미지)를 자연스럽게 합성해주세요. 모든 이미지의 주요 요소를 포함하고, 특히 상품이 돋보이도록 조화롭게 통합해주세요."
281
-
282
- prompt += " 이미지를 생성해주세요. 이미지에 텍스트나 글자를 포함하지 마세요."
283
- return prompt
284
-
285
- def generate_with_images(prompt, images, variation_index=0):
286
- try:
287
- if not GEMINI_API_KEY:
288
- return None, "API 키가 설정되지 않았습니다. 환경변수를 확인해주세요."
289
-
290
- model = genai.GenerativeModel('gemini-2.0-flash-exp-image-generation')
291
- logger.info(f"Gemini API 요청 시작 - 프롬프트: {prompt}, 변형 인덱스: {variation_index}")
292
-
293
- variation_suffixes = [
294
- " Create this as the first variation. Do not add any text, watermarks, or labels to the image.",
295
- " Create this as the second variation with more vivid colors. Do not add any text, watermarks, or labels to the image.",
296
- " Create this as the third variation with a more creative style. Do not add any text, watermarks, or labels to the image.",
297
- " Create this as the fourth variation with enhanced details. Do not add any text, watermarks, or labels to the image."
298
- ]
299
-
300
- if variation_index < len(variation_suffixes):
301
- prompt = prompt + variation_suffixes[variation_index]
302
- else:
303
- prompt = prompt + " Do not add any text, watermarks, or labels to the image."
304
-
305
- contents = [prompt]
306
- for idx, img in enumerate(images, 1):
307
- if img is not None:
308
- contents.append(img)
309
- logger.info(f"이미지 #{idx} 추가됨")
310
-
311
- response = model.generate_content(
312
- contents=contents,
313
- generation_config=genai.GenerationConfig(
314
- temperature=1,
315
- top_p=0.95,
316
- top_k=40,
317
- max_output_tokens=8192
318
- )
319
- )
320
-
321
- with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
322
- temp_path = tmp.name
323
- result_text = ""
324
- image_found = False
325
-
326
- if hasattr(response, 'candidates') and response.candidates:
327
- candidate = response.candidates[0]
328
- if hasattr(candidate, 'content') and candidate.content:
329
- for part in candidate.content.parts:
330
- if hasattr(part, 'text') and part.text:
331
- result_text += part.text
332
- logger.info(f"응답 텍스트: {part.text}")
333
- elif hasattr(part, 'inline_data') and part.inline_data:
334
- save_binary_file(temp_path, part.inline_data.data)
335
- image_found = True
336
- logger.info("응답에서 이미지 추출 성공")
337
-
338
- if not image_found:
339
- return None, f"API에서 이미지를 생성하지 못했습니다. 응답 텍스트: {result_text}"
340
-
341
- result_img = Image.open(temp_path)
342
- if result_img.mode == "RGBA":
343
- result_img = result_img.convert("RGB")
344
-
345
- return result_img, f"이미지가 성공적으로 생성되었습니다. {result_text}"
346
- except Exception as e:
347
- logger.exception("이미지 생성 중 오류 발생:")
348
- return None, f"오류 발생: {str(e)}"
349
-
350
- def process_images_with_prompt(image1, image2, image3, prompt, variation_index=0, max_retries=3):
351
- retry_count = 0
352
- last_error = None
353
-
354
- while retry_count < max_retries:
355
- try:
356
- images = [image1, image2, image3]
357
- valid_images = [img for img in images if img is not None]
358
- if not valid_images:
359
- return None, "적어도 하나의 이미지를 업로드해주세요.", ""
360
-
361
- if prompt and prompt.strip():
362
- processed_prompt = preprocess_prompt(prompt, image1, image2, image3)
363
- if re.search("[가-힣]", processed_prompt):
364
- final_prompt = translate_prompt_to_english(processed_prompt)
365
- else:
366
- final_prompt = processed_prompt
367
- else:
368
- if len(valid_images) == 1:
369
- final_prompt = "Please creatively transform this image into a more vivid and artistic version. Do not include any text or watermarks in the generated image."
370
- logger.info("Default prompt generated for single image")
371
- elif len(valid_images) == 2:
372
- final_prompt = "Please seamlessly composite these two images, integrating their key elements harmoniously into a single image. Do not include any text or watermarks in the generated image."
373
- logger.info("Default prompt generated for two images")
374
- else:
375
- final_prompt = "Please creatively composite these three images, combining their main elements into a cohesive and natural scene. Do not include any text or watermarks in the generated image."
376
- logger.info("Default prompt generated for three images")
377
-
378
- result_img, status = generate_with_images(final_prompt, valid_images, variation_index)
379
- if result_img is not None:
380
- return result_img, status, final_prompt
381
- else:
382
- last_error = status
383
- retry_count += 1
384
- logger.warning(f"이미지 생성 실패, 재시도 {retry_count}/{max_retries}: {status}")
385
- time.sleep(1)
386
- except Exception as e:
387
- last_error = str(e)
388
- retry_count += 1
389
- logger.exception(f"이미지 처리 중 오류 발생, 재시도 {retry_count}/{max_retries}:")
390
- time.sleep(1)
391
-
392
- return None, f"최대 재시도 횟수({max_retries}회) 초과 후 실패: {last_error}", prompt
393
-
394
- def generate_multiple_images(image1, image2, image3, prompt, progress=gr.Progress()):
395
- results = []
396
- statuses = []
397
- prompts = []
398
-
399
- num_images = 4
400
- max_retries = 3
401
-
402
- progress(0, desc="이미지 생성 준비 중...")
403
-
404
- for i in range(num_images):
405
- progress((i / num_images), desc=f"{i+1}/{num_images} 이미지 생성 중...")
406
- result_img, status, final_prompt = process_images_with_prompt(image1, image2, image3, prompt, i, max_retries)
407
-
408
- if result_img is not None:
409
- results.append(result_img)
410
- statuses.append(f"이미지 #{i+1}: {status}")
411
- prompts.append(f"이미지 #{i+1}: {final_prompt}")
412
- else:
413
- results.append(None)
414
- statuses.append(f"이미지 #{i+1} 생성 실패: {status}")
415
- prompts.append(f"이미지 #{i+1}: {final_prompt}")
416
-
417
- time.sleep(1)
418
-
419
- progress(1.0, desc="이미지 생성 완료!")
420
-
421
- while len(results) < 4:
422
- results.append(None)
423
-
424
- combined_status = "\n".join(statuses)
425
- combined_prompts = "\n".join(prompts)
426
-
427
- return results[0], results[1], results[2], results[3], combined_status, combined_prompts
428
-
429
- # 커스텀 CSS 스타일
430
- custom_css = """
431
- :root {
432
- --primary-color: #5561e9;
433
- --secondary-color: #6c8aff;
434
- --accent-color: #ff6b6b;
435
- --background-color: #f0f5ff;
436
- --card-bg: #ffffff;
437
- --text-color: #334155;
438
- --border-radius: 18px;
439
- --shadow: 0 8px 30px rgba(0, 0, 0, 0.08);
440
- }
441
-
442
- body {
443
- font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
444
- background-color: var(--background-color);
445
- color: var(--text-color);
446
- line-height: 1.6;
447
- }
448
-
449
- /* Gradio 컨테이너 오버라이드 */
450
- .gradio-container {
451
- max-width: 100% !important;
452
- margin: 0 auto !important;
453
- padding: 0 !important;
454
- background-color: var(--background-color) !important;
455
- }
456
-
457
- /* 상단 헤더 */
458
- .app-header {
459
- background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
460
- color: white;
461
- padding: 2rem;
462
- border-radius: var(--border-radius);
463
- margin-bottom: 1.5rem;
464
- box-shadow: var(--shadow);
465
- text-align: center;
466
- }
467
-
468
- .app-header h1 {
469
- margin: 0;
470
- font-size: 2.5rem;
471
- font-weight: 700;
472
- letter-spacing: -0.5px;
473
- }
474
-
475
- .app-header p {
476
- margin: 0.75rem 0 0;
477
- font-size: 1.1rem;
478
- opacity: 0.9;
479
- }
480
-
481
- /* 패널 스타일링 */
482
- .panel {
483
- background-color: var(--card-bg);
484
- border-radius: var(--border-radius);
485
- box-shadow: var(--shadow);
486
- padding: 1.5rem;
487
- margin-bottom: 1.5rem;
488
- border: 1px solid rgba(0, 0, 0, 0.04);
489
- transition: transform 0.3s ease;
490
- }
491
-
492
- .panel:hover {
493
- transform: translateY(-5px);
494
- }
495
-
496
- /* 섹션 제목 */
497
- .section-title {
498
- font-size: 1.5rem;
499
- font-weight: 700;
500
- color: var(--primary-color);
501
- margin-bottom: 1rem;
502
- padding-bottom: 0.5rem;
503
- border-bottom: 2px solid var(--secondary-color);
504
- display: flex;
505
- align-items: center;
506
- }
507
-
508
- .section-title i {
509
- margin-right: 0.5rem;
510
- font-size: 1.4rem;
511
- }
512
-
513
- /* 버튼 스타일링 */
514
- .custom-button {
515
- background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
516
- color: white !important;
517
- border: none !important;
518
- border-radius: calc(var(--border-radius) - 5px) !important;
519
- padding: 0.8rem 1.2rem !important;
520
- font-weight: 600 !important;
521
- cursor: pointer !important;
522
- transition: all 0.3s ease !important;
523
- box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
524
- text-transform: none !important;
525
- display: flex !important;
526
- align-items: center !important;
527
- justify-content: center !important;
528
- }
529
-
530
- .custom-button:hover {
531
- transform: translateY(-2px) !important;
532
- box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08) !important;
533
- }
534
-
535
- .custom-button.primary {
536
- background: linear-gradient(135deg, var(--accent-color), #ff9a8b) !important;
537
- }
538
-
539
- .custom-button i {
540
- margin-right: 0.5rem;
541
- }
542
-
543
- /* 이미지 컨테이너 */
544
- .image-container {
545
- border-radius: var(--border-radius);
546
- overflow: hidden;
547
- border: 1px solid rgba(0, 0, 0, 0.08);
548
- transition: all 0.3s ease;
549
- background-color: white;
550
- }
551
-
552
- .image-container:hover {
553
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
554
- }
555
-
556
- /* 탭 컨테이너 */
557
- .custom-tabs {
558
- background-color: transparent !important;
559
- border: none !important;
560
- margin-bottom: 1rem;
561
- }
562
-
563
- .custom-tabs button {
564
- background-color: rgba(255, 255, 255, 0.7) !important;
565
- border: none !important;
566
- border-radius: var(--border-radius) var(--border-radius) 0 0 !important;
567
- padding: 0.8rem 1.5rem !important;
568
- font-weight: 600 !important;
569
- color: var(--text-color) !important;
570
- transition: all 0.3s ease !important;
571
- }
572
-
573
- .custom-tabs button[aria-selected="true"] {
574
- background-color: var(--primary-color) !important;
575
- color: white !important;
576
- }
577
-
578
- /* 입력 필드 */
579
- .custom-input {
580
- border-radius: calc(var(--border-radius) - 5px) !important;
581
- border: 1px solid rgba(0, 0, 0, 0.1) !important;
582
- padding: 0.8rem 1rem !important;
583
- transition: all 0.3s ease !important;
584
- }
585
-
586
- .custom-input:focus {
587
- border-color: var(--primary-color) !important;
588
- box-shadow: 0 0 0 2px rgba(85, 97, 233, 0.2) !important;
589
- }
590
-
591
- /* 사용자 매뉴얼 */
592
- .user-manual {
593
- background-color: white;
594
- padding: 2rem;
595
- border-radius: var(--border-radius);
596
- box-shadow: var(--shadow);
597
- margin-top: 2rem;
598
- }
599
-
600
- .manual-title {
601
- font-size: 1.8rem;
602
- font-weight: 700;
603
- color: var(--primary-color);
604
- margin-bottom: 1.5rem;
605
- text-align: center;
606
- display: flex;
607
- align-items: center;
608
- justify-content: center;
609
- }
610
-
611
- .manual-title i {
612
- margin-right: 0.5rem;
613
- font-size: 1.8rem;
614
- }
615
-
616
- .manual-section {
617
- margin-bottom: 1.5rem;
618
- padding: 1.2rem;
619
- background-color: #f8faff;
620
- border-radius: calc(var(--border-radius) - 5px);
621
- }
622
-
623
- .manual-section-title {
624
- font-size: 1.3rem;
625
- font-weight: 700;
626
- margin-bottom: 1rem;
627
- color: var(--primary-color);
628
- display: flex;
629
- align-items: center;
630
- }
631
-
632
- .manual-section-title i {
633
- margin-right: 0.5rem;
634
- font-size: 1.2rem;
635
- }
636
-
637
- .manual-text {
638
- font-size: 1rem;
639
- line-height: 1.7;
640
- }
641
-
642
- .manual-text strong {
643
- color: var(--accent-color);
644
- }
645
-
646
- .tip-box {
647
- background-color: rgba(255, 107, 107, 0.1);
648
- border-left: 3px solid var(--accent-color);
649
- padding: 1rem 1.2rem;
650
- margin: 1rem 0;
651
- border-radius: 8px;
652
- }
653
-
654
- /* 버튼 그룹 */
655
- .button-grid {
656
- display: grid;
657
- grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
658
- gap: 0.8rem;
659
- margin-bottom: 1.2rem;
660
- }
661
-
662
- /* 로딩 애니메이션 */
663
- .progress-container {
664
- background-color: rgba(255, 255, 255, 0.9);
665
- border-radius: var(--border-radius);
666
- padding: 2rem;
667
- box-shadow: var(--shadow);
668
- text-align: center;
669
- }
670
-
671
- .progress-bar {
672
- height: 8px;
673
- border-radius: 4px;
674
- background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
675
- margin: 1rem 0;
676
- }
677
-
678
- /* 예시 그리드 */
679
- .examples-grid {
680
- display: grid;
681
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
682
- gap: 1rem;
683
- margin: 1.5rem 0;
684
- }
685
-
686
- .example-card {
687
- background: white;
688
- border-radius: var(--border-radius);
689
- overflow: hidden;
690
- box-shadow: var(--shadow);
691
- transition: transform 0.3s ease;
692
- }
693
-
694
- .example-card:hover {
695
- transform: translateY(-5px);
696
- }
697
-
698
- /* 반응형 */
699
- @media (max-width: 768px) {
700
- .button-grid {
701
- grid-template-columns: repeat(2, 1fr);
702
- }
703
- }
704
-
705
- /* 전체 인터페이스 둥근 모서리 */
706
- .block, .prose, .gr-prose, .gr-form, .gr-panel {
707
- border-radius: var(--border-radius) !important;
708
- }
709
-
710
- /* 메인 컨텐츠 스크롤바 */
711
- ::-webkit-scrollbar {
712
- width: 8px;
713
- height: 8px;
714
- }
715
-
716
- ::-webkit-scrollbar-track {
717
- background: rgba(0, 0, 0, 0.05);
718
- border-radius: 10px;
719
- }
720
-
721
- ::-webkit-scrollbar-thumb {
722
- background: var(--secondary-color);
723
- border-radius: 10px;
724
- }
 
1
+ if text_match:
2
+ text_to_remove = text_match.group(1)
3
+ prompt = f"첫 번째 이미지에서 '{text_to_remove}' 텍스트를 찾아 자연스럽게 제거해주세요. 텍스트가 있던 부분을 배경과 조화롭게 채워주세요."
4
+ else:
5
+ prompt = "첫 번째 이미지에서 모든 텍스트를 찾아 자연스럽게 제거해주세요. 깔끔한 이미지로 만들어주세요."
6
+
7
+ elif "4. 옷바꾸기" in prompt:
8
+ prompt = "첫 번째 이미지의 인물 의상을 두 번째 이미지의 의상으로 변경해주세요. 의상의 스타일과 색상은 두 번째 이미지를 따르되, 신체 비율과 포즈는 첫 번째 이미지를 유지해주세요."
9
+
10
+ elif "5. 배경바꾸기" in prompt:
11
+ prompt = "첫 번째 이미지의 배경을 두 번째 이미지의 배경으로 변경해주세요. 첫 번째 이미지의 주요 피사체는 유지하고, 두 번째 이미지의 배경과 조화롭게 합성해주세요."
12
+
13
+ elif "6. 이미지 합성(상품포함)" in prompt:
14
+ prompt = "첫 번째 이미지와 두 번째 이미지(또는 세 번째 이미지)를 자연스럽게 합성해주세요. 모든 이미지의 주요 요소를 포함하고, 특히 상품이 돋보이도록 조화롭게 통합해주세요."
15
+
16
+ prompt += " 이미지를 생성해주세요. 이미지에 텍스트나 글자를 포함하지 마세요."
17
+ return prompt
18
+
19
+ def generate_with_images(prompt, images, variation_index=0):
20
+ try:
21
+ if not GEMINI_API_KEY:
22
+ return None, "API 키가 설정되지 않았습니다. 환경변수를 확인해주세요."
23
+
24
+ model = genai.GenerativeModel('gemini-2.0-flash-exp-image-generation')
25
+ logger.info(f"Gemini API 요청 시작 - 프롬프트: {prompt}, 변형 인덱스: {variation_index}")
26
+
27
+ variation_suffixes = [
28
+ " Create this as the first variation. Do not add any text, watermarks, or labels to the image.",
29
+ " Create this as the second variation with more vivid colors. Do not add any text, watermarks, or labels to the image.",
30
+ " Create this as the third variation with a more creative style. Do not add any text, watermarks, or labels to the image.",
31
+ " Create this as the fourth variation with enhanced details. Do not add any text, watermarks, or labels to the image."
32
+ ]
33
+
34
+ if variation_index < len(variation_suffixes):
35
+ prompt = prompt + variation_suffixes[variation_index]
36
+ else:
37
+ prompt = prompt + " Do not add any text, watermarks, or labels to the image."
38
+
39
+ contents = [prompt]
40
+ for idx, img in enumerate(images, 1):
41
+ if img is not None:
42
+ contents.append(img)
43
+ logger.info(f"이미지 #{idx} 추가됨")
44
+
45
+ response = model.generate_content(
46
+ contents=contents,
47
+ generation_config=genai.GenerationConfig(
48
+ temperature=1,
49
+ top_p=0.95,
50
+ top_k=40,
51
+ max_output_tokens=8192
52
+ )
53
+ )
54
+
55
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
56
+ temp_path = tmp.name
57
+ result_text = ""
58
+ image_found = False
59
+
60
+ if hasattr(response, 'candidates') and response.candidates:
61
+ candidate = response.candidates[0]
62
+ if hasattr(candidate, 'content') and candidate.content:
63
+ for part in candidate.content.parts:
64
+ if hasattr(part, 'text') and part.text:
65
+ result_text += part.text
66
+ logger.info(f"응답 텍스트: {part.text}")
67
+ elif hasattr(part, 'inline_data') and part.inline_data:
68
+ save_binary_file(temp_path, part.inline_data.data)
69
+ image_found = True
70
+ logger.info("응답에서 이미지 추출 성공")
71
+
72
+ if not image_found:
73
+ return None, f"API에서 이미지를 생성하지 못했습니다. 응답 텍스트: {result_text}"
74
+
75
+ result_img = Image.open(temp_path)
76
+ if result_img.mode == "RGBA":
77
+ result_img = result_img.convert("RGB")
78
+
79
+ return result_img, f"이미지가 성공적으로 생성되었습니다. {result_text}"
80
+ except Exception as e:
81
+ logger.exception("이미지 생성 중 오류 발생:")
82
+ return None, f"오류 발생: {str(e)}"
83
+
84
+ def process_images_with_prompt(image1, image2, image3, prompt, variation_index=0, max_retries=3):
85
+ retry_count = 0
86
+ last_error = None
87
+
88
+ while retry_count < max_retries:
89
+ try:
90
+ images = [image1, image2, image3]
91
+ valid_images = [img for img in images if img is not None]
92
+ if not valid_images:
93
+ return None, "적어도 하나의 이미지를 업로드해주세요.", ""
94
+
95
+ if prompt and prompt.strip():
96
+ processed_prompt = preprocess_prompt(prompt, image1, image2, image3)
97
+ if re.search("[가-힣]", processed_prompt):
98
+ final_prompt = translate_prompt_to_english(processed_prompt)
99
+ else:
100
+ final_prompt = processed_prompt
101
+ else:
102
+ if len(valid_images) == 1:
103
+ final_prompt = "Please creatively transform this image into a more vivid and artistic version. Do not include any text or watermarks in the generated image."
104
+ logger.info("Default prompt generated for single image")
105
+ elif len(valid_images) == 2:
106
+ final_prompt = "Please seamlessly composite these two images, integrating their key elements harmoniously into a single image. Do not include any text or watermarks in the generated image."
107
+ logger.info("Default prompt generated for two images")
108
+ else:
109
+ final_prompt = "Please creatively composite these three images, combining their main elements into a cohesive and natural scene. Do not include any text or watermarks in the generated image."
110
+ logger.info("Default prompt generated for three images")
111
+
112
+ result_img, status = generate_with_images(final_prompt, valid_images, variation_index)
113
+ if result_img is not None:
114
+ return result_img, status, final_prompt
115
+ else:
116
+ last_error = status
117
+ retry_count += 1
118
+ logger.warning(f"이미지 생성 실패, 재시도 {retry_count}/{max_retries}: {status}")
119
+ time.sleep(1)
120
+ except Exception as e:
121
+ last_error = str(e)
122
+ retry_count += 1
123
+ logger.exception(f"이미지 처리 중 오류 발생, 재시도 {retry_count}/{max_retries}:")
124
+ time.sleep(1)
125
+
126
+ return None, f"최대 재시도 횟수({max_retries}회) 초과 후 실패: {last_error}", prompt
127
+
128
+ def generate_multiple_images(image1, image2, image3, prompt, progress=gr.Progress()):
129
+ results = []
130
+ statuses = []
131
+ prompts = []
132
+
133
+ num_images = 4
134
+ max_retries = 3
135
+
136
+ progress(0, desc="이미지 생성 준비 중...")
137
+
138
+ for i in range(num_images):
139
+ progress((i / num_images), desc=f"{i+1}/{num_images} 이미지 생성 중...")
140
+ result_img, status, final_prompt = process_images_with_prompt(image1, image2, image3, prompt, i, max_retries)
141
+
142
+ if result_img is not None:
143
+ results.append(result_img)
144
+ statuses.append(f"이미지 #{i+1}: {status}")
145
+ prompts.append(f"이미지 #{i+1}: {final_prompt}")
146
+ else:
147
+ results.append(None)
148
+ statuses.append(f"이미지 #{i+1} 생성 실패: {status}")
149
+ prompts.append(f"이미지 #{i+1}: {final_prompt}")
150
+
151
+ time.sleep(1)
152
+
153
+ progress(1.0, desc="이미지 생성 완료!")
154
+
155
+ while len(results) < 4:
156
+ results.append(None)
157
+
158
+ combined_status = "\n".join(statuses)
159
+ combined_prompts = "\n".join(prompts)
160
+
161
+ return results[0], results[1], results[2], results[3], combined_status, combined_prompts
162
+
163
+ def create_app():
164
+ with gr.Blocks() as demo:
165
+ gr.Markdown("# 고급 상품 이미지 배경 프롬프트 생성기")
166
+ gr.Markdown("상품 이미지를 업로드하고 옵션을 선택하면 고품질 상업용 프롬프트가 생성됩니다.")
167
+
168
+ with gr.Row():
169
+ with gr.Column():
170
+ image_input = gr.Image(type="pil", label="상품 이미지 업로드")
171
+
172
+ gr.Markdown("## 배경 유형 선택")
173
+ background_type = gr.Radio(
174
+ choices=["심플 배경", "스튜디오 배경", "자연 환경", "실내 환경", "추상/특수 배경"],
175
+ label="배경 유형",
176
+ value="심플 배경"
177
+ )
178
+
179
+ # 심플 배경 선택 드롭다운
180
+ simple_dropdown = gr.Dropdown(
181
+ choices=list(SIMPLE_BACKGROUNDS.keys()),
182
+ value=list(SIMPLE_BACKGROUNDS.keys())[0] if SIMPLE_BACKGROUNDS else None,
183
+ label="심플 배경 선택",
184
+ visible=True,
185
+ interactive=True
186
+ )
187
+
188
+ # 스튜디오 배경 선택 드롭다운
189
+ studio_dropdown = gr.Dropdown(
190
+ choices=list(STUDIO_BACKGROUNDS.keys()),
191
+ value=list(STUDIO_BACKGROUNDS.keys())[0] if STUDIO_BACKGROUNDS else None,
192
+ label="스튜디오 배경 선택",
193
+ visible=False,
194
+ interactive=True
195
+ )
196
+
197
+ # 자연 환경 선택 드롭다운
198
+ nature_dropdown = gr.Dropdown(
199
+ choices=list(NATURE_BACKGROUNDS.keys()),
200
+ value=list(NATURE_BACKGROUNDS.keys())[0] if NATURE_BACKGROUNDS else None,
201
+ label="자연 환경 선택",
202
+ visible=False,
203
+ interactive=True
204
+ )
205
+
206
+ # 실내 환경 선택 드롭다운
207
+ indoor_dropdown = gr.Dropdown(
208
+ choices=list(INDOOR_BACKGROUNDS.keys()),
209
+ value=list(INDOOR_BACKGROUNDS.keys())[0] if INDOOR_BACKGROUNDS else None,
210
+ label="실내 환경 선택",
211
+ visible=False,
212
+ interactive=True
213
+ )
214
+
215
+ # 추상/특수 배경 선택 드롭다운
216
+ abstract_dropdown = gr.Dropdown(
217
+ choices=list(ABSTRACT_BACKGROUNDS.keys()),
218
+ value=list(ABSTRACT_BACKGROUNDS.keys())[0] if ABSTRACT_BACKGROUNDS else None,
219
+ label="추상/특수 배경 선택",
220
+ visible=False,
221
+ interactive=True
222
+ )
223
+
224
+ # 상품명 입력
225
+ product_name = gr.Textbox(label="상품명 (한국어 입력)", placeholder="예: 스킨케어 튜브, 텀블러 등")
226
+
227
+ # 추가 요청사항
228
+ additional_info = gr.Textbox(
229
+ label="추가 요청사항 (선택사항)",
230
+ placeholder="예: 고급스러운 느낌, 밝은 조명, 자연스러운 보조적인 객체를 추가해주세요 등",
231
+ lines=3
232
+ )
233
+
234
+ generate_prompt_btn = gr.Button("프롬프트 생성")
235
+
236
+ with gr.Column():
237
+ prompt_output = gr.Textbox(label="생성된 프롬프트", lines=6)
238
+
239
+ gr.Markdown("## 이미지 생성")
240
+ with gr.Row():
241
+ generate_single_btn = gr.Button("이미지 생성 (1장)")
242
+ generate_multiple_btn = gr.Button("이미지 생성 (4장)")
243
+
244
+ gr.Markdown("## 생성된 이미지")
245
+ with gr.Row():
246
+ with gr.Column():
247
+ output_image1 = gr.Image(label="이미지 #1")
248
+ with gr.Column():
249
+ output_image2 = gr.Image(label="이미지 #2")
250
+ with gr.Row():
251
+ with gr.Column():
252
+ output_image3 = gr.Image(label="이미지 #3")
253
+ with gr.Column():
254
+ output_image4 = gr.Image(label="이미지 #4")
255
+
256
+ status_output = gr.Textbox(label="상태 메시지", lines=2)
257
+
258
+ # 배경 유형에 따라 드롭다운 표시 업데이트
259
+ def update_dropdowns(bg_type):
260
+ return {
261
+ simple_dropdown: gr.update(visible=(bg_type == "심플 배경")),
262
+ studio_dropdown: gr.update(visible=(bg_type == "스튜디오 배경")),
263
+ nature_dropdown: gr.update(visible=(bg_type == "자연 환경")),
264
+ indoor_dropdown: gr.update(visible=(bg_type == "실내 환경")),
265
+ abstract_dropdown: gr.update(visible=(bg_type == "추상/특수 배경"))
266
+ }
267
+
268
+ # 배경 유형 변경 이벤트 처리
269
+ background_type.change(
270
+ fn=update_dropdowns,
271
+ inputs=[background_type],
272
+ outputs=[simple_dropdown, studio_dropdown, nature_dropdown, indoor_dropdown, abstract_dropdown]
273
+ )
274
+
275
+ # 선택된 배경 정보 가져오기
276
+ def get_selected_background_info(bg_type, simple, studio, nature, indoor, abstract):
277
+ if bg_type == "심플 배경":
278
+ return {
279
+ "category": "심플 배경",
280
+ "name": simple,
281
+ "english": SIMPLE_BACKGROUNDS.get(simple, "white background")
282
+ }
283
+ elif bg_type == "스튜디오 배경":
284
+ return {
285
+ "category": "스튜디오 배경",
286
+ "name": studio,
287
+ "english": STUDIO_BACKGROUNDS.get(studio, "product photography studio")
288
+ }
289
+ elif bg_type == "자연 환경":
290
+ return {
291
+ "category": "자연 환경",
292
+ "name": nature,
293
+ "english": NATURE_BACKGROUNDS.get(nature, "natural environment")
294
+ }
295
+ elif bg_type == "실내 환경":
296
+ return {
297
+ "category": "실내 환경",
298
+ "name": indoor,
299
+ "english": INDOOR_BACKGROUNDS.get(indoor, "indoor environment")
300
+ }
301
+ elif bg_type == "추상/특수 배경":
302
+ return {
303
+ "category": "추상/특수 배경",
304
+ "name": abstract,
305
+ "english": ABSTRACT_BACKGROUNDS.get(abstract, "abstract background")
306
+ }
307
+ else:
308
+ return {
309
+ "category": "기본 배경",
310
+ "name": "화이트 배경",
311
+ "english": "white background"
312
+ }
313
+
314
+ # 프롬프트 생성 함수
315
+ def generate_prompt(image, bg_type, simple, studio, nature, indoor, abstract, product_text, additional_text):
316
+ if image is None:
317
+ gr.Warning("이미지를 업로드해주세요.")
318
+ return "이미지를 업로드해주세요."
319
+
320
+ product_text = product_text.strip() or "제품"
321
+
322
+ # 배경 정보 가져오기
323
+ background_info = get_selected_background_info(bg_type, simple, studio, nature, indoor, abstract)
324
+
325
+ try:
326
+ prompt = generate_prompt_with_gemini(product_text, background_info, additional_text)
327
+
328
+ if not GEMINI_API_KEY:
329
+ gr.Warning("Gemini API 키가 설정되지 않았습니다. 키를 발급받아 설정해주세요.")
330
+ prompt = """
331
+ [Gemini API 키 누락]
332
+ API 키 설정 방법:
333
+ 1. 환경 변수: export GEMINI_API_KEY="your-api-key"
334
+ 2. 코드 내 직접 입력: GEMINI_API_KEY = "your-api-key"
335
+ 키 발급: https://makersuite.google.com/
336
+ """
337
+ return prompt
338
+
339
+ return prompt
340
+ except Exception as e:
341
+ error_msg = f"프롬프트 생성 중 오류 발생: {str(e)}"
342
+ gr.Error(error_msg)
343
+ return error_msg
344
+
345
+ # 단일 이미지 생성 함수
346
+ def generate_single_image(image, prompt):
347
+ if image is None:
348
+ return None, None, None, None, "이미지를 업로드해주세요."
349
+
350
+ if not prompt or prompt.strip() == "":
351
+ return None, None, None, None, "프롬프트를 생성해주세요."
352
+
353
+ if not GEMINI_API_KEY:
354
+ return None, None, None, None, "Gemini API 키가 설정되지 않았습니다. 환경 변수를 확인해주세요."
355
+
356
+ try:
357
+ # 영어 프롬프트로 변환
358
+ if re.search("[가-힣]", prompt):
359
+ prompt = translate_prompt_to_english(prompt)
360
+
361
+ # 이미지 1개 생성 (variation_index=0)
362
+ result_img, status, _ = process_images_with_prompt(image, None, None, prompt, 0, 3)
363
+
364
+ if result_img is None:
365
+ return None, None, None, None, status
366
+
367
+ return result_img, None, None, None, status
368
+ except Exception as e:
369
+ error_msg = f"이미지 생성 중 오류 발생: {str(e)}"
370
+ return None, None, None, None, error_msg
371
+
372
+ # 프롬프트 생성 버튼 이벤트 연결
373
+ generate_prompt_btn.click(
374
+ fn=generate_prompt,
375
+ inputs=[
376
+ image_input,
377
+ background_type,
378
+ simple_dropdown,
379
+ studio_dropdown,
380
+ nature_dropdown,
381
+ indoor_dropdown,
382
+ abstract_dropdown,
383
+ product_name,
384
+ additional_info
385
+ ],
386
+ outputs=prompt_output
387
+ )
388
+
389
+ # 단일 이미지 생성 버튼 이벤트 연결
390
+ generate_single_btn.click(
391
+ fn=generate_single_image,
392
+ inputs=[image_input, prompt_output],
393
+ outputs=[output_image1, output_image2, output_image3, output_image4, status_output]
394
+ )
395
+
396
+ # 여러 이미지 생성 버튼 이벤트 연결
397
+ generate_multiple_btn.click(
398
+ fn=lambda img, prompt: generate_multiple_images(img, None, None, prompt),
399
+ inputs=[image_input, prompt_output],
400
+ outputs=[output_image1, output_image2, output_image3, output_image4, status_output, prompt_output]
401
+ )
402
+
403
+ return demo
404
+
405
+ if __name__ == "__main__":
406
+ app = create_app()
407
+ app.launch()import gradio as gr
408
  import google.generativeai as genai
409
  from PIL import Image
410
  import os
 
669
  prompt = "첫 번째 이미지를 창의적으로 변형해주세요. 더 생생하고 예술적인 버전으로 만들어주세요."
670
 
671
  elif "2. 글자지우기" in prompt:
672
+ text_match = re.search(r'#1에서 "(.*?)"를 지워라', prompt)