import pyautogui import time import os import subprocess import win32gui, win32con, win32process import pyscreeze class WhaleTranslator: def __init__(self, logger, error_image_filenames=['fail_translated1.png', 'fail_translated2.png'], pixel_check_interval=0.1, secret_mode=True, timeout=20, color_tolerance=20): self.logger = logger self.error_image_paths = [os.path.join(os.path.dirname(__file__), filename) for filename in error_image_filenames] self.page_loading_icon_path = os.path.join(os.path.dirname(__file__), 'page_loading.png') self.translating_image_path = os.path.join(os.path.dirname(__file__), 'translating.png') # self.translating_image_path = os.path.join(os.path.dirname(__file__), 'src', 'img', 'translating.png') self.pixel_check_interval = pixel_check_interval self.whale_window_name = "새 시크릿 탭 - Whale" if secret_mode else "새 탭 - Whale" self.whale_pid = None self.timeout = timeout # 번역 성공 여부를 판단하기 위한 시간 제한 설정 self.color_tolerance = color_tolerance # 색상 허용 오차 self.colors = {'before': None, 'during': None, 'after': None} # 색상 기록 self.whale_rect = None def start_whale_browser(self, url): whale_path = r"C:\\Program Files\\Naver\\Naver Whale\\Application\\whale.exe" process = subprocess.Popen([whale_path, '--incognito']) time.sleep(2) self.whale_pid = process.pid self.logger.debug(f"Whale 브라우저 실행, PID: {self.whale_pid}") hwnd = self.find_whale_window() if hwnd: self.set_window_position(hwnd, 1, 1, 1280, 720) # 위치 (100, 100), 크기 (1280x720) self.update_whale_rect() if hwnd: win32gui.SetForegroundWindow(hwnd) pyautogui.hotkey('ctrl', 'l') time.sleep(0.5) pyautogui.typewrite(url) pyautogui.press('enter') time.sleep(2) size = self.get_whale_window_title() # # 페이지 로딩 완료 대기 # self.wait_for_loading_icon_to_disappear() self.move_mouse_to_center() # 마우스 중앙으로 이동 time.sleep(0.5) # 마우스 이동 후 대기 original_color = self.get_mouse_position_color() # 현재 색상 가져오기 self.colors['before'] = original_color self.logger.debug(f"번역 전 색상: {original_color}") # 우클릭 및 번역 시작 pyautogui.rightClick() time.sleep(1) pyautogui.press('r') # # 번역 시작 감지 (translating.png 확인) # if self.is_translating(): # self.logger.debug("번역 시작 감지 ('translating.png' 발견)") # 번역 중 색상 지속 확인 result = self.check_translation_by_color_change() if result == "success": self.logger.debug(f"URL: {url} - 번역이 성공적으로 완료되었습니다!") elif result == "error": self.logger.debug(f"URL: {url} - 번역에 실패했습니다.") else: self.logger.debug(f"URL: {url} - 번역 상태를 확인하지 못했습니다.") # 최종 색상 출력 self.logger.debug(f"URL: {url} - 번역 중 색상: {self.colors['during']}") self.logger.debug(f"URL: {url} - 번역 후 색상: {self.colors['after']}") def wait_for_loading_icon_to_disappear(self, max_wait=20): """ 로딩 아이콘이 화면에서 사라질 때까지 대기합니다. max_wait: 최대 대기 시간 (초) """ start_time = time.time() while time.time() - start_time < max_wait: try: # 화면에서 아이콘 위치 확인 시도 icon_location = self.find_image_with_confidence(self.page_loading_icon_path, confidence=0.9) if icon_location: self.logger.debug("페이지 로딩 중...") time.sleep(0.5) # 간격을 두고 다시 확인 except pyautogui.ImageNotFoundException as e: # 아이콘이 화면에 없으면 로딩 완료로 간주 # self.logger.debug(f"페이지 로딩이 완료되었습니다. {e}", exc_info=True) self.logger.debug(f"페이지 로딩이 완료되었습니다.") return True # 로딩 완료 self.logger.debug("로딩 완료 대기 시간이 초과되었습니다.") return False def find_image_with_confidence(self, image_path, confidence=0.8): """이미지를 찾을 때 highest confidence 값을 로그로 출력하는 메서드""" if self.whale_rect: # 웨일 창의 크기를 region으로 설정 region = (self.whale_rect[0], self.whale_rect[1], self.whale_rect[2] - self.whale_rect[0], self.whale_rect[3] - self.whale_rect[1]) self.logger.debug(f"이미지를 찾을 영역: {region}") try: # locateOnScreen 시도 result = pyscreeze.locateOnScreen(image_path, confidence=confidence, region=region) if result: self.logger.debug(f"이미지를 찾았습니다: {image_path}") return result else: raise pyscreeze.ImageNotFoundException("Could not locate the image") except pyscreeze.ImageNotFoundException as e: # 'Could not locate the image' 메시지 발생 시 최고 confidence 값 로그 출력 if hasattr(e, 'args') and 'highest confidence' in str(e.args[0]): self.logger.error(f"{image_path} 이미지를 찾지 못했습니다만 유사한 부분을 찾았습니다. : [{e.args[0]}]") else: self.logger.error(f"{image_path}이미지를 찾지 못했습니다. 이미지와 유사한 부분을 찾지 못했습니다.") return None def is_translating(self): """translating.png가 화면에 나타나는지 확인하여 번역 시작 여부를 판단""" try: # translating_location = pyautogui.locateOnScreen(self.translating_image_path, confidence=0.8) translating_location = self.find_image_with_confidence(self.translating_image_path, confidence=0.4) if translating_location: return True except pyautogui.ImageNotFoundException as e: self.logger.debug(f"'translating.png'를 찾지 못했습니다. : {e}", exc_info=True) return False def update_whale_rect(self): """웨일 창의 위치 및 크기 rect를 업데이트""" if self.whale_hwnd: self.whale_rect = win32gui.GetWindowRect(self.whale_hwnd) self.logger.debug(f"웨일 창 크기 및 위치 저장: {self.whale_rect}") def get_whale_window_title(self): """ 현재 활성화된 웨일 창의 이름을 가져옵니다. 제목에서 이미지의 해상도를 확인한 후, 일정 크기 이하인 경우 작업을 패스하도록 처리합니다. """ # hwnd = self.find_whale_window() # 웨일 창 핸들 가져오기 if self.whale_hwnd: window_title = win32gui.GetWindowText(self.whale_hwnd) self.logger.debug(f"현재 웨일 창의 제목: {window_title}") # 해상도를 추출하기 위해 제목에서 괄호 안의 (숫자×숫자) 부분을 찾음 import re match = re.search(r"\((\d+)×(\d+)\)", window_title) if match: width = int(match.group(1)) height = int(match.group(2)) self.logger.debug(f"이미지 해상도: {width}×{height}") # 해상도가 기준보다 작은 경우 패스 if width < 300 or height < 200: self.logger.debug(f"이미지 해상도가 너무 작습니다. ({width}×{height}), 작업을 패스합니다.") return False # 작업을 수행하지 않음 return True # 작업을 계속 진행 else: self.logger.error("이미지 해상도를 가져오지 못했습니다.") return False self.logger.error("웨일 창을 찾을 수 없습니다.") return False def find_whale_window(self): """웨일 창을 제목을 기준으로 찾는 메서드""" def callback(hwnd, extra): if win32gui.IsWindowVisible(hwnd): title = win32gui.GetWindowText(hwnd) if self.whale_window_name in title: extra.append(hwnd) hwnd_list = [] win32gui.EnumWindows(callback, hwnd_list) if hwnd_list: self.whale_hwnd = hwnd_list[0] self.logger.debug(f"웨일 창을 찾았습니다: {self.whale_hwnd}") self.update_whale_rect() return self.whale_hwnd else: self.logger.debug("웨일 창을 찾지 못했습니다.") return None def set_window_position(self, hwnd, x, y, width, height): """지정된 위치와 크기로 창을 조정""" win32gui.ShowWindow(hwnd, win32con.SW_RESTORE) win32gui.SetWindowPos(hwnd, None, x, y, width, height, win32con.SWP_NOZORDER | win32con.SWP_NOACTIVATE) self.logger.debug(f"창 위치 및 크기 설정: 위치({x}, {y}), 크기({width}x{height})") def move_mouse_to_center(self): """웨일 브라우저 창의 중앙으로 마우스 커서를 이동""" if self.whale_rect: center_x = (self.whale_rect[0] + self.whale_rect[2]) // 2 # 가로 중앙 계산 center_y = (self.whale_rect[1] + self.whale_rect[3]) // 2 # 세로 중앙 계산 center_y = center_y + 50 pyautogui.moveTo(center_x, center_y) self.logger.debug(f"마우스 커서를 창 중앙으로 이동: ({center_x}, {center_y})") else: self.logger.error("웨일 창의 크기를 알 수 없습니다. 먼저 창을 찾으세요.") def is_color_changed(self, current_color): """현재 색상이 변화했는지 확인 (필터 제거 여부)""" if self.colors['during'] is None: self.colors['during'] = current_color return not self.is_similar_color(current_color, self.colors['during']) def check_translation_by_color_change(self): start_time = time.time() # 번역 시작 감지 (translating.png 확인) while time.time() - start_time < self.timeout: current_color = self.get_mouse_position_color() self.logger.debug(f"현재 색상: {current_color}") # 필터가 사라져서 색상이 변했는지 확인 if self.is_color_changed(current_color): self.colors['after'] = current_color self.logger.debug("색상 변화 감지 (필터 제거됨)") # translating.png가 여전히 존재하는지 확인하여 번역 성공 여부 판단 result = self.find_image_with_confidence(self.translating_image_path, confidence=0.4) if result: return "success" else: return "error" else: # 번역 실패 이미지 확인 if self.is_translation_failed(): self.colors['after'] = current_color return "error" time.sleep(self.pixel_check_interval) # 타임아웃 발생 시 translating.png 여부로 번역 성공/실패 판단 self.colors['after'] = current_color result = self.find_image_with_confidence(self.translating_image_path, confidence=0.4) if result: self.logger.debug("번역 성공으로 간주 (타임아웃 후 translating.png 존재)") return "success" else: self.logger.debug("번역 실패로 간주 (타임아웃 후 translating.png 없음)") return "error" def is_similar_color(self, color1, color2): """색상이 유사한지 확인 (허용 오차 적용)""" r_diff = abs(color1[0] - color2[0]) g_diff = abs(color1[1] - color2[1]) b_diff = abs(color1[2] - color2[2]) return r_diff < self.color_tolerance and g_diff < self.color_tolerance and b_diff < self.color_tolerance def get_mouse_position_color(self): x, y = pyautogui.position() return pyautogui.pixel(x, y) def is_translation_failed(self): """번역 실패 이미지 확인""" for image_path in self.error_image_paths: result = self.find_image_with_confidence(image_path, confidence=0.8) if result: self.logger.error(f"번역 실패: '{os.path.basename(image_path)}' 메시지가 감지되었습니다.") return True return False # 예제 사용법 import logging # 로거 설정 logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) # 클래스 인스턴스 생성 및 브라우저 실행 후 번역 상태 확인 translator = WhaleTranslator(logger, ['fail_translated1.png', 'fail_translated2.png']) # 브라우저 실행 후 URL로 이동 및 번역 성공 여부 확인 translator.start_whale_browser('https://img.alicdn.com/imgextra/i3/350475995/O1CN01uFwQ9v1u9kwOuU78C-350475995.png_Q75.jpg') translator.start_whale_browser("https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/66ff967773994c46d388bb36/82d07178-ae60-49f7-a489-e02801ff7b06.jpg") translator.start_whale_browser('https://img.alicdn.com/imgextra/i4/735691568/O1CN01sRUYqb1NSBuefMBlw_!!735691568.jpg_Q75.jpg') translator.start_whale_browser('https://img.alicdn.com/imgextra/i4/1773313923/O1CN01VMRs1Z1eqmfYSXQDu_!!1773313923.jpg_Q75.jpg')