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__) # LLM 설정 및 번역 함수 def get_translation(korean_text): """ 한국어 텍스트를 영어로 번역 """ try: api_key = os.environ.get("GEMINI_API_KEY") if not api_key: logger.error("GEMINI_API_KEY가 설정되지 않았습니다.") return korean_text # 클라이언트 초기화 client = genai.GenerativeModel( model_name="gemini-2.0-flash", generation_config={ "temperature": 0.2, "max_output_tokens": 1024, "top_p": 0.9, }, system_instruction="You are a professional translator who translates Korean to English accurately.", api_key=api_key ) # 번역 프롬프트 translation_prompt = f""" Translate the following Korean text to English accurately: {korean_text} Provide only the translation, no explanations. """ # 번역 요청 response = client.generate_content(translation_prompt) # 응답에서 텍스트 추출 if hasattr(response, 'text'): english_text = response.text.strip() logger.info(f"번역 결과: {english_text}") return english_text else: logger.warning("번역 응답에 text 속성이 없습니다.") return korean_text except Exception as e: logger.exception(f"번역 중 오류 발생: {str(e)}") return korean_text # 오류 발생 시 원본 한국어 프롬프트 반환 def save_binary_file(file_name, data): with open(file_name, "wb") as f: f.write(data) def preprocess_prompt(prompt, image1=None, image2=None, image3=None): """ 프롬프트를 처리하고 기능 명령을 해석 """ # 이미지 없는 참조 확인 및 처리 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: prompt = "첫 번째 이미지를 창의적으로 변형해주세요. 더 생생하고 예술적인 버전으로 만들어주세요." elif "2. 글자지우기" in prompt: 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 호출 방식 구현 재시도 로직 추가 """ max_retries = 2 retries = 0 while retries <= max_retries: 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) # 프롬프트 번역 english_prompt = get_translation(prompt) logger.info(f"원본 프롬프트: {prompt}") logger.info(f"번역된 프롬프트: {english_prompt}") # 컨텐츠 준비 contents = [] # 텍스트 프롬프트 추가 contents.append(english_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: if retries < max_retries: retries += 1 logger.warning(f"이미지 생성 실패, 재시도 {retries}/{max_retries}") continue else: 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: if retries < max_retries: retries += 1 logger.warning(f"이미지 생성 중 오류 발생, 재시도 {retries}/{max_retries}: {str(e)}") else: logger.exception("이미지 생성 중 오류 발생:") return None, f"오류 발생: {str(e)}" def process_images_with_prompt(image1, image2, image3, prompt): """ 3개의 이미지와 프롬프트를 처리하는 함수 """ try: # 이미지 개수 확인 images = [img for img in [image1, image2, image3] if img is not None] # 프롬프트 처리 if not prompt or not prompt.strip(): # 프롬프트가 없으면 업로드된 이미지 수에 따라 자동 합성 프롬프트 생성 if len(images) == 0: prompt = "아름다운 자연 풍경 이미지를 생성해주세요. 산, 호수, 하늘이 포함된 평화로운 장면이면 좋겠습니다." logger.info("이미지 없음, 기본 생성 프롬프트 사용") elif len(images) == 1: prompt = "이 이미지를 창의적으로 변형해주세요. 더 생생하고 예술적인 버전으로 만들어주세요." logger.info("단일 이미지 프롬프트 자동 생성") elif len(images) == 2: prompt = "이 두 이미지를 자연스럽게 합성해주세요. 두 이미지의 요소를 조화롭게 통합하여 하나의 이미지로 만들어주세요." logger.info("두 이미지 합성 프롬프트 자동 생성") else: prompt = "이 세 이미지를 창의적으로 합성해주세요. 모든 이미지의 주요 요소를 포함하되 자연스럽고 일관된 하나의 장면으로 만들어주세요." logger.info("세 이미지 합성 프롬프트 자동 생성") else: # 프롬프트 전처리 및 기능 명령 해석 prompt = preprocess_prompt(prompt, image1, image2, image3) # 이미지 생성 API 호출 return generate_with_images(prompt, images) except Exception as e: logger.exception("이미지 처리 중 오류 발생:") return None, f"오류 발생: {str(e)}" # 기능 선택 콜백 (수정된 버전) def update_prompt_from_function(function_choice): function_templates = { "1. 이미지 변경": "#1을 창의적으로 변형해주세요", "2. 글자지우기": "#1에서 텍스트를 지워주세요", "3. 얼굴바꾸기": "#1의 인물을 #2의 얼굴로 바꿔라", "4. 옷바꾸기": "#1의 인물에 #2 또는 #3의 옷으로 변경하라", "5. 배경바꾸기": "#1의 이미지에 #2의 배경으로 자연스럽게 바꿔라", "6. 이미지 합성(상품포함)": "#1와 #2 또는 #3의 를 합성하라", "7. 이미지 합성(스타일적용)": "#1와 #2를 스타일로 변환하라" } return function_templates.get(function_choice, "") # Gradio 인터페이스 (수정된 버전) def create_interface(): with gr.Blocks() as demo: gr.HTML( """
이미지를 업로드하거나 프롬프트만 입력하여 이미지를 생성할 수 있습니다.