import pyautogui import os import time import win32gui, win32con, win32process from pyvda import VirtualDesktop, get_virtual_desktops import subprocess import pyscreeze import KO_EN import pyperclip # 클립보드 데이터를 확인하기 위한 라이브러리 from PIL import ImageGrab import re class WhaleTranslator: def __init__(self, app, logger, secret_mode=True, vd_mode=False, pixel_check_interval=0.1, timeout=10, color_tolerance=20): self.app = app self.logger = logger self.vd_mode = vd_mode self.newtab = "about:newtab" self.whale_pid = None self.whale_rect = None isSecret = secret_mode self.pixel_check_interval = pixel_check_interval self.timeout = timeout # 번역 성공 여부를 판단하기 위한 시간 제한 설정 self.color_tolerance = color_tolerance # 색상 허용 오차 self.colors = {'before': None, 'during': None, 'after': None} # 색상 기록 # main.py 실행 경로의 하위 폴더(src/img)에서 이미지 파일 경로 설정 base_dir = os.path.dirname(os.path.abspath(__file__)) img_dir = os.path.join(base_dir, 'src', 'img') self.error_image_path1 = os.path.join(img_dir, 'fail_translated1.png') self.error_image_path2 = os.path.join(img_dir, 'fail_translated2.png') self.page_loading_icon_path = os.path.join(img_dir, 'page_loading.png') self.translating_image_path = os.path.join(img_dir, 'translating.png') self.translation_success_flag = False # 번역 성공 플래그 self.failure_count = 0 # 실패 횟수 if isSecret: self.whale_window_name = "새 시크릿 탭 - Whale" else: self.whale_window_name = "새 탭 - Whale" self.whale_hwnd = None def start_whale_browser(self): """비동기 브라우저 시작 및 가상 데스크탑 처리""" if self.vd_mode: self.ensure_virtual_desktop_2_exists() # 가상 데스크탑 2 생성 # Whale 브라우저 실행 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}") # 창을 찾기 전 대기 (최대 10초 동안 반복 시도) wait_time = 0 self.whale_hwnd = None while self.whale_hwnd is None and wait_time < 10: self.whale_hwnd = win32gui.FindWindow(None, self.whale_window_name) if self.whale_hwnd: self.logger.debug(f"Whale 창을 찾았습니다: {self.whale_hwnd}") break time.sleep(1) wait_time += 1 self.logger.debug(f"Whale 창을 찾지 못했습니다. 재시도 중... ({wait_time}초 경과)") # 창을 찾지 못한 경우 처리 if not self.whale_hwnd: self.logger.debug("Whale 창을 찾을 수 없습니다.") return # 창 크기 조절 및 포커스 이동 # win32gui.ShowWindow(self.whale_hwnd, win32con.SW_NORMAL) # win32gui.SetWindowPos(self.whale_hwnd, None, 0, 0, 1920, 1080, win32con.SWP_NOZORDER) # self.logger.debug("Whale 창 크기 조절 완료") self.set_window_position(self.whale_hwnd, 1, 1, 1280, 720) # 위치 (1, 1), 크기 (1280x720) self.update_whale_rect() # 주소창으로 이동 후 URL 입력 # pyautogui.hotkey('ctrl', 'l') # time.sleep(0.4) # pyautogui.typewrite('https://daum.net') # self.change_lang() # self.enter_url("about:newtab", change=True) # self.logger.debug("URL 입력 완료") # 가상 데스크탑 복귀 처리 if self.vd_mode: self.return_to_virtual_desktop_1() # 가상 데스크탑 1로 복귀 def reset_failures(self): """실패 횟수를 초기화""" self.failure_count = 0 self.logger.debug("실패 횟수가 초기화되었습니다.") # def handle_translation_failure(self): # """번역 실패 시 처리""" # self.failure_count += 1 # self.logger.error(f"번역 실패! 실패 횟수: {self.failure_count}/{self.max_failures}") # if self.failure_count >= self.max_failures: # self.logger.error("최대 실패 횟수에 도달했습니다. 웨일 브라우저를 재시작합니다.") # self.close_whale_window_if_exists() # time.sleep(2) # 재시작 전에 짧은 대기 # asyncio.run(self.start_whale_browser()) # 브라우저 재시작 # self.reset_failures() # 실패 횟수 초기화 # def is_image_in_clipboard_with_text(self): # """클립보드에 이미지 데이터 또는 base64로 인코딩된 이미지 데이터가 있는지 확인""" # clipboard_content = pyperclip.paste() # if clipboard_content.startswith("data:image") or isinstance(clipboard_content, bytes): # self.logger.debug("클립보드에 이미지 데이터가 확인되었습니다.") # return True # else: # self.logger.debug("클립보드에 이미지 데이터가 없습니다.") # return False def is_image_in_clipboard(self): """클립보드에 이미지 데이터가 있는지 확인""" try: # 클립보드에서 이미지를 가져오고 None이 아니면 이미지가 있는 것으로 간주 image = ImageGrab.grabclipboard() if isinstance(image, bytes) or image is not None: self.logger.debug("클립보드에 이미지 데이터가 확인되었습니다.") return True else: self.logger.debug("클립보드에 이미지 데이터가 없습니다.") return False except Exception as e: self.logger.error(f"클립보드에서 이미지 확인 중 오류 발생: {e}", exc_info=True) 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 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 find_window_by_title(self, window_name): def enum_windows_callback(hwnd, result): if win32gui.IsWindowVisible(hwnd) and window_name in win32gui.GetWindowText(hwnd): result.append(hwnd) result = [] win32gui.EnumWindows(enum_windows_callback, result) return result[0] if result else None def find_whale_window_pid(self): """프로세스 ID를 기반으로 웨일 창 핸들을 찾는 메서드""" if not self.whale_hwnd: self.whale_hwnd = self.find_window_by_pid(self.whale_pid) return self.whale_hwnd def find_window_by_pid(self, pid): """PID와 창 제목을 통해 유일한 웨일 창을 찾는 메서드""" def enum_windows_callback(hwnd, pid_list): tid, found_pid = win32process.GetWindowThreadProcessId(hwnd) window_title = win32gui.GetWindowText(hwnd) # 웨일의 창 제목이 일치하고 PID도 일치하는 창을 찾음 if found_pid == pid and self.whale_window_name in window_title: pid_list.append(hwnd) hwnd_list = [] win32gui.EnumWindows(enum_windows_callback, hwnd_list) if hwnd_list: self.logger.debug(f"웨일 창을 찾았습니다: {hwnd_list[0]}") return hwnd_list[0] else: self.logger.debug(f"PID {pid}에 해당하는 웨일 창을 찾지 못했습니다.") return None def find_window_by_pid_ori(self, pid): """프로세스 ID를 기반으로 창 핸들을 찾는 메서드""" def enum_windows_callback(hwnd, pid_list): tid, found_pid = win32process.GetWindowThreadProcessId(hwnd) if found_pid == pid: pid_list.append(hwnd) hwnd_list = [] win32gui.EnumWindows(enum_windows_callback, hwnd_list) return hwnd_list[0] if hwnd_list else None def ensure_virtual_desktop_2_exists(self): """가상 데스크톱 2가 존재하는지 확인하고, 없으면 생성""" try: # 현재 활성화된 가상 데스크톱 수 확인 desktops = get_virtual_desktops() number_of_desktops = len(desktops) # 가상 데스크톱 2가 존재하지 않으면 생성 if number_of_desktops < 2: pyautogui.hotkey('win', 'ctrl', 'd') # 새 가상데스크탑 생성 self.logger.debug("가상 데스크톱 2가 생성되었습니다.") time.sleep(1) else: self.switch_to_virtual_desktop_2() self.close_whale_window_if_exists() self.logger.debug("가상 데스크톱 2가 이미 존재합니다.") except Exception as e: self.logger.debug(f"가상 데스크톱 확인/생성 중 오류 발생: {e}", exc_info=True) def switch_to_whale(self): """웨일로 포커스 전환""" if self.whale_hwnd: win32gui.ShowWindow(self.whale_hwnd, win32con.SW_RESTORE) win32gui.SetForegroundWindow(self.whale_hwnd) self.logger.debug('크롬 창으로 포커스 이동.') else: self.logger.debug('크롬 창을 찾을 수 없습니다.') def switch_to_virtual_desktop_2(self): """가상 데스크톱 2로 전환""" try: VirtualDesktop(2).go() self.logger.debug("가상 데스크톱 2로 전환되었습니다.") time.sleep(0.3) except Exception as e: self.logger.debug(f"가상 데스크톱 전환 중 오류 발생: {e}", exc_info=True) def return_to_virtual_desktop_1(self): """가상 데스크톱 1로 복귀""" try: VirtualDesktop(1).go() self.logger.debug("가상 데스크톱 1로 전환되었습니다.") time.sleep(0.3) except Exception as e: self.logger.debug(f"가상 데스크톱 전환 중 오류 발생: {e}", exc_info=True) def translate_image(self, url, path=None): if self.vd_mode: self.switch_to_virtual_desktop_2() if not self.whale_hwnd: # 웨일 창을 찾지 못했을 경우 사용자에게 입력 받기 self.logger.debug("웨일 창을 찾지 못했습니다. 새로운 웨일창 호출.") self.create_and_update_whale_window() if url.startswith("https://assets.alicdn.com"): self.logger.debug("assets.alicdn.com 확인 : 번역작업을 패스합니다.") return False elif self.whale_hwnd: try: self.logger.debug(f"웨일 창을 찾았습니다.{self.whale_hwnd}") win32gui.ShowWindow(self.whale_hwnd, win32con.SW_RESTORE) # 웨일 창 활성화 win32gui.SetForegroundWindow(self.whale_hwnd) # pyautogui.hotkey('ctrl', 'l') # 웨일 브라우저의 주소창으로 이동 self.logger.debug(f"이미지 URL 주소 {url} 입력") self.enter_url_for_clipboard(url) pyautogui.press('enter') time.sleep(1) # 페이지 로딩 대기 # 현재 웨일 창의 해상도를 확인하여 기준 이하일 경우 패스 min_width = 200 min_height = 150 if not self.check_image_size(url, min_width=min_width,min_height=min_height): self.logger.info("해상도가 기준보다 낮아 작업을 패스합니다.") return False # 해상도 기준 미달로 작업 종료 # 페이지 로딩 완료 대기 # self.logger.debug(f"페이지 로딩완료 대기") # self.wait_for_loading_icon_to_disappear() self.logger.debug(f"페이지 로딩 완료 후 웨일 창의 가운데로 마우스 커서 이동") # pyautogui.moveTo(960,580) # 마우스 센터로 이동 self.move_mouse_to_center() time.sleep(0.4) # 마우스 이동 후 대기 # original_color = self.get_mouse_position_color() # 현재 색상 가져오기 # self.colors['before'] = original_color # self.logger.debug(f"번역 전 색상: {original_color}") self.logger.debug("번역 작업을 위한 마우스 오른쪽 클릭 및 R 전송") if not self.right_click_and_send_key('r'): self.logger.error("번역 작업이 대화상자 문제로 중단되었습니다.") return False # # 번역 성공 여부 확인 # result = self.check_translation_by_color_change() # if result == "success": # self.logger.debug("번역이 성공적으로 완료되었습니다!") # elif result == "error": # self.logger.debug("번역에 실패했습니다.") # else: # self.logger.debug("번역 상태를 확인하지 못했습니다.") # # 최종 색상 출력 # self.logger.debug(f"번역 전 색상: {self.colors['before']}") # self.logger.debug(f"번역 중 색상: {self.colors['during']}") # self.logger.debug(f"번역 후 색상: {self.colors['after']}") # self.check_translation_status(duration_ms=5000) # 5초 동안 번역실패 감지 time.sleep(6) self.logger.debug("이미지 복사를 위한 마우스 오른쪽 클릭 및 C 전송") if not self.right_click_and_send_key('c'): self.logger.error("복사 작업이 대화상자 문제로 중단되었습니다.") return False self.logger.debug(f"클립보드에 번역된이미지 복사 대기 1s") time.sleep(1) # 클립보드 업데이트 대기 self.logger.debug("클립보드에 이미지 데이터가 존재하는지 확인 중.....") self.translation_success_flag = self.is_image_in_clipboard() if self.translation_success_flag: # 클립보드에 이미지 데이터가 있으면 성공 self.logger.info(f'번역 성공: {url}') # self.reset_failures() # 번역 성공 시 연속 실패 횟수 초기화 else: self.logger.error(f'번역 실패: 클립보드에 이미지 데이터가 없음') # self.handle_translation_failure() self.logger.info(f'번역 프로세스 완료. 웨일 기본페이지로 돌아감') # self.enter_url(self.newtab) pyautogui.hotkey('alt', 'left') # 클립보드 이미지 붙여넣기 # self.logger.debug(f'번역 완료: {url}') if self.vd_mode: self.return_to_virtual_desktop_1() # 경로를 인자로 받을경우 해당경로에 파일 저장 if path: try: # 클립보드에서 이미지 가져오기 clipboard_image = ImageGrab.grabclipboard() if clipboard_image: clipboard_image.save(path, format='PNG') self.logger.info(f"번역된 이미지가 {path}에 저장되었습니다.") else: self.logger.error("클립보드에 이미지가 존재하지 않아 파일로 저장할 수 없습니다.") return False except Exception as e: self.logger.error(f"이미지 저장 중 오류 발생: {e}", exc_info=True) return False return True if self.translation_success_flag else False except Exception as e: self.logger.error(f"번역 중 오류 발생: {e}", exc_info=True) return False # self.handle_translation_failure() else: self.logger.debug('웨일 창을 찾을 수 없습니다.') return False # self.handle_translation_failure() def detect_unexpected_dialog(self): """예상치 못한 대화상자 감지""" try: dialog_hwnd = win32gui.FindWindow(None, "다른 이름으로 저장") # 저장 대화상자의 이름이 실제와 다를 수 있습니다. if dialog_hwnd: self.logger.debug("예상치 못한 '다른 이름으로 저장' 대화상자 발견!") win32gui.PostMessage(dialog_hwnd, win32con.WM_CLOSE, 0, 0) # 대화상자 닫기 time.sleep(1) return True except Exception as e: self.logger.error(f"대화상자 감지 중 오류 발생: {e}", exc_info=True) return False def create_and_update_whale_window(self): """ 웨일 창을 찾지 못했을 경우 새 웨일 창을 생성하고 핸들을 업데이트하는 메서드. """ self.logger.debug("웨일 창을 찾지 못해 새 웨일 창을 생성합니다.") # 웨일 브라우저 실행 whale_path = r"C:\\Program Files\\Naver\\Naver Whale\\Application\\whale.exe" process = subprocess.Popen([whale_path, '--incognito']) self.whale_pid = process.pid self.logger.debug(f"새 웨일 브라우저 실행, PID: {self.whale_pid}") # await asyncio.sleep(2) # 브라우저가 실행될 때까지 대기 time.sleep(2) # 창 핸들 업데이트 self.whale_hwnd = self.find_whale_window() if self.whale_hwnd: self.logger.debug(f"새로 생성된 웨일 창 핸들: {self.whale_hwnd}") win32gui.ShowWindow(self.whale_hwnd, win32con.SW_NORMAL) win32gui.SetWindowPos(self.whale_hwnd, None, 0, 0, 1920, 1080, win32con.SWP_NOZORDER) else: self.logger.debug("새로 생성된 웨일 창을 찾지 못했습니다.") # def switch_language(self): # # Shift 키 누름 # pyautogui.keyDown('shift') # # 0.1초 대기 후 Alt 키 누름 # time.sleep(1) # pyautogui.keyDown('alt') # # 0.5초 대기 # time.sleep(1) # # Alt 키 해제 # pyautogui.keyUp('alt') # # Shift 키 해제 # pyautogui.keyUp('shift') # def set_input_language(self, lang='EN'): # # 윈도우에서 입력 언어를 변경하는 코드 # # lang='EN' 또는 lang='KR'을 설정 가능 # user32 = ctypes.WinDLL('user32', use_last_error=True) # if lang == 'EN': # # 영어로 변경 # user32.LoadKeyboardLayoutW('00000409', 1) # elif lang == 'KR': # # 한글로 변경 # user32.LoadKeyboardLayoutW('00000412', 1) def change_lang(self): # 언어 전환이 완료되었는지 확인하는 함수 def wait_for_language_switch(): # 최대 3초까지 언어 전환 확인 시도 (0.1초 간격) for _ in range(30): if KO_EN.get_hanguel_state() == 0: # 영어로 전환된 상태가 0 return True time.sleep(0.1) # await asyncio.sleep(0.1) return False # 주소창에 URL 입력하기 전에 영어로 변경 KO_EN.set_keyboard_language('eng') # 언어 전환 완료 대기 is_switched = wait_for_language_switch() if not is_switched: self.logger.debug("영어로 전환하는데 실패했습니다.") return else: self.logger.debug("전환 성공") def enter_url(self, url, change=False): # 언어 전환이 완료되면 주소창으로 이동 후 URL 입력 pyautogui.hotkey('ctrl', 'l') # 주소창으로 이동 time.sleep(0.5) # 주소창 이동 후 잠시 대기 pyautogui.typewrite(url) # URL 입력 pyautogui.press('enter') # Enter 키 입력 time.sleep(1) # 페이지 로딩 대기 def enter_url_for_clipboard(self, url, change=False): # URL을 클립보드에 복사 pyperclip.copy(url) # 언어 전환이 완료되면 주소창으로 이동 후 URL 입력 pyautogui.hotkey('ctrl', 'l') # 주소창으로 이동 time.sleep(0.5) # 주소창 이동 후 잠시 대기 pyautogui.hotkey('ctrl', 'v') # 주소 입력 pyautogui.press('enter') # Enter 키 입력 time.sleep(1) # 페이지 로딩 대기 def close_whale_window_if_exists(self): """웨일 브라우저 창을 프로세스 ID(pid)로 찾아 종료""" try: if not self.whale_pid: self.logger.debug("웨일 프로세스 ID가 설정되지 않았습니다.") return def enum_windows_callback(hwnd, result): _, found_pid = win32process.GetWindowThreadProcessId(hwnd) if found_pid == self.whale_pid and win32gui.IsWindowVisible(hwnd): result.append(hwnd) result = [] win32gui.EnumWindows(enum_windows_callback, result) if result: hwnd = result[0] self.logger.debug(f"웨일 창을 찾았습니다. 핸들: {hwnd}. 종료 중...") win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0) # 창을 종료하는 메시지 전송 time.sleep(1) self.logger.debug(f"웨일 창이 성공적으로 종료되었습니다.") else: self.logger.debug("웨일 창을 찾을 수 없습니다.") except Exception as e: self.logger.error(f"웨일 창을 종료하는 중 오류 발생: {e}", exc_info=True) def close_all_virtual_desktops(self): """모든 가상 데스크톱을 종료""" try: desktops = get_virtual_desktops() number_of_desktops = len(desktops) # 가상 데스크톱이 1개 이상일 때만 종료 while number_of_desktops > 1: self.close_whale_window_if_exists() # 웨일 브라우저 창이 있으면 종료 pyautogui.hotkey('win', 'ctrl', 'f4') # 현재 가상 데스크톱 닫기 time.sleep(1) # 각 데스크톱 닫기 사이에 짧은 대기시간 추가 desktops = get_virtual_desktops() number_of_desktops = len(desktops) self.logger.debug(f"남은 가상 데스크톱 수: {number_of_desktops}") self.logger.debug("모든 가상 데스크톱이 종료되었습니다.") except Exception as e: self.logger.debug(f"가상 데스크톱 종료 중 오류 발생: {e}", exc_info=True) # def wait_for_loading_icon_to_disappear(self, max_wait=10): # """ # 로딩 아이콘이 화면에서 사라질 때까지 대기합니다. # max_wait: 최대 대기 시간 (초) # """ # start_time = time.time() # while time.time() - start_time < max_wait: # try: # # 화면에서 아이콘 위치 확인 시도 # icon_location = pyautogui.locateOnScreen(self.page_loading_icon_path, confidence=0.9) # if icon_location: # self.logger.debug("페이지 로딩 중...") # time.sleep(0.5) # 간격을 두고 다시 확인 # except pyautogui.ImageNotFoundException: # # 아이콘이 화면에 없으면 로딩 완료로 간주 # self.logger.debug("페이지 로딩이 완료되었습니다.") # return True # 로딩 완료 # self.logger.debug("로딩 완료 대기 시간이 초과되었습니다.") # return False 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 get_whale_window_title(self): """ 현재 활성화된 웨일 창의 이름을 가져옵니다. """ try: if self.whale_hwnd: window_title = win32gui.GetWindowText(self.whale_hwnd) self.logger.debug(f"현재 웨일 창의 제목: {window_title}") else: self.logger.warning(f"웨일 창 핸들이 없습니다.") except Exception as e: window_title = None self.logger.error(f"현재 웨일 창의 제목을 가져올 수 없습니다. : {e}", exc_info = True) return window_title def check_image_size(self, url, min_width=200, min_height=150): """ 현재 활성화된 웨일 창의 이름을 가져온 후 제목에서 이미지의 해상도를 확인하고, 일정 크기 이하인 경우 작업을 패스하도록 처리합니다. """ try: # 현재 웨일 창의 제목 가져오기 window_title = self.get_whale_window_title() # 창 제목이 초기 웨일 창 제목과 동일하다면 URL로 이동하여 새로 업데이트 if window_title == self.whale_window_name: self.logger.warning("이미지 주소로 이동하지 못하여 이미지 해상도를 가져오지 못했습니다.") self.enter_url(url) window_title = self.get_whale_window_title() # 가져온 창 제목에서 해상도 추출 시도 match = re.search(r"\((\d+)×(\d+)\)", window_title) if match: # 해상도가 있는 경우, 너비와 높이를 기준으로 작업 처리 width = int(match.group(1)) height = int(match.group(2)) self.logger.info(f"이미지 해상도: {width}×{height}") if width < min_width or height < min_height: self.logger.info(f"이미지 해상도가 기준 이하 [{min_width} x {min_height}]입니다. 작업을 패스합니다.") return False # 작업을 수행하지 않음 return True # 해상도 조건을 만족하면 작업을 진행 # 해상도가 없다면, 파일 확장자를 확인하여 번역 여부 결정 elif window_title.endswith(".jpg") or window_title.endswith(".png"): self.logger.info("이미지 해상도가 없지만, 파일 확장자가 .jpg 또는 .png입니다. 번역 작업을 진행합니다.") return True # 해상도가 없어도 번역 작업을 수행 # 해상도도 없고, 파일 확장자도 매칭되지 않는 경우 self.logger.warning("이미지 해상도가 존재하지 않고 파일 확장자도 일치하지 않습니다. 작업을 패스합니다.") return False except Exception as e: self.logger.error(f"이미지 사이즈 측정 중 오류발생: {e}", exc_info=True) 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 # 세로 중앙 계산 self.logger.debug(f"마우스 커서를 추가로 50px 내림") center_y = center_y + 50 pyautogui.moveTo(center_x, center_y) self.logger.debug(f"마우스 커서를 창 중앙으로 이동: ({center_x}, {center_y})") else: self.logger.error("웨일 창의 크기를 알 수 없습니다. 먼저 창을 찾으세요.") 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 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} confidence: {confidence}") return result else: # locateOnScreen 함수가 이미지와 충분히 유사하다고 판단하지 않은 경우 highest_confidence = pyscreeze._locateAll_opencv(image_path, region)[0][1] # 최고 유사도 값 가져오기 self.logger.debug(f"최고 유사도 값: {highest_confidence}") if highest_confidence >= confidence: self.logger.debug(f"유사한 이미지를 찾았습니다. 최고 유사도 값: {highest_confidence}") return True # 유사도는 충분하므로 True 반환 else: self.logger.error(f"{image_path} 이미지를 찾지 못했습니다. 유사도: {highest_confidence}") return None # 유사도가 충분하지 않으면 None 반환 except pyscreeze.ImageNotFoundException as e: self.logger.error(f"{image_path} 이미지를 찾지 못했습니다. 예외 발생: {e}") return None # def check_translation_by_color_change_ori(self, original_color): # start_time = time.time() # while time.time() - start_time < self.timeout: # current_color = self.get_mouse_position_color() # if self.colors['during'] is None: # 번역 중 첫 색상 기록 # self.colors['during'] = current_color # self.logger.debug(f"현재 색상: {current_color}") # if self.is_similar_color(current_color, original_color): # self.colors['after'] = current_color # self.logger.debug("번역 성공 감지 (원래 색상과 유사)") # return "success" # if not self.is_similar_color(current_color, original_color): # self.logger.debug("번역 중 상태 감지 (필터 색상)") # if self.is_translation_failed(): # self.colors['after'] = current_color # return "error" # time.sleep(self.pixel_check_interval) # # 타임아웃 발생 시, 번역 성공으로 간주 # self.colors['after'] = current_color # self.logger.debug("번역 성공으로 간주 (타임아웃)") # return "success" 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 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 check_translation_status(self, duration_ms = 5): """ 번역 상태를 주어진 밀리초 동안 0.1초 간격으로 확인하여 성공/실패 여부를 반환하는 메서드 Parameters: - duration_ms (int): 번역을 확인할 시간, 밀리초 단위 """ start_time = time.time() timeout = duration_ms / 1000.0 # 밀리초를 초로 변환 while time.time() - start_time < timeout: # duration_ms 초 동안 번역 상태 확인 if self.is_translation_failed(): # 번역 실패 여부 확인 self.logger.error("번역 실패가 감지되었습니다.") return False time.sleep(0.1) # 0.1초 대기 # 5초 동안 실패하지 않으면 번역 성공으로 간주 self.logger.debug("번역 성공으로 간주됩니다.") return True def is_translation_failed(self): """번역 실패 이미지 확인""" try: # if pyautogui.locateOnScreen(image_path, confidence=0.8): if self.find_image_with_confidence(self.translating_image_path, confidence=0.8): self.logger.error(f"번역 실패: '{os.path.basename(self.error_image_path2)}' 메시지가 감지되었습니다.") return True except pyautogui.ImageNotFoundException: self.logger.debug(f"번역 실패 이미지가 화면에 없습니다: {os.path.basename(self.error_image_path2)}") return False def right_click_and_send_key(self, key, max_retries=3): """ 마우스 오른쪽 클릭 후 지정된 키를 전송하는 메서드. 대화상자가 감지되면 닫고, 최대 재시도 횟수까지 반복 시도. Args: key (str): 전송할 키 ('r' 또는 'c'). max_retries (int): 최대 재시도 횟수. """ retry_count = 0 self.move_mouse_to_center() while retry_count < max_retries: self.logger.debug(f"마우스 오른쪽 클릭 시도 #{retry_count + 1}") pyautogui.rightClick() time.sleep(1) # 컨텍스트 창 대기 if self.detect_unexpected_dialog(): self.logger.debug("예상치 못한 대화상자를 닫고 다시 시도합니다.") retry_count += 1 else: self.logger.debug(f"대화상자가 감지되지 않았습니다. 키보드로 '{key}' 전송 준비.") break else: self.logger.error(f"대화상자가 계속 발생하여 '{key}' 작업을 중단합니다.") return False # 작업 실패 # 대화상자가 없으면 지정된 키를 전송 self.logger.debug(f"키보드로 '{key}' 전송") pyautogui.press(key) time.sleep(0.5) # 작업 대기 return True # 작업 성공