783 lines
35 KiB
Python
783 lines
35 KiB
Python
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')
|
||
|
||
error_image_filenames = ['fail_translated1.png', 'fail_translated2.png']
|
||
self.error_image_paths = [os.path.join(img_dir, filename) for filename in error_image_filenames]
|
||
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 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(url)
|
||
pyautogui.press('enter')
|
||
time.sleep(2) # 페이지 로딩 대기
|
||
|
||
# 현재 웨일 창의 해상도를 확인하여 기준 이하일 경우 패스
|
||
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(1) # 마우스 이동 후 대기
|
||
|
||
# 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']}")
|
||
|
||
time.sleep(5) # 번역 대기
|
||
|
||
|
||
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("클립보드에 이미지 데이터가 존재하는지 확인 중.....")
|
||
if self.is_image_in_clipboard(): # 클립보드에 이미지 데이터가 있으면 성공
|
||
self.translation_success_flag = True
|
||
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)
|
||
# self.logger.debug(f'번역 완료: {url}')
|
||
|
||
if self.vd_mode:
|
||
self.return_to_virtual_desktop_1()
|
||
|
||
# 경로를 인자로 받을경우 해당경로에 파일 저장
|
||
if path:
|
||
pass # 클립보드의 이미지를 path의 파일로 저장하고 저장경로를 리턴하는 메서드
|
||
# path에는 현재 폴더의 tmp_img폴더에 상품명-옵션명 형태로 제공됨
|
||
|
||
return True
|
||
|
||
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):
|
||
"""
|
||
현재 활성화된 웨일 창의 이름을 가져온 후
|
||
제목에서 이미지의 해상도를 확인한 후, 일정 크기 이하인 경우 작업을 패스하도록 처리합니다.
|
||
"""
|
||
window_title = self.get_whale_window_title()
|
||
|
||
# 해상도를 추출하기 위해 제목에서 괄호 안의 (숫자×숫자) 부분을 찾음
|
||
|
||
if not window_title == self.whale_window_name:
|
||
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 < min_width or height < min_height:
|
||
self.logger.debug(f"이미지 해상도가 기준이하 [{min_width} x {min_height}]입니다. 작업을 패스합니다.")
|
||
return False # 작업을 수행하지 않음
|
||
return True # 작업을 계속 진행
|
||
|
||
elif 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.debug(f"이미지 해상도: {width}×{height}")
|
||
|
||
# 해상도가 기준보다 작은 경우 패스
|
||
if width < min_width or height < min_height:
|
||
self.logger.debug(f"이미지 해상도가 기준이하 [{min_width} x {min_height}]입니다. 작업을 패스합니다.")
|
||
return False # 작업을 수행하지 않음
|
||
return True # 작업을 계속 진행
|
||
|
||
else:
|
||
return False
|
||
|
||
self.logger.error("웨일 창을 찾을 수 없습니다.")
|
||
return False
|
||
|
||
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 is_translation_failed(self):
|
||
"""번역 실패 이미지 확인"""
|
||
for image_path in self.error_image_paths:
|
||
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(image_path)}' 메시지가 감지되었습니다.")
|
||
return True
|
||
except pyautogui.ImageNotFoundException:
|
||
self.logger.debug(f"번역 실패 이미지가 화면에 없습니다: {os.path.basename(image_path)}")
|
||
return False
|
||
|
||
def right_click_and_send_key(self, key, max_retries=3):
|
||
"""
|
||
마우스 오른쪽 클릭 후 지정된 키를 전송하는 메서드.
|
||
대화상자가 감지되면 닫고, 최대 재시도 횟수까지 반복 시도.
|
||
|
||
Args:
|
||
key (str): 전송할 키 ('r' 또는 'c').
|
||
max_retries (int): 최대 재시도 횟수.
|
||
"""
|
||
retry_count = 0
|
||
|
||
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 # 작업 성공
|