This commit is contained in:
R5600U_PC 2024-10-17 22:36:32 +09:00
parent 67bc926ab7
commit 5bf721106d
24 changed files with 233994 additions and 87079 deletions

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

81074
appTranslator.log.1 Normal file

File diff suppressed because it is too large Load Diff

94795
appTranslator.log.2 Normal file

File diff suppressed because one or more lines are too long

View File

@ -502,17 +502,30 @@ class BrowserController:
await input_field.press('Enter')
# # 첫 번째 옵션에만 - 기호를 붙여 목록 시작
# await input_field.type(f"- A. {option_data[0]}")
# await input_field.press('Enter') # 첫 번째 옵션 이후 엔터로 줄바꿈
# # 나머지 옵션들은 - 없이 입력하여 마크다운 목록으로 표시
# for i, option in enumerate(option_data[1:], start=2):
# option_text = option[0] if isinstance(option, tuple) else option
# option_prefix = f"{chr(64 + i)}. "
# await input_field.type(option_prefix + option_text)
# await input_field.press('Enter') # 엔터 키를 입력하여 줄바꿈
# 첫 번째 옵션에만 - 기호를 붙여 목록 시작
await input_field.type(f"- A. {option_data[0]}")
await input_field.type(f"- 1. {option_data[0]}")
await input_field.press('Enter') # 첫 번째 옵션 이후 엔터로 줄바꿈
# 나머지 옵션들은 - 없이 입력하여 마크다운 목록으로 표시
# 나머지 옵션들은 - 없이 숫자 접두사로 표시
for i, option in enumerate(option_data[1:], start=2):
option_text = option[0] if isinstance(option, tuple) else option
option_prefix = f"{chr(64 + i)}. "
option_prefix = f"{i}. "
await input_field.type(option_prefix + option_text)
await input_field.press('Enter') # 엔터 키를 입력하여 줄바꿈
# 목록 끝을 알리기 위해 엔터 두 번 입력
await input_field.press('Enter')
await input_field.press('Enter')
@ -541,10 +554,10 @@ class BrowserController:
# clipboard_content = pyperclip.paste()
if clipboardImageManager.is_clipboard_image():
pyautogui.hotkey('ctrl', 'v') # 클립보드 이미지 붙여넣기
pyautogui.press('right') # 오른쪽 입력
self.logger.debug("이미지 붙여넣기 완료.")
pyautogui.press('right') # 오른쪽 입력
self.logger.debug("이미지 붙여넣기 완료로 클립보드 비우기.")
await clipboardImageManager.clear_clipboard()
clipboardImageManager.clear_clipboard()
else:
self.logger.debug("클립보드가 비어있습니다.")
except Exception as e:

View File

@ -20,7 +20,7 @@ class ClipboardImageManager:
self.browser_controller = browser_controller # BrowserController 인스턴스를 전달받음
self.debug = debug # 디버그 플래그를 클래스 변수로 사용
self.debug = True
self.debug = False
def get_clipboard_data(self):
"""클립보드의 텍스트 또는 이미지 데이터를 가져옵니다."""
@ -266,7 +266,13 @@ class ClipboardImageManager:
def is_clipboard_image(self):
"""클립보드에 이미지가 있는지 확인하는 함수"""
return win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_DIB)
is_clipboard_image_flag = win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_DIB)
if is_clipboard_image_flag:
self.logger.debug("클립보드에 이미지가 존재합니다.")
else:
self.logger.debug("클립보드에 이미지가 없습니다.")
return is_clipboard_image_flag
def get_image_from_clipboard(self):
"""클립보드에서 이미지를 가져오는 함수"""

View File

@ -18,7 +18,8 @@ option_product_locator = '//div[@id="productMainContentContainerId"]//label[cont
total_options_selector = '#productMainContentContainerId label.ant-checkbox-wrapper'
; is_all_option_checked_selector = '#productMainContentContainerId .ant-checkbox-indeterminate'
is_all_option_checked_selector = '//*[@id="productMainContentContainerId"]/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[4]/div[2]/div[1]/label/span[1]/input'
ai_option_btn_selector = 'div#productMainContentContainerId div:nth-child(2) > div > div > div.ant-row.ant-row-middle.css-1li46mu > div:nth-child(4) > button[type=\"button\"]'
; ai_option_btn_selector = 'div#productMainContentContainerId div:nth-child(2) > div > div > div.ant-row.ant-row-middle.css-1li46mu > div:nth-child(4) > button[type=\"button\"]'
ai_option_btn_selector = 'button:has-text("AI 옵션명 다듬기")'
original_name_selector_template = 'div#productMainContentContainerId li:nth-child({index}) > div > div:nth-child(1) > div > div:nth-child(3) > div:nth-child(3) > span'
edit_field_selector_template = 'div#productMainContentContainerId li:nth-child({index}) > div > div:nth-child(1) > div > div:nth-child(3) > div:nth-child(2) > div:nth-child(1) > span > input'
checkbox_selector_template = '#productMainContentContainerId li:nth-child({index}) input[type="checkbox"]'
@ -30,6 +31,7 @@ add_button_selector_template = '#productMainContentContainerId > div.sc-TOgAA.fZ
file_input_locator = 'input[type="file"]'
low_order_button_locator = 'button:has-text("가격 낮은 순")'
AtoZ_button_locator = 'button:has-text("A-Z")'
one_to_nine_button_locator = 'button:has-text("1-99")'
[DetailLocators]
product_detail_input_locator = '//*[@id='detailMainContainerId']/div/div/div[{i}]/textarea'

8
gui.py
View File

@ -715,7 +715,7 @@ class TranslationApp(QWidget):
self.logger.debug(f'현재 페이지: {page_number}')
if not page_number == 1:
self.browser_controller.scroll_page_to_top()
await self.browser_controller.scroll_page_to_top()
self.logger.debug(f'1페이지가 아니므로 동적로딩을 위해 휠 스크롤 업')
# 4. 현재 페이지의 모든 "세부사항 수정 및 업로드" 버튼 찾기
@ -842,10 +842,12 @@ class TranslationApp(QWidget):
self.logger.debug('번역 작업이 중단되었습니다.')
break
self.logger.debug(f"이미지 번역 프로세스")
self.logger.debug(f"웨일 브라우저를 활용한 이미지 번역 프로세스")
self.whale_translator.translate_image(url)
self.logger.debug(f"이미지 붙여넣기")
self.logger.debug(f"paste_image_in_chrome - 이미지 붙여넣기")
await self.browser_controller.paste_image_in_chrome(self.clipboardImageManager, url)
self.logger.debug(f"Progress Update")
self.update_detail_progress(i,total_images)

View File

@ -83,6 +83,7 @@ class LocatorManager:
'file_input_locator': self.config.get('OptionLocators', 'file_input_locator').strip("'"),
'low_order_button_locator': self.config.get('OptionLocators', 'low_order_button_locator').strip("'"),
'AtoZ_button_locator': self.config.get('OptionLocators', 'AtoZ_button_locator').strip("'"),
'one_to_nine_button_locator': self.config.get('OptionLocators', 'one_to_nine_button_locator').strip("'"),
}
# TitleLocators 섹션

View File

@ -34,6 +34,7 @@ class OptionHandler:
self.file_input_locator = self.locator_manager.get_locator('OptionLocators', 'file_input_locator')
self.low_order_button_locator = self.locator_manager.get_locator('OptionLocators', 'low_order_button_locator')
self.AtoZ_button_locator = self.locator_manager.get_locator('OptionLocators', 'AtoZ_button_locator')
self.one_to_nine_button_locator = self.locator_manager.get_locator('OptionLocators', 'one_to_nine_button_locator')
def update_page(self, page1):
self.page = page1
@ -248,29 +249,17 @@ class OptionHandler:
option_name = translated_options.get(f'trans_option_{index}', f'옵션_{index}')
await self.update_option_image(index, option_image_url, product_name, option_name, self.debug_flag)
# # Vertex AI를 통해 옵션명을 번역
# translated_options = await self.vertexAItranslator.translate_options(self.option_info['original_names'], product_name)
# self.logger.debug(f"번역된 옵션: {translated_options}")
# 8. A-Z or 1-99 button 클릭
# # 5. 번역된 옵션명 편집칸에 입력
# self.logger.debug("번역된 옵션명을 입력합니다.")
# await self.apply_translated_options(translated_options, self.option_info['edit_fields'])
# # 6. 옵션 이미지 업데이트 (옵션 이미지가 있는 경우)
# self.logger.debug("옵션 이미지 업데이트 (옵션 이미지가 있는 경우)")
# for index, option_image_url in enumerate(self.option_info.get('option_images', []), start=1):
# option_name = translated_options.get(f'trans_option_{index}', f'옵션_{index}')
# await self.update_option_image(index, option_image_url, product_name, option_name, self.debug)
# # 7. 옵션 선택 및 제한 처리
# await self.adjust_options(self.option_info['checkboxes'], max_option_count)
# # 8. 정리된 옵션을 다시한번 더 가격 낮은 순으로 정렬 클릭
# await self.low_order_click()
what_prefix_button = '1-99'
# 9. A-Z 버튼 클릭
self.logger.debug("A-Z 버튼을 클릭합니다.")
self.AtoZ_button_click()
if what_prefix_button == 'A-Z':
self.logger.debug("A-Z 버튼을 클릭합니다.")
await self.AtoZ_button_click()
elif what_prefix_button == '1-99':
# # 9. A-Z 버튼 클릭
self.logger.debug("1-99 버튼을 클릭합니다.")
await self.one_to_nine_button_click()
# 9. 저장 버튼 클릭
self.logger.debug("저장 버튼을 클릭합니다.")
@ -633,6 +622,10 @@ class OptionHandler:
self.logger.debug("A-Z 버튼을 클릭합니다.")
await self.page.click(self.AtoZ_button_locator)
async def one_to_nine_button_click(self):
self.logger.debug("1-99 버튼을 클릭합니다.")
await self.page.click(self.one_to_nine_button_locator)
async def low_order_click(self):
self.logger.debug("가격 낮은 순 정렬을 클릭합니다.")
await self.page.click(self.low_order_button_locator)

View File

@ -765,16 +765,17 @@ class CMBSettingsDialog(QDialog):
query = '''
SELECT id, category1, category2, category3, category4, crmobi_stage
FROM categories
WHERE category1 LIKE ? OR category2 LIKE ? OR category3 LIKE ? OR category4 LIKE ?
WHERE category1 LIKE :search_text OR category2 LIKE :search_text
OR category3 LIKE :search_text OR category4 LIKE :search_text
'''
args = [f"%{search_text}%"] * 4
args = {"search_text": f"%{search_text}%"} # 사전 형태로 변경
else:
# 검색어가 없는 경우 전체 로드
query = '''
SELECT id, category1, category2, category3, category4, crmobi_stage
FROM categories
'''
args = []
args = {}
# 검색 결과를 트리에 표시
self.category_tree.clear()
@ -812,53 +813,53 @@ class CMBSettingsDialog(QDialog):
self.logger.error(f"카테고리 검색 중 오류: {e}", exc_info=True)
def get_crmobi_stage_by_keyword(self, category):
"""
주어진 카테고리에 해당하는 CMB 단계가 설정되어 있는지 확인하고,
설정된 경우 해당 단계의 범위를 반환합니다.
# def get_crmobi_stage_by_keyword(self, category):
# """
# 주어진 카테고리에 해당하는 CMB 단계가 설정되어 있는지 확인하고,
# 설정된 경우 해당 단계의 범위를 반환합니다.
Parameters:
category (str): 카테고리 이름 (: '가구/인테리어')
# Parameters:
# category (str): 카테고리 이름 (예: '가구/인테리어')
Returns:
tuple: (min_amount, unit_amount, extra_cost) 값이 있는 경우
None: 설정된 CMB 단계가 없는 경우
"""
try:
# DB에서 카테고리에 대한 CMB 단계 정보를 가져옴
query = '''
SELECT crmobi_stage FROM categories
WHERE category1 = ? OR category2 = ? OR category3 = ? OR category4 = ?
'''
# Returns:
# tuple: (min_amount, unit_amount, extra_cost) 값이 있는 경우
# None: 설정된 CMB 단계가 없는 경우
# """
# try:
# # DB에서 카테고리에 대한 CMB 단계 정보를 가져옴
# query = '''
# SELECT crmobi_stage FROM categories
# WHERE category1 = ? OR category2 = ? OR category3 = ? OR category4 = ?
# '''
self.logger.debug(f"category : {category}")
args = [category] * 4
# self.db_manager.execute(query, args)
result = self.db_manager.fetchone(query, args)
# self.logger.debug(f"category : {category}")
# args = [category] * 4
# # self.db_manager.execute(query, args)
# result = self.db_manager.fetchone(query, args)
if result and result[0] > 0: # CMB 단계가 설정되어 있을 때
crmobi_stage = result[0]
# if result and result[0] > 0: # CMB 단계가 설정되어 있을 때
# crmobi_stage = result[0]
# 설정된 CMB 단계에 해당하는 범위 가져오기
stage_query = '''
SELECT threshold, increment_unit, extra_cost
FROM crmobi_stages
WHERE stage = ?
'''
# self.db_manager.execute(stage_query, (crmobi_stage,))
stage_result = self.db_manager.fetchone(stage_query, (crmobi_stage,))
self.logger.debug(f"stage_result : {stage_result}")
# # 설정된 CMB 단계에 해당하는 범위 가져오기
# stage_query = '''
# SELECT threshold, increment_unit, extra_cost
# FROM crmobi_stages
# WHERE stage = ?
# '''
# # self.db_manager.execute(stage_query, (crmobi_stage,))
# stage_result = self.db_manager.fetchone(stage_query, (crmobi_stage,))
# self.logger.debug(f"stage_result : {stage_result}")
if stage_result:
min_amount, unit_amount, extra_cost = stage_result
return (min_amount, unit_amount, extra_cost)
# if stage_result:
# min_amount, unit_amount, extra_cost = stage_result
# return (min_amount, unit_amount, extra_cost)
# CMB 단계가 설정되지 않은 경우
return None
# # CMB 단계가 설정되지 않은 경우
# return None
except Exception as e:
QMessageBox.critical(self, "DB 오류", f"DB 조회 중 오류가 발생했습니다: {e}")
return None
# except Exception as e:
# QMessageBox.critical(self, "DB 오류", f"DB 조회 중 오류가 발생했습니다: {e}")
# return None
def get_crmobi_stage(self, category):

BIN
src/fail_translated1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/fail_translated2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
src/page_loading.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

BIN
test/fail_translated1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
test/fail_translated2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

182
test/img_test.py Normal file
View File

@ -0,0 +1,182 @@
import pyautogui
import time
import os
import subprocess
import win32gui, win32con, win32process
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.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} # 색상 기록
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, 100, 100, 1280, 720) # 위치 (100, 100), 크기 (1280x720)
if hwnd:
win32gui.SetForegroundWindow(hwnd)
pyautogui.hotkey('ctrl', 'l')
time.sleep(0.5)
pyautogui.typewrite(url)
pyautogui.press('enter')
time.sleep(2)
# 페이지 로딩 완료 대기
self.wait_for_loading_icon_to_disappear()
self.move_mouse_to_center(hwnd) # 마우스 중앙으로 이동
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')
time.sleep(1)
# 번역 성공 여부 확인
result = self.check_translation_by_color_change(original_color)
if result == "success":
print("번역이 성공적으로 완료되었습니다!")
elif result == "error":
print("번역에 실패했습니다.")
else:
print("번역 상태를 확인하지 못했습니다.")
# 최종 색상 출력
self.logger.debug(f"번역 전 색상: {self.colors['before']}")
self.logger.debug(f"번역 중 색상: {self.colors['during']}")
self.logger.debug(f"번역 후 색상: {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 = pyautogui.locateOnScreen(self.page_loading_icon_path, confidence=0.9)
if icon_location:
print("페이지 로딩 중...")
time.sleep(0.5) # 간격을 두고 다시 확인
except pyautogui.ImageNotFoundException:
# 아이콘이 화면에 없으면 로딩 완료로 간주
print("페이지 로딩이 완료되었습니다.")
return True # 로딩 완료
print("로딩 완료 대기 시간이 초과되었습니다.")
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.logger.debug(f"웨일 창을 찾았습니다: {hwnd_list[0]}")
return hwnd_list[0]
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, hwnd):
"""웨일 브라우저 창의 중앙으로 마우스 커서를 이동"""
rect = win32gui.GetWindowRect(hwnd) # 창 위치 및 크기 가져오기
center_x = (rect[0] + rect[2]) // 2 # 가로 중앙 계산
center_y = (rect[1] + rect[3]) // 2 # 세로 중앙 계산
pyautogui.moveTo(center_x, center_y)
self.logger.debug(f"마우스 커서를 창 중앙으로 이동: ({center_x}, {center_y})")
def check_translation_by_color_change(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_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):
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
# 예제 사용법
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://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/66ff967773994c46d388bb36/82d07178-ae60-49f7-a489-e02801ff7b06.jpg")

BIN
test/page_loading.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

BIN
userDB.db

Binary file not shown.

View File

@ -1,5 +1,5 @@
import pyautogui
import ctypes
import os
import time
import win32gui, win32con, win32process
from pyvda import VirtualDesktop, get_virtual_desktops
@ -10,7 +10,7 @@ import pyperclip # 클립보드 데이터를 확인하기 위한 라이브러
from PIL import ImageGrab
class WhaleTranslator:
def __init__(self, app, logger, secret_mode=True, vd_mode=False, max_failures=5):
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
@ -18,9 +18,21 @@ class WhaleTranslator:
self.whale_pid = 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.translation_success_flag = False # 번역 성공 플래그
self.failure_count = 0 # 실패 횟수
self.max_failures = max_failures # 최대 실패 횟수
# self.failure_count = 0 # 실패 횟수
if isSecret:
self.whale_window_name = "새 시크릿 탭 - Whale"
@ -59,9 +71,11 @@ class WhaleTranslator:
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 창 크기 조절 완료")
# 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, 100, 100, 1280, 720) # 위치 (100, 100), 크기 (1280x720)
# 주소창으로 이동 후 URL 입력
pyautogui.hotkey('ctrl', 'l')
@ -75,32 +89,32 @@ class WhaleTranslator:
if self.vd_mode:
self.return_to_virtual_desktop_1() # 가상 데스크탑 1로 복귀
def reset_failures(self):
"""실패 횟수를 초기화"""
self.failure_count = 0
self.logger.debug("실패 횟수가 초기화되었습니다.")
# 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}")
# 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() # 실패 횟수 초기화
# 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_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):
"""클립보드에 이미지 데이터가 있는지 확인"""
@ -225,47 +239,74 @@ class WhaleTranslator:
if not self.whale_hwnd:
# 웨일 창을 찾지 못했을 경우 사용자에게 입력 받기
self.logger.debug("웨일 창을 찾지 못했습니다. 계속하려면 'y'를 입력하세요.")
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.moveTo(960,580) # 마우스 센터로 이동
# pyautogui.hotkey('ctrl', 'l') # 웨일 브라우저의 주소창으로 이동
self.logger.debug(f"이미지 URL 주소 입력")
self.enter_url(url)
pyautogui.press('enter')
# await asyncio.sleep(1) # 페이지 로딩 대기
time.sleep(1)
time.sleep(1) # 페이지 로딩 대기
pyautogui.rightClick()
# await asyncio.sleep(0.2) # 페이지 로딩 대기
time.sleep(1)
# 페이지 로딩 완료 대기
self.logger.debug(f"페이지 로딩완료 대기")
self.wait_for_loading_icon_to_disappear()
pyautogui.press('r') # 번역 클릭
# await asyncio.sleep(7) # 페이지 로딩 대기
time.sleep(5)
self.logger.debug(f"페이지 로딩 완료 후 웨일 창의 가운데로 마우스 커서 이동")
# pyautogui.moveTo(960,580) # 마우스 센터로 이동
self.move_mouse_to_center(self.whale_hwnd)
time.sleep(0.5) # 마우스 이동 후 대기
pyautogui.rightClick()
# await asyncio.sleep(0.2) # 페이지 로딩 대기
time.sleep(1)
original_color = self.get_mouse_position_color() # 현재 색상 가져오기
self.colors['before'] = original_color
self.logger.debug(f"번역 전 색상: {original_color}")
pyautogui.press('c') # 번역된 이미지 클립보드에 복사
self.logger.debug("번역 작업을 위한 마우스 오른쪽 클릭 및 R 전송")
if not self.right_click_and_send_key('r'):
self.logger.error("번역 작업이 대화상자 문제로 중단되었습니다.")
return
# 번역 성공 여부 확인
result = self.check_translation_by_color_change(original_color)
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.logger.debug("이미지 복사를 위한 마우스 오른쪽 클릭 및 C 전송")
if not self.right_click_and_send_key('c'):
self.logger.error("복사 작업이 대화상자 문제로 중단되었습니다.")
return
self.logger.debug(f"클립보드에 번역된이미지 복사 대기 1s")
time.sleep(1) # 클립보드 업데이트 대기
self.logger.debug("클립보드에 이미지 데이터가 존재하는지 확인 중.....")
if self.is_image_in_clipboard(): # 클립보드에 이미지 데이터가 있으면 성공
self.translation_success_flag = True
self.logger.debug(f'번역 성공: {url}')
self.reset_failures() # 번역 성공 시 연속 실패 횟수 초기화
else:
self.logger.error(f'번역 실패: 클립보드에 이미지 데이터가 없음')
self.handle_translation_failure()
# self.handle_translation_failure()
self.logger.error(f'번역 프로세스 완료. 웨일 기본페이지로 돌아감')
self.enter_url(self.newtab)
self.logger.debug(f'번역 완료: {url}')
# self.logger.debug(f'번역 완료: {url}')
if self.vd_mode:
self.return_to_virtual_desktop_1()
@ -276,11 +317,24 @@ class WhaleTranslator:
# path에는 현재 폴더의 tmp_img폴더에 상품명-옵션명 형태로 제공됨
except Exception as e:
self.logger.error(f"번역 중 오류 발생: {e}")
self.handle_translation_failure()
self.logger.error(f"번역 중 오류 발생: {e}", exc_info=True)
# self.handle_translation_failure()
else:
self.logger.debug('웨일 창을 찾을 수 없습니다.')
self.handle_translation_failure()
# 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):
"""
@ -427,3 +481,120 @@ class WhaleTranslator:
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 move_mouse_to_center(self, hwnd):
"""웨일 브라우저 창의 중앙으로 마우스 커서를 이동"""
rect = win32gui.GetWindowRect(hwnd) # 창 위치 및 크기 가져오기
center_x = (rect[0] + rect[2]) // 2 # 가로 중앙 계산
center_y = (rect[1] + rect[3]) // 2 # 세로 중앙 계산
pyautogui.moveTo(center_x, center_y)
self.logger.debug(f"마우스 커서를 창 중앙으로 이동: ({center_x}, {center_y})")
def check_translation_by_color_change(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_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):
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(5) # 작업 대기
return True # 작업 성공