import os import tempfile from PIL import Image import gradio as gr import logging import re import io from io import BytesIO from google import genai from google.genai import types # 환경변수 로드 from dotenv import load_dotenv load_dotenv() # 로깅 설정 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) def save_binary_file(file_name, data): with open(file_name, "wb") as f: f.write(data) def preprocess_prompt(prompt, image1, image2, image3): """ 프롬프트를 처리하고 기능 명령을 해석 """ # 기존 preprocess_prompt 함수 코드 유지 # 이미지 없는 참조 확인 및 처리 has_img1 = image1 is not None has_img2 = image2 is not None has_img3 = image3 is not None # #1, #2, #3 참조를 설명으로 변환 (이미지가 없는 경우 무시) if "#1" in prompt and not has_img1: prompt = prompt.replace("#1", "첫 번째 이미지(없음)") else: prompt = prompt.replace("#1", "첫 번째 이미지") if "#2" in prompt and not has_img2: prompt = prompt.replace("#2", "두 번째 이미지(없음)") else: prompt = prompt.replace("#2", "두 번째 이미지") if "#3" in prompt and not has_img3: prompt = prompt.replace("#3", "세 번째 이미지(없음)") else: prompt = prompt.replace("#3", "세 번째 이미지") # 기능 명령 해석 if "1. 이미지 변경" in prompt: # 설명 추출을 시도하지만 실패해도 기본 프롬프트 제공 desc_match = re.search(r'#1을 "(.*?)"으로 바꿔라', prompt) if desc_match: description = desc_match.group(1) prompt = f"첫 번째 이미지를 {description}으로 변경해주세요. 원본 이미지의 주요 내용은 유지하되 새로운 스타일과 분위기로 재해석해주세요." else: prompt = "첫 번째 이미지를 창의적으로 변형해주세요. 더 생생하고 예술적인 버전으로 만들어주세요." elif "2. 글자지우기" in prompt: # 지울 텍스트 추출을 시도하지만 실패해도 기본 프롬프트 제공 text_match = re.search(r'#1에서 "(.*?)"를 지워라', prompt) if text_match: text_to_remove = text_match.group(1) prompt = f"첫 번째 이미지에서 '{text_to_remove}' 텍스트를 찾아 자연스럽게 제거해주세요. 텍스트가 있던 부분을 배경과 조화롭게 채워주세요." else: prompt = "첫 번째 이미지에서 모든 텍스트를 찾아 자연스럽게 제거해주세요. 깔끔한 이미지로 만들어주세요." elif "3. 얼굴바꾸기" in prompt: prompt = "첫 번째 이미지의 인물 얼굴을 두 번째 이미지의 얼굴로 자연스럽게 교체해주세요. 얼굴의 표정과 특징은 두 번째 이미지를 따르되, 나머지 부분은 첫 번째 이미지를 유지해주세요." elif "4. 옷바꾸기" in prompt: # 여러 이미지 참조 처리 if "#3" in prompt or "또는 #3" in prompt: prompt = "첫 번째 이미지의 인물 의상을 두 번째 또는 세 번째 이미지의 의상으로 자연스럽게 교체해주세요. 의상의 스타일과 색상은 참조 이미지를 따르되, 신체 비율과 포즈는 첫 번째 이미지를 유지해주세요." else: prompt = "첫 번째 이미지의 인물 의상을 두 번째 이미지의 의상으로 자연스럽게 교체해주세요. 의상의 스타일과 색상은 두 번째 이미지를 따르되, 신체 비율과 포즈는 첫 번째 이미지를 유지해주세요." elif "5. 배경바꾸기" in prompt: prompt = "첫 번째 이미지의 배경을 두 번째 이미지의 배경으로 자연스럽게 교체해주세요. 첫 번째 이미지의 주요 피사체는 유지하고, 두 번째 이미지의 배경과 조화롭게 합성해주세요." elif "6. 이미지 합성(상품포함)" in prompt: # 여러 이미지 참조 처리 if "#3" in prompt or "또는 #3" in prompt: prompt = "첫 번째 이미지와 두 번째, 세 번째 이미지를 자연스럽게 합성해주세요. 모든 이미지의 주요 요소를 포함하고, 특히 상품이 잘 보이도록 조화롭게 통합해주세요." else: prompt = "첫 번째 이미지와 두 번째 이미지를 자연스럽게 합성해주세요. 두 이미지의 주요 요소를 포함하고, 특히 상품이 잘 보이도록 조화롭게 통합해주세요." elif "7. 이미지 합성(스타일적용)" in prompt: prompt = "첫 번째 이미지의 내용을 두 번째 이미지의 스타일로 변환해주세요. 첫 번째 이미지의 주요 피사체와 구도는 유지하되, 두 번째 이미지의 예술적 스타일, 색상, 질감을 적용해주세요." # 간단한 색상 변경 요청 처리 elif "을 붉은색으로 바꿔라" in prompt or "를 붉은색으로 바꿔라" in prompt: prompt = "첫 번째 이미지를 붉은색 톤으로 변경해주세요. 전체적인 색상을 붉은 계열로 조정하고 자연스러운 느낌을 유지해주세요." # 명확한 이미지 생성 요청 추가 prompt += " 이미지를 생성해주세요." return prompt def generate_with_images(prompt, images): """ 공식 문서에 기반한 올바른 API 호출 방식 구현 """ try: # API 키 확인 api_key = os.environ.get("GEMINI_API_KEY") if not api_key: return None, "API 키가 설정되지 않았습니다. 환경변수를 확인해주세요." # Gemini 클라이언트 초기화 client = genai.Client(api_key=api_key) logger.info(f"Gemini API 요청 시작 - 프롬프트: {prompt}") # 컨텐츠 준비 contents = [] # 텍스트 프롬프트 추가 contents.append(prompt) # 이미지 추가 for idx, img in enumerate(images, 1): if img is not None: contents.append(img) logger.info(f"이미지 #{idx} 추가됨") # 생성 설정 - 공식 문서에 따라 responseModalities 설정 response = client.models.generate_content( model="gemini-2.0-flash-exp-image-generation", contents=contents, config=types.GenerateContentConfig( response_modalities=['Text', 'Image'], temperature=1, top_p=0.95, top_k=40, max_output_tokens=8192 ) ) # 임시 파일 생성 with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp: temp_path = tmp.name result_text = "" image_found = False # 응답 처리 for part in response.candidates[0].content.parts: if hasattr(part, 'text') and part.text: result_text += part.text logger.info(f"응답 텍스트: {part.text}") elif hasattr(part, 'inline_data') and part.inline_data: save_binary_file(temp_path, part.inline_data.data) image_found = True logger.info("응답에서 이미지 추출 성공") if not image_found: return None, f"API에서 이미지를 생성하지 못했습니다. 응답 텍스트: {result_text}" # 결과 이미지 반환 result_img = Image.open(temp_path) if result_img.mode == "RGBA": result_img = result_img.convert("RGB") return result_img, f"이미지가 성공적으로 생성되었습니다. {result_text}" except Exception as e: logger.exception("이미지 생성 중 오류 발생:") return None, f"오류 발생: {str(e)}" def process_images_with_prompt(image1, image2, image3, prompt): """ 3개의 이미지와 프롬프트를 처리하는 함수 """ try: # 이미지 개수 확인 images = [image1, image2, image3] valid_images = [img for img in images if img is not None] if not valid_images: return None, "적어도 하나의 이미지를 업로드해주세요." # 프롬프트 처리 if not prompt or not prompt.strip(): # 프롬프트가 없으면 업로드된 이미지 수에 따라 자동 합성 프롬프트를 영어로 생성 if len(valid_images) == 1: prompt = "Please creatively transform this image into a more vivid and artistic version." logger.info("Default prompt generated for single image") elif len(valid_images) == 2: prompt = "Please seamlessly composite these two images, integrating their key elements harmoniously into a single image." logger.info("Default prompt generated for two images") else: prompt = "Please creatively composite these three images, combining their main elements into a cohesive and natural scene." logger.info("Default prompt generated for three images") else: # 프롬프트 전처리 및 기능 명령 해석 prompt = preprocess_prompt(prompt, image1, image2, image3) # 새로운 API 호출 방식 사용 return generate_with_images(prompt, valid_images) except Exception as e: logger.exception("이미지 처리 중 오류 발생:") return None, f"오류 발생: {str(e)}" # (기능 선택 관련 코드 전체 삭제됨) # Gradio 인터페이스 with gr.Blocks() as demo: gr.HTML( """
이미지를 업로드하고 바로 실행하면 자동으로 합성합니다.