298 lines
14 KiB
Python
298 lines
14 KiB
Python
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')
|
||
|