AutoPercenty3/whale_new.py

348 lines
18 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 time
import os, sys
import re
import logging
import pyperclip
from pywinauto import Application, findwindows, clipboard, timings
from pywinauto.controls.hwndwrapper import HwndWrapper
from pywinauto.findwindows import ElementNotFoundError
from pywinauto.timings import wait_until
from PIL import ImageGrab
class WhaleTranslator:
def __init__(self, logger):
self.logger = logger
self.whale_app = None
self.whale_window = None
self.translation_success_flag = False # 번역 성공 플래그
self.failure_count = 0 # 실패 횟수
self.min_image_width = 200
self.min_image_height = 150
def get_base_dir(self):
"""
실행 환경에 따라 base_dir을 설정하는 메서드.
cx_Freeze로 패키징된 경우 실행 파일의 경로, 일반 Python 환경일 경우 __file__을 기준으로 설정.
"""
if getattr(sys, 'frozen', False): # 패키징된 경우
base_dir = os.path.dirname(sys.executable)
internal_dir = os.path.join(base_dir, '_internal') # _internal 디렉토리 포함
if os.path.exists(internal_dir): # _internal 디렉토리가 존재하면 base_dir로 설정
return internal_dir
else: # 일반 Python 실행 환경
base_dir = os.path.dirname(os.path.abspath(__file__))
return base_dir
def safe_click(self, element, right=False):
"""
click_input() 호출 시 활성 데스크탑 오류가 발생하면 click() 또는 right_click()을 사용하여 클릭합니다.
"""
try:
if right:
element.right_click_input()
else:
element.click_input()
except RuntimeError as e:
if "no active desktop" in str(e):
self.logger.log("Active desktop 오류 발생 - 메시지 기반 클릭으로 대체합니다.", level=logging.WARNING)
if right:
element.right_click()
else:
element.click()
else:
raise
def start_whale_browser(self):
base_path = self.get_base_dir()
whale_exe_path = os.path.join(base_path, "browsers", "whale", "whale.exe")
user_data_dir = os.path.join(base_path, "browsers", "whale", "user_data")
cache_dir = os.path.join(base_path, "browsers", "whale", "cache")
self.whale_app = Application(backend="uia").start(
f'"{whale_exe_path}" --incognito --user-data-dir="{user_data_dir}" --disk-cache-dir="{cache_dir}"'
)
# 창이 완전히 생성될 때까지 대기
self.whale_window = self.find_whale_window()
if self.whale_window:
self.logger.log("웨일 시크릿 모드로 시작 완료.", level=logging.INFO)
else:
self.logger.log("웨일 창을 찾을 수 없습니다.", level=logging.WARNING)
def find_whale_window(self):
try:
# 최대 10초 동안 '새 시크릿 탭 - Whale' 창이 나타나기를 기다림
timings.wait_until(10, 0.5, lambda: any(window.name == '새 시크릿 탭 - Whale' for window in findwindows.find_elements()))
windows = findwindows.find_elements()
for window in windows:
if window.name == '새 시크릿 탭 - Whale':
whale_pid = window.process_id
self.whale_app = Application(backend="uia").connect(process=whale_pid)
self.whale_window = self.whale_app.top_window()
# 위치 및 크기 조절
self.hwnd_wrapper = HwndWrapper(self.whale_window.handle)
self.hwnd_wrapper.move_window(x=1, y=1, width=1280, height=720)
self.whale_window.set_focus()
self.logger.log("웨일 창을 성공적으로 찾았습니다.", level=logging.INFO)
return self.whale_window
self.logger.log("'새 시크릿 탭 - Whale' 창을 찾을 수 없습니다.", level=logging.ERROR)
except Exception as e:
self.logger.log(f"웨일 창 탐색 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
return None
def translate_image(self, url, path=None):
if url.startswith("https://assets.alicdn.com"):
self.logger.log("assets.alicdn.com 확인 : 번역작업을 패스합니다.", level=logging.INFO)
return False
if self.whale_app:
try:
self.navigate_to_url(url)
if not self.check_image_size():
self.logger.log("해상도가 기준보다 낮아 작업을 패스합니다.", level=logging.INFO)
self.click_back_button()
return False # 해상도 기준 미달로 작업 종료
self.translation_success_flag = self.right_click_on_image_and_inspect()
self.click_back_button()
# 경로를 인자로 받을 경우 해당 경로에 파일 저장
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
else:
self.logger.log('웨일 창을 찾을 수 없습니다.', level=logging.ERROR)
return False
def navigate_to_url(self, url):
"""주소창에 URL을 입력하고 페이지 로딩을 대기"""
retry_count = 3 # 주소창 찾기 재시도 횟수
for attempt in range(retry_count):
try:
# URL을 클립보드에 복사
pyperclip.copy(url)
# 주소창을 클릭하여 URL 붙여넣기
address_bar = self.whale_window.child_window(title="주소창 및 검색창", control_type="Edit")
self.safe_click(address_bar)
# Ctrl + V로 URL 붙여넣기 후 Enter 키 입력
address_bar.type_keys("^v{ENTER}", with_spaces=True)
self.logger.log(f"{url}로 이동 중...", level=logging.DEBUG)
# 5초 동안 0.5초 간격으로 이미지 요소가 나타나는지 검사
start_time = time.time()
while time.time() - start_time < 5:
try:
# 이미지 요소를 찾으면 즉시 반환
image_element = self.whale_window.child_window(
title="누락된 이미지 설명을 확인하려면 컨텍스트 메뉴를 여세요.",
control_type="Image"
)
if image_element.exists(timeout=1):
self.logger.log("페이지 로딩 완료: 이미지 요소가 나타났습니다.", level=logging.DEBUG)
return
except Exception as e:
self.logger.log(f"이미지 요소 확인 중 오류 발생: {e}", level=logging.DEBUG)
# 안전장치 1: 현재 창 제목 확인
window_title = self.whale_window.window_text()
if re.match(r".+\.(jpg|png|jpeg) \(\d+x\d+\)", window_title, re.IGNORECASE):
self.logger.log(f"창 제목에 이미지 파일과 해상도가 감지됨: {window_title}", level=logging.INFO)
return
# 안전장치 2: 현재 창의 컨트롤 핸들 요소 출력
self.logger.log("지정된 시간 내에 이미지 요소를 찾지 못했습니다. 현재 창의 컨트롤 요소:", level=logging.ERROR)
with open("debug_controls.txt", "w", encoding="utf-8") as f:
original_stdout = os.sys.stdout
os.sys.stdout = f
self.whale_window.print_control_identifiers()
os.sys.stdout = original_stdout
self.logger.log("컨트롤 식별자가 debug_controls.txt에 저장되었습니다.", level=logging.INFO)
except ElementNotFoundError as e:
self.logger.log("주소창 요소를 찾을 수 없습니다. 창을 다시 검색합니다.", level=logging.ERROR, exc_info=True)
self.whale_window = self.find_whale_window() # 창을 다시 찾음
if not self.whale_window:
self.logger.log("웨일 창을 다시 찾을 수 없습니다. 작업 중단.", level=logging.ERROR)
return
except Exception as e:
self.logger.log(f"주소창 접근 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
self.logger.log("주소창 찾기 및 URL 이동 시도 횟수를 초과했습니다.", level=logging.ERROR)
def navigate_to_url_for_typing(self, url):
"""주소창에 URL을 입력하고 페이지 로딩을 대기"""
try:
address_bar = self.whale_window.child_window(title="주소창 및 검색창", control_type="Edit")
self.safe_click(address_bar)
address_bar.type_keys(f"{url}{{ENTER}}", with_spaces=True)
self.logger.log(f"{url}로 이동 중...", level=logging.DEBUG)
# 5초 동안 0.1초 간격으로 이미지 요소가 나타나는지 검사
start_time = time.time()
while time.time() - start_time < 5:
try:
image_element = self.whale_window.child_window(
title="누락된 이미지 설명을 확인하려면 컨텍스트 메뉴를 여세요.",
control_type="Image"
)
if image_element.exists(timeout=0.5):
self.logger.log("페이지 로딩 완료: 이미지 요소가 나타났습니다.", level=logging.DEBUG)
return
except Exception:
pass
self.logger.log("지정된 시간 내에 이미지 요소를 찾지 못했습니다.", level=logging.ERROR)
except Exception as e:
self.logger.log(f"주소창에 접근할 수 없습니다: {e}", level=logging.ERROR, exc_info=True)
def check_translation_status(self, max_wait_time=10, check_interval=0.5):
start_time = time.time()
while time.time() - start_time < max_wait_time:
try:
fail_indicator = self.whale_window.child_window(title="번역할 영역을 선택하세요.", control_type="Text")
if fail_indicator.exists():
self.logger.log("번역할 문구가 없는 이미지 입니다.", level=logging.INFO)
return "fail"
image_element = self.whale_window.child_window(title="누락된 이미지 설명을 확인하려면 컨텍스트 메뉴를 여세요.", control_type="Image")
if image_element.exists():
self.safe_click(image_element, right=True)
success_indicator = self.whale_window.child_window(title="이미지 복사(C)", control_type="MenuItem")
if success_indicator.wait('visible', timeout=5):
self.safe_click(success_indicator)
time.sleep(0.5)
formats = clipboard.GetClipboardFormats()
# self.logger.debug(f"클립보드에 있는 형식 목록: {formats}")
for format_id in formats:
format_name = clipboard.GetFormatName(format_id)
# self.logger.debug(f"형식 ID {format_id}: {format_name}")
if format_name in ("CF_BITMAP", "CF_DIB"):
image_data = clipboard.GetData(format_id=format_id)
if isinstance(image_data, bytes):
self.logger.log("번역 성공: 이미지 데이터 복사 완료.", level=logging.INFO)
return "success"
self.logger.log("번역이 아직 완료되지 않았습니다. 다시 시도 중...", level=logging.INFO)
except Exception as e:
self.logger.log("클립보드 접근 중 오류 발생", level=logging.ERROR, exc_info=True)
return "error"
time.sleep(check_interval)
self.logger.log("번역 확인 시간 초과.", level=logging.WARNING)
return "timeout"
def right_click_on_image_and_inspect(self):
try:
image = self.whale_window.child_window(title="누락된 이미지 설명을 확인하려면 컨텍스트 메뉴를 여세요.", control_type="Image")
if image.exists():
self.safe_click(image, right=True)
self.logger.log("이미지 요소에서 우클릭을 수행했습니다.", level=logging.DEBUG)
else:
self.logger.log("이미지 요소를 찾을 수 없습니다.", level=logging.ERROR)
return False
translate_menu_item = self.whale_window.child_window(title="이미지 번역 (R)", control_type="MenuItem")
self.safe_click(translate_menu_item)
self.logger.log("이미지 번역 명령이 실행되었습니다.", level=logging.DEBUG)
time.sleep(0.5)
status = self.check_translation_status()
if status == "success":
self.logger.log("번역이 성공적으로 완료되었습니다.", level=logging.INFO)
return True
elif status == "fail":
self.logger.log("번역에 실패했습니다.", level=logging.WARNING)
return False
else:
self.logger.log("번역 상태를 확인할 수 없습니다.", level=logging.WARNING)
return False
except Exception as e:
self.logger.log(f"이미지 요소에서 우클릭 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
return False
def check_image_size(self):
"""창 제목에서 이미지의 너비와 높이를 추출하여 반환합니다."""
try:
window_title = self.whale_window.window_text()
self.logger.log(f"Window Title: {window_title}", level=logging.DEBUG)
# 창 제목에서 해상도 추출 시도
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 < self.min_image_width or height < self.min_image_height:
self.logger.log(f"이미지 해상도가 기준 이하 [{self.min_image_width} x {self.min_image_height}]입니다. 작업을 패스합니다.", level=logging.INFO)
return False
return True
elif ".jpg" in window_title or ".png" in window_title or ".jpeg" in window_title:
self.logger.log("이미지 해상도가 없지만, 파일 확장자가 .jpg 또는 .jpeg 또는 .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)
return False
def click_back_button(self):
"""'뒤로' 버튼을 클릭합니다."""
try:
back_button = self.whale_window.child_window(title="뒤로", control_type="Button")
if back_button.exists():
self.safe_click(back_button)
self.logger.log("'뒤로' 버튼을 클릭했습니다.", level=logging.DEBUG)
else:
self.logger.log("'뒤로' 버튼을 찾을 수 없습니다.", level=logging.WARNING)
except Exception as e:
self.logger.log(f"'뒤로' 버튼 클릭 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
def close_whale_window(self):
"""웨일 창을 종료하는 메서드."""
try:
if self.whale_app:
self.whale_app.kill()
self.logger.log("웨일 창을 성공적으로 종료했습니다.", level=logging.INFO)
else:
self.logger.log("웨일 애플리케이션이 시작되지 않았습니다.", level=logging.WARNING)
except Exception as e:
self.logger.log("웨일 창을 종료하는 중 오류 발생", level=logging.ERROR, exc_info=True)