AutoPercenty3/whale_translator.py

816 lines
39 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
import logging
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.log(f"Whale 브라우저 실행, PID: {self.whale_pid}", level=logging.DEBUG)
# 창을 찾기 전 대기 (최대 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.log(f"Whale 창을 찾았습니다: {self.whale_hwnd}", level=logging.DEBUG)
break
time.sleep(1)
wait_time += 1
self.logger.log(f"Whale 창을 찾지 못했습니다. 재시도 중... ({wait_time}초 경과)", level=logging.DEBUG)
# 창을 찾지 못한 경우 처리
if not self.whale_hwnd:
self.logger.log("Whale 창을 찾을 수 없습니다.", level=logging.DEBUG)
return
# 창 크기 조절 및 포커스 이동
# win32gui.ShowWindow(self.whale_hwnd, win32con.SW_NORMAL)
# win32gui.SetWindowPos(self.whale_hwnd, None, 0, 0, 1920, 1080, win32con.SWP_NOZORDER)
# self.logger.log("Whale 창 크기 조절 완료", level=logging.DEBUG)
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.log("URL 입력 완료", level=logging.DEBUG)
# 가상 데스크탑 복귀 처리
if self.vd_mode:
self.return_to_virtual_desktop_1() # 가상 데스크탑 1로 복귀
def reset_failures(self):
"""실패 횟수를 초기화"""
self.failure_count = 0
self.logger.log("실패 횟수가 초기화되었습니다.", level=logging.DEBUG)
# def handle_translation_failure(self):
# """번역 실패 시 처리"""
# self.failure_count += 1
# self.logger.log(f"번역 실패! 실패 횟수: {self.failure_count}/{self.max_failures}", level=logging.ERROR)
# if self.failure_count >= self.max_failures:
# self.logger.log("최대 실패 횟수에 도달했습니다. 웨일 브라우저를 재시작합니다.", level=logging.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.log("클립보드에 이미지 데이터가 확인되었습니다.", level=logging.DEBUG)
# return True
# else:
# self.logger.log("클립보드에 이미지 데이터가 없습니다.", level=logging.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.log("클립보드에 이미지 데이터가 확인되었습니다.", level=logging.DEBUG)
return True
else:
self.logger.log("클립보드에 이미지 데이터가 없습니다.", level=logging.DEBUG)
return False
except Exception as e:
self.logger.log(f"클립보드에서 이미지 확인 중 오류 발생: {e}", level=logging.ERROR, 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.log(f"웨일 창을 찾았습니다: {self.whale_hwnd}", level=logging.DEBUG)
self.update_whale_rect()
return self.whale_hwnd
else:
self.logger.log("웨일 창을 찾지 못했습니다.", level=logging.DEBUG)
return None
def update_whale_rect(self):
"""웨일 창의 위치 및 크기 rect를 업데이트"""
if self.whale_hwnd:
self.whale_rect = win32gui.GetWindowRect(self.whale_hwnd)
self.logger.log(f"웨일 창 크기 및 위치 저장: {self.whale_rect}", level=logging.DEBUG)
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.log(f"웨일 창을 찾았습니다: {hwnd_list[0]}", level=logging.DEBUG)
return hwnd_list[0]
else:
self.logger.log(f"PID {pid}에 해당하는 웨일 창을 찾지 못했습니다.", level=logging.DEBUG)
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.log("가상 데스크톱 2가 생성되었습니다.", level=logging.DEBUG)
time.sleep(1)
else:
self.switch_to_virtual_desktop_2()
self.close_whale_window_if_exists()
self.logger.log("가상 데스크톱 2가 이미 존재합니다.", level=logging.DEBUG)
except Exception as e:
self.logger.log(f"가상 데스크톱 확인/생성 중 오류 발생: {e}", level=logging.DEBUG, 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.log('크롬 창으로 포커스 이동.', level=logging.DEBUG)
else:
self.logger.log('크롬 창을 찾을 수 없습니다.', level=logging.DEBUG)
def switch_to_virtual_desktop_2(self):
"""가상 데스크톱 2로 전환"""
try:
VirtualDesktop(2).go()
self.logger.log("가상 데스크톱 2로 전환되었습니다.", level=logging.DEBUG)
time.sleep(0.3)
except Exception as e:
self.logger.log(f"가상 데스크톱 전환 중 오류 발생: {e}", level=logging.DEBUG, exc_info=True)
def return_to_virtual_desktop_1(self):
"""가상 데스크톱 1로 복귀"""
try:
VirtualDesktop(1).go()
self.logger.log("가상 데스크톱 1로 전환되었습니다.", level=logging.DEBUG)
time.sleep(0.3)
except Exception as e:
self.logger.log(f"가상 데스크톱 전환 중 오류 발생: {e}", level=logging.DEBUG, 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.log("웨일 창을 찾지 못했습니다. 새로운 웨일창 호출.", level=logging.DEBUG)
self.create_and_update_whale_window()
if url.startswith("https://assets.alicdn.com"):
self.logger.log("assets.alicdn.com 확인 : 번역작업을 패스합니다.", level=logging.DEBUG)
return False
elif self.whale_hwnd:
try:
self.logger.log(f"웨일 창을 찾았습니다.{self.whale_hwnd}", level=logging.DEBUG)
win32gui.ShowWindow(self.whale_hwnd, win32con.SW_RESTORE) # 웨일 창 활성화
win32gui.SetForegroundWindow(self.whale_hwnd)
# pyautogui.hotkey('ctrl', 'l') # 웨일 브라우저의 주소창으로 이동
self.logger.log(f"이미지 URL 주소 {url} 입력", level=logging.DEBUG)
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.log("해상도가 기준보다 낮아 작업을 패스합니다.", level=logging.INFO)
return False # 해상도 기준 미달로 작업 종료
# 페이지 로딩 완료 대기
# self.logger.log(f"페이지 로딩완료 대기", level=logging.DEBUG)
# self.wait_for_loading_icon_to_disappear()
self.logger.log(f"페이지 로딩 완료 후 웨일 창의 가운데로 마우스 커서 이동", level=logging.DEBUG)
# 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.log(f"번역 전 색상: {original_color}", level=logging.DEBUG)
self.logger.log("번역 작업을 위한 마우스 오른쪽 클릭 및 R 전송", level=logging.DEBUG)
if not self.right_click_and_send_key('r'):
self.logger.log("번역 작업이 대화상자 문제로 중단되었습니다.", level=logging.ERROR)
return False
# # 번역 성공 여부 확인
# result = self.check_translation_by_color_change()
# if result == "success":
# self.logger.log("번역이 성공적으로 완료되었습니다!", level=logging.DEBUG)
# elif result == "error":
# self.logger.log("번역에 실패했습니다.", level=logging.DEBUG)
# else:
# self.logger.log("번역 상태를 확인하지 못했습니다.", level=logging.DEBUG)
# # 최종 색상 출력
# self.logger.log(f"번역 전 색상: {self.colors['before']}", level=logging.DEBUG)
# self.logger.log(f"번역 중 색상: {self.colors['during']}", level=logging.DEBUG)
# self.logger.log(f"번역 후 색상: {self.colors['after']}", level=logging.DEBUG)
# self.check_translation_status(duration_ms=5000) # 5초 동안 번역실패 감지
time.sleep(6)
self.logger.log("이미지 복사를 위한 마우스 오른쪽 클릭 및 C 전송", level=logging.DEBUG)
if not self.right_click_and_send_key('c'):
self.logger.log("복사 작업이 대화상자 문제로 중단되었습니다.", level=logging.ERROR)
return False
self.logger.log(f"클립보드에 번역된이미지 복사 대기 1s", level=logging.DEBUG)
time.sleep(1) # 클립보드 업데이트 대기
self.logger.log("클립보드에 이미지 데이터가 존재하는지 확인 중.....", level=logging.DEBUG)
self.translation_success_flag = self.is_image_in_clipboard()
if self.translation_success_flag: # 클립보드에 이미지 데이터가 있으면 성공
self.logger.log(f'번역 성공: {url}', level=logging.INFO)
# self.reset_failures() # 번역 성공 시 연속 실패 횟수 초기화
else:
self.logger.log(f'번역 실패: 클립보드에 이미지 데이터가 없음', level=logging.INFO)
# self.handle_translation_failure()
self.logger.log(f'번역 프로세스 완료. 웨일 기본페이지로 돌아감', level=logging.INFO)
# self.enter_url(self.newtab)
pyautogui.hotkey('alt', 'left') # 클립보드 이미지 붙여넣기
# self.logger.log(f'번역 완료: {url}', level=logging.DEBUG)
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.log(f"번역된 이미지가 {path}에 저장되었습니다.", level=logging.INFO)
else:
self.logger.log("클립보드에 이미지가 존재하지 않아 파일로 저장할 수 없습니다.", level=logging.ERROR)
return False
except Exception as e:
self.logger.log(f"이미지 저장 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
return False
return True if self.translation_success_flag else False
except Exception as e:
self.logger.log(f"번역 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
return False
# self.handle_translation_failure()
else:
self.logger.log('웨일 창을 찾을 수 없습니다.', level=logging.DEBUG)
return False
# self.handle_translation_failure()
def detect_unexpected_dialog(self):
"""예상치 못한 대화상자 감지"""
try:
dialog_hwnd = win32gui.FindWindow(None, "다른 이름으로 저장") # 저장 대화상자의 이름이 실제와 다를 수 있습니다.
if dialog_hwnd:
self.logger.log("예상치 못한 '다른 이름으로 저장' 대화상자 발견!", level=logging.DEBUG)
win32gui.PostMessage(dialog_hwnd, win32con.WM_CLOSE, 0, 0) # 대화상자 닫기
time.sleep(1)
return True
except Exception as e:
self.logger.log(f"대화상자 감지 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
return False
def create_and_update_whale_window(self):
"""
웨일 창을 찾지 못했을 경우 새 웨일 창을 생성하고 핸들을 업데이트하는 메서드.
"""
self.logger.log("웨일 창을 찾지 못해 새 웨일 창을 생성합니다.", level=logging.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.log(f"새 웨일 브라우저 실행, PID: {self.whale_pid}", level=logging.DEBUG)
# await asyncio.sleep(2) # 브라우저가 실행될 때까지 대기
time.sleep(2)
# 창 핸들 업데이트
self.whale_hwnd = self.find_whale_window()
if self.whale_hwnd:
self.logger.log(f"새로 생성된 웨일 창 핸들: {self.whale_hwnd}", level=logging.DEBUG)
win32gui.ShowWindow(self.whale_hwnd, win32con.SW_NORMAL)
win32gui.SetWindowPos(self.whale_hwnd, None, 0, 0, 1920, 1080, win32con.SWP_NOZORDER)
else:
self.logger.log("새로 생성된 웨일 창을 찾지 못했습니다.", level=logging.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.log("영어로 전환하는데 실패했습니다.", level=logging.DEBUG)
return
else:
self.logger.log("전환 성공", level=logging.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.log("웨일 프로세스 ID가 설정되지 않았습니다.", level=logging.DEBUG)
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.log(f"웨일 창을 찾았습니다. 핸들: {hwnd}. 종료 중...", level=logging.DEBUG)
win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0) # 창을 종료하는 메시지 전송
time.sleep(1)
self.logger.log(f"웨일 창이 성공적으로 종료되었습니다.", level=logging.DEBUG)
else:
self.logger.log("웨일 창을 찾을 수 없습니다.", level=logging.DEBUG)
except Exception as e:
self.logger.log(f"웨일 창을 종료하는 중 오류 발생: {e}", level=logging.ERROR, 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.log(f"남은 가상 데스크톱 수: {number_of_desktops}", level=logging.DEBUG)
self.logger.log("모든 가상 데스크톱이 종료되었습니다.", level=logging.DEBUG)
except Exception as e:
self.logger.log(f"가상 데스크톱 종료 중 오류 발생: {e}", level=logging.DEBUG, 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.log("페이지 로딩 중...", level=logging.DEBUG)
# time.sleep(0.5) # 간격을 두고 다시 확인
# except pyautogui.ImageNotFoundException:
# # 아이콘이 화면에 없으면 로딩 완료로 간주
# self.logger.log("페이지 로딩이 완료되었습니다.", level=logging.DEBUG)
# return True # 로딩 완료
# self.logger.log("로딩 완료 대기 시간이 초과되었습니다.", level=logging.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.log(f"창 위치 및 크기 설정: 위치({x}, {y}), 크기({width}x{height})", level=logging.DEBUG)
def get_whale_window_title(self):
"""
현재 활성화된 웨일 창의 이름을 가져옵니다.
"""
try:
if self.whale_hwnd:
window_title = win32gui.GetWindowText(self.whale_hwnd)
self.logger.log(f"현재 웨일 창의 제목: {window_title}", level=logging.DEBUG)
else:
self.logger.log(f"웨일 창 핸들이 없습니다.", level=logging.WARNING)
except Exception as e:
window_title = None
self.logger.log(f"현재 웨일 창의 제목을 가져올 수 없습니다. : {e}", level=logging.ERROR, 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.log("이미지 주소로 이동하지 못하여 이미지 해상도를 가져오지 못했습니다.", level=logging.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.log(f"이미지 해상도: {width}×{height}", level=logging.INFO)
if width < min_width or height < min_height:
self.logger.log(f"이미지 해상도가 기준 이하 [{min_width} x {min_height}]입니다. 작업을 패스합니다.", level=logging.INFO)
return False # 작업을 수행하지 않음
return True # 해상도 조건을 만족하면 작업을 진행
# 해상도가 없다면, 파일 확장자를 확인하여 번역 여부 결정
elif window_title.endswith(".jpg") or window_title.endswith(".png"):
self.logger.log("이미지 해상도가 없지만, 파일 확장자가 .jpg 또는 .png입니다. 번역 작업을 진행합니다.", level=logging.INFO)
return True # 해상도가 없어도 번역 작업을 수행
# 해상도도 없고, 파일 확장자도 매칭되지 않는 경우
self.logger.log("이미지 해상도가 존재하지 않고 파일 확장자도 일치하지 않습니다. 작업을 패스합니다.", level=logging.WARNING)
return False
except Exception as e:
self.logger.log(f"이미지 사이즈 측정 중 오류발생: {e}", level=logging.ERROR, 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.log(f"마우스 커서를 추가로 50px 내림", level=logging.DEBUG)
center_y = center_y + 50
pyautogui.moveTo(center_x, center_y)
self.logger.log(f"마우스 커서를 창 중앙으로 이동: ({center_x}, {center_y})", level=logging.DEBUG)
else:
self.logger.log("웨일 창의 크기를 알 수 없습니다. 먼저 창을 찾으세요.", level=logging.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.log(f"현재 색상: {current_color}", level=logging.DEBUG)
# 필터가 사라져서 색상이 변했는지 확인
if self.is_color_changed(current_color):
self.colors['after'] = current_color
self.logger.log("색상 변화 감지 (필터 제거됨)", level=logging.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.log("번역 성공으로 간주 (타임아웃 후 translating.png 존재)", level=logging.DEBUG)
return "success"
else:
self.logger.log("번역 실패로 간주 (타임아웃 후 translating.png 없음)", level=logging.DEBUG)
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.log(f"이미지를 찾을 영역: {region}", level=logging.DEBUG)
try:
# locateOnScreen 시도
result = pyscreeze.locateOnScreen(image_path, confidence=confidence, region=region)
if result:
self.logger.log(f"이미지를 찾았습니다: {image_path} confidence: {confidence}", level=logging.DEBUG)
return result
else:
# locateOnScreen 함수가 이미지와 충분히 유사하다고 판단하지 않은 경우
highest_confidence = pyscreeze._locateAll_opencv(image_path, region)[0][1] # 최고 유사도 값 가져오기
self.logger.log(f"최고 유사도 값: {highest_confidence}", level=logging.DEBUG)
if highest_confidence >= confidence:
self.logger.log(f"유사한 이미지를 찾았습니다. 최고 유사도 값: {highest_confidence}", level=logging.DEBUG)
return True # 유사도는 충분하므로 True 반환
else:
self.logger.log(f"{image_path} 이미지를 찾지 못했습니다. 유사도: {highest_confidence}", level=logging.ERROR)
return None # 유사도가 충분하지 않으면 None 반환
except pyscreeze.ImageNotFoundException as e:
self.logger.log(f"{image_path} 이미지를 찾지 못했습니다. 예외 발생: {e}", level=logging.ERROR)
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.log(f"현재 색상: {current_color}", level=logging.DEBUG)
# if self.is_similar_color(current_color, original_color):
# self.colors['after'] = current_color
# self.logger.log("번역 성공 감지 (원래 색상과 유사)", level=logging.DEBUG)
# return "success"
# if not self.is_similar_color(current_color, original_color):
# self.logger.log("번역 중 상태 감지 (필터 색상)", level=logging.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.log("번역 성공으로 간주 (타임아웃)", level=logging.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.log("번역 실패가 감지되었습니다.", level=logging.ERROR)
return False
time.sleep(0.1) # 0.1초 대기
# 5초 동안 실패하지 않으면 번역 성공으로 간주
self.logger.log("번역 성공으로 간주됩니다.", level=logging.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.log(f"번역 실패: '{os.path.basename(self.error_image_path2)}' 메시지가 감지되었습니다.", level=logging.ERROR)
return True
except pyautogui.ImageNotFoundException:
self.logger.log(f"번역 실패 이미지가 화면에 없습니다: {os.path.basename(self.error_image_path2)}", level=logging.DEBUG)
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.log(f"마우스 오른쪽 클릭 시도 #{retry_count + 1}", level=logging.DEBUG)
pyautogui.rightClick()
time.sleep(1) # 컨텍스트 창 대기
if self.detect_unexpected_dialog():
self.logger.log("예상치 못한 대화상자를 닫고 다시 시도합니다.", level=logging.DEBUG)
retry_count += 1
else:
self.logger.log(f"대화상자가 감지되지 않았습니다. 키보드로 '{key}' 전송 준비.", level=logging.DEBUG)
break
else:
self.logger.log(f"대화상자가 계속 발생하여 '{key}' 작업을 중단합니다.", level=logging.ERROR)
return False # 작업 실패
# 대화상자가 없으면 지정된 키를 전송
self.logger.log(f"키보드로 '{key}' 전송", level=logging.DEBUG)
pyautogui.press(key)
time.sleep(0.5) # 작업 대기
return True # 작업 성공