많은 수정
This commit is contained in:
parent
771dcd32b6
commit
ea89b50a7e
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
72020
appTranslator.log
72020
appTranslator.log
File diff suppressed because one or more lines are too long
|
|
@ -231,53 +231,57 @@ class BrowserController:
|
|||
return 0
|
||||
|
||||
|
||||
# async def get_product_name(self, index, selector='xpath'):
|
||||
# def fetch_image_urls_ori(self, html_content):
|
||||
# """
|
||||
# 상품명을 수집하는 메서드
|
||||
# index : 상품명을 수집하는 인덱스
|
||||
# selector : 수집방법 (css 또는 xpath)
|
||||
# HTML 콘텐츠에서 모든 <img> 태그의 URL을 순서대로 추출하고 중복 제거.
|
||||
# """
|
||||
# try:
|
||||
# # config.ini에서 설정된 선택자에 인덱스를 적용하여 가져옴
|
||||
# # product_name_selector = self.product_name_template.format(index=index)
|
||||
# # self.logger.debug(f"사용된 선택자: {product_name_selector}") # 선택자 출력
|
||||
# soup = BeautifulSoup(html_content, 'html.parser')
|
||||
|
||||
# product_name_xpath_selector = self.product_name_template_xpath.format(index=index)
|
||||
# self.logger.debug(f"사용된 선택자: {product_name_xpath_selector}") # 선택자 출력
|
||||
# # 순서를 유지하면서 중복을 제거하기 위해 리스트 사용
|
||||
# image_urls = []
|
||||
# seen_urls = set()
|
||||
|
||||
# # <figure class="image"> 내부의 모든 <img> 태그 찾기
|
||||
# figures = soup.find_all('figure', class_='image')
|
||||
# for figure in figures:
|
||||
# img_tag = figure.find('img')
|
||||
# if img_tag and 'src' in img_tag.attrs:
|
||||
# url = img_tag['src']
|
||||
# if url not in seen_urls:
|
||||
# image_urls.append(url)
|
||||
# seen_urls.add(url) # 중복 방지
|
||||
|
||||
# # XPath 기반으로 요소 검색
|
||||
# product_name_element = await self.page.locator(f"xpath={product_name_xpath_selector}").element_handle()
|
||||
|
||||
# # product_name_element = await self.page.query_selector(product_name_selector)
|
||||
|
||||
# # product_name_element가 None인지 확인
|
||||
# if product_name_element is None:
|
||||
# self.logger.error(f"상품명 요소를 찾을 수 없습니다: index {index}")
|
||||
# return "수집 오류 발생"
|
||||
|
||||
# # 요소가 존재할 경우, inner_text 수집
|
||||
# product_name = await product_name_element.inner_text()
|
||||
# return product_name.strip()
|
||||
# except Exception as e:
|
||||
# self.logger.error(f"상품명 수집 중 오류: {e}")
|
||||
# return "수집 오류 발생"
|
||||
|
||||
# # class="image_resized"를 가진 모든 <img> 태그 찾기
|
||||
# images_resized = soup.find_all('img', class_='image_resized')
|
||||
# for img in images_resized:
|
||||
# if img and 'src' in img.attrs:
|
||||
# url = img['src']
|
||||
# if url not in seen_urls:
|
||||
# image_urls.append(url)
|
||||
# seen_urls.add(url) # 중복 방지
|
||||
|
||||
# return image_urls
|
||||
|
||||
def fetch_image_urls(self, html_content):
|
||||
"""
|
||||
HTML 콘텐츠에서 모든 <img> 태그의 URL을 HTML 소스의 순서대로 추출.
|
||||
HTML 콘텐츠에서 모든 <img> 태그의 URL을 추출하는 함수.
|
||||
<figure> 안의 <img> 태그와 독립된 <img> 태그 모두 처리.
|
||||
"""
|
||||
soup = BeautifulSoup(html_content, 'html.parser')
|
||||
|
||||
# HTML 소스에서 순서대로 <img> 태그를 찾음
|
||||
|
||||
# 모든 <img> 태그를 찾기
|
||||
image_urls = []
|
||||
img_tags = soup.find_all('img')
|
||||
|
||||
for img in img_tags:
|
||||
if img and 'src' in img.attrs:
|
||||
image_urls.append(img['src']) # URL을 리스트에 추가
|
||||
# img 태그에서 src 속성 추출
|
||||
if 'src' in img.attrs:
|
||||
image_url = img['src']
|
||||
image_urls.append(image_url)
|
||||
self.logger.debug(f"fetch_image_urls 에서 추출한 이미지URL 갯수 : {len(image_urls)} 개")
|
||||
|
||||
self.logger.debug(f"fetch_image_urls 에서 추출한 이미지URL 목록 : {image_urls}")
|
||||
|
||||
return image_urls
|
||||
|
||||
async def close_ad_if_exists(self):
|
||||
|
|
@ -285,7 +289,7 @@ class BrowserController:
|
|||
"""광고 다이얼로그가 있으면 닫기 버튼을 클릭하는 메서드"""
|
||||
try:
|
||||
# 광고 다이얼로그가 나타날 때까지 기다림
|
||||
await self.page.wait_for_selector(self.close_ad_dialog_locator, timeout=3000)
|
||||
await self.page.wait_for_selector(self.close_ad_dialog_locator, timeout=10000, state='visible')
|
||||
self.logger.debug("다이얼로그가 발견되었습니다. 닫기 버튼을 클릭합니다.")
|
||||
|
||||
# 닫기 버튼 클릭
|
||||
|
|
@ -503,43 +507,33 @@ class BrowserController:
|
|||
# await input_field.press('Enter') # 엔터 키를 입력하여 줄바꿈
|
||||
|
||||
|
||||
# 마크다운 형식
|
||||
md_prifix = "#### "
|
||||
# 첫 번째 옵션에만 - 기호를 붙여 목록 시작
|
||||
await input_field.type(f"{md_prifix}")
|
||||
|
||||
await input_field.type(f"- 1. {option_data[0]}")
|
||||
# 첫 번째 옵션의 번역된 옵션명만 입력
|
||||
first_key = list(option_data.keys())[0]
|
||||
first_value = option_data[first_key]
|
||||
await input_field.type(f"- 1. {first_value}")
|
||||
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"{i}. "
|
||||
option_prefix = ""
|
||||
await input_field.type(f"{md_prifix}")
|
||||
await input_field.type(option_prefix + option_text)
|
||||
# 나머지 옵션도 번역된 옵션명만 입력
|
||||
for i, (key, value) in enumerate(list(option_data.items())[1:], start=2):
|
||||
await input_field.type(f"{i}. {value}") # 옵션 번호와 번역된 옵션명만 입력
|
||||
await input_field.press('Enter') # 엔터 키를 입력하여 줄바꿈
|
||||
|
||||
# 목록 끝을 알리기 위해 엔터 두 번 입력
|
||||
await input_field.press('Enter')
|
||||
await input_field.press('Enter')
|
||||
|
||||
# 목록 끝을 알리기 위해 엔터 두 번 입력
|
||||
await input_field.press('Enter')
|
||||
await input_field.press('Enter')
|
||||
await input_field.press('Enter')
|
||||
# 후두부 텍스트 입력
|
||||
await input_field.type('### 나열된 옵션목록 이외의 옵션이 필요하실 경우 고객센터로 연락주세요.')
|
||||
await input_field.press('Enter')
|
||||
await input_field.type('---')
|
||||
await input_field.press('Enter')
|
||||
|
||||
|
||||
# 후두부 텍스트 입력
|
||||
await input_field.type('---')
|
||||
await input_field.type('### 나열된 옵션목록 이외의 옵션이 필요하실 경우 고객센터로 연락주세요.')
|
||||
await input_field.press('Enter')
|
||||
await input_field.type('---')
|
||||
await input_field.press('Enter')
|
||||
|
||||
self.logger.debug('옵션 데이터 입력 완료 후 엔터 입력')
|
||||
self.logger.debug('옵션 데이터 입력 완료 후 엔터 입력')
|
||||
|
||||
return image_urls
|
||||
except Exception as e:
|
||||
self.logger.debug(f"이미지 URL 추출 중 오류: {e}", exc_info=True)
|
||||
return []
|
||||
self.logger.debug(f"이미지 URL 추출 & 옵션데이터 입력 처리 중 오류: {e}", exc_info=True)
|
||||
return image_urls if image_urls else []
|
||||
|
||||
def paste_image_in_chrome(self, clipboardImageManager, url):
|
||||
"""크롬으로 포커스를 옮기고 클립보드의 이미지를 붙여넣고 엔터 입력"""
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ class ClipboardImageManager:
|
|||
self.logger.debug("유효하지 않은 Base64 이미지 데이터입니다.")
|
||||
return None
|
||||
|
||||
async def download_image_from_url(self, url, max_retries=3):
|
||||
def download_image_from_url(self, url, max_retries=3):
|
||||
"""URL에서 이미지를 다운로드하고 PIL 이미지 객체로 반환"""
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
|
||||
|
|
|
|||
12
config.ini
12
config.ini
|
|
@ -7,7 +7,8 @@ oversea_shipping_locator = '//*[@id='productMainContentContainerId']/div/div[1]/
|
|||
option_count_text_locator = 'div#productMainContentContainerId th:nth-child(2) > div > span'
|
||||
product_cost_locator = '//*[@id='productMainContentContainerId']/div/div[2]/div/div/div[5]/div[1]/div/div/div/div/div[2]/table/tbody/tr[{index}]/td[3]/div/div/div/div[2]/input'
|
||||
standard_selling_price_locator = '//*[@id='productMainContentContainerId']/div/div[2]/div/div/div[5]/div[1]/div/div/div/div/div[2]/table/tbody/tr[{index}]/td[4]/div/div/div[1]/div/div[2]/input'
|
||||
|
||||
product_cost_for_single_locator = '//*[@id="productMainContentContainerId"]/div/div[2]/div/div/div[2]/div[1]/div/div/div/div/div[2]/table/tbody/tr[2]/td[2]/div/div/div/div[2]/input'
|
||||
standard_selling_price_for_single_locator = '//*[@id="productMainContentContainerId"]/div/div[2]/div/div/div[2]/div[1]/div/div/div/div/div[2]/table/tbody/tr[2]/td[3]/div/div/div[1]/div/div[2]/input'
|
||||
|
||||
[OptionLocators]
|
||||
# 옵션 관련 선택자
|
||||
|
|
@ -23,8 +24,10 @@ 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"]'
|
||||
image_selector_template = '#productMainContentContainerId li:nth-child({index}) img.sc-gbvfcU.ezktkd'
|
||||
price_selector_template = '#productMainContentContainerId li:nth-child({index}) sup'
|
||||
; image_selector_template = '#productMainContentContainerId li:nth-child({index}) img.sc-gbvfcU.ezktkd'
|
||||
image_selector_template = 'div#productMainContentContainerId li:nth-child({index}) > div > div:nth-child(1) > div > div:nth-child(2) > div > img'
|
||||
; price_selector_template = '#productMainContentContainerId li:nth-child({index}) sup'
|
||||
price_selector_template = '//*[@id="productMainContentContainerId"]/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{index}]/div/div[1]/div/div[3]/div[1]/div[2]/button/span/sup'
|
||||
delete_button_selector_template = '#productMainContentContainerId > div.sc-TOgAA.fZvEqY > div:nth-child(2) > div > div > div:nth-child(2) > div > div.sc-cFShuL.dbIeho > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.sc-fGdiLE.iyXMeU > div.ant-list.ant-list-split.css-1li46mu > div > div > ul > li:nth-child({index}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div.ant-row.ant-row-no-wrap.ant-row-space-between.ant-row-middle.css-1li46mu > div:nth-child(1) > div'
|
||||
confirm_delete_button_locator = 'body > div:nth-child(18) > div > div.ant-modal-wrap.ant-modal-confirm-centered.ant-modal-centered > div > div.sc-ddjGPC.jbwEYW > div > div > div > div.ant-modal-confirm-btns > button.ant-btn.css-1li46mu.ant-btn-primary.ant-btn-dangerous'
|
||||
add_button_selector_template = '#productMainContentContainerId > div.sc-TOgAA.fZvEqY > div:nth-child(2) > div > div > div:nth-child(2) > div > div.sc-cFShuL.dbIeho > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.sc-fGdiLE.iyXMeU > div.ant-list.ant-list-split.css-1li46mu > div > div > ul > li:nth-child({index}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div > img'
|
||||
|
|
@ -97,7 +100,8 @@ staff_login_button_locator = 'button:has-text("직원 로그인 하기")'
|
|||
admin_toggle_locator = 'button[role="switch"]'
|
||||
|
||||
# 광고 다이얼로그 관련 선택자
|
||||
close_ad_dialog_locator = 'div.ant-modal-wrap.ant-modal-centered'
|
||||
; close_ad_dialog_locator = 'div.ant-modal-wrap.ant-modal-centered'
|
||||
close_ad_dialog_locator = 'div[role="dialog"]'
|
||||
close_ad_button_locator = 'div.ant-modal-footer > div > div > button[type='button'].ant-btn.css-1li46mu.ant-btn-default'
|
||||
|
||||
# 상품 관련 선택자
|
||||
|
|
|
|||
11
gui.py
11
gui.py
|
|
@ -56,7 +56,7 @@ class TranslationApp(QWidget):
|
|||
self.cmb_diag = CMBSettingsDialog(parent=self, logger=self.logger, db_manager=self.db_manager, initial_db_path=self.initial_db_path, user_db_path=self.user_db_path, debug=self.debug)
|
||||
self.clipboardImageManager = ClipboardImageManager(self, logger, self.browser_controller, self.debug)
|
||||
self.optionHandler = OptionHandler(self.locator_manager, self.browser_controller, self.whale_translator, self.logger, self.vertexAI, self.debug)
|
||||
self.priceHandler = PriceHandler(self.locator_manager, self.browser_controller, self.logger, self.vertexAI, self.cmb_diag, self.debug)
|
||||
self.priceHandler = PriceHandler(self.locator_manager, self.browser_controller, self.logger, self.optionHandler, self.vertexAI, self.cmb_diag, self.debug)
|
||||
self.titleHandler = TitleHandler(self.locator_manager, self.browser_controller, self.logger)
|
||||
self.running = False
|
||||
|
||||
|
|
@ -844,10 +844,13 @@ class TranslationApp(QWidget):
|
|||
break
|
||||
|
||||
self.logger.debug(f"웨일 브라우저를 활용한 이미지 번역 프로세스")
|
||||
self.whale_translator.translate_image(url)
|
||||
is_success_translated = self.whale_translator.translate_image(url)
|
||||
|
||||
self.logger.debug(f"paste_image_in_chrome - 이미지 붙여넣기")
|
||||
self.browser_controller.paste_image_in_chrome(self.clipboardImageManager, url)
|
||||
if is_success_translated:
|
||||
self.logger.debug(f"paste_image_in_chrome - 이미지 붙여넣기")
|
||||
self.browser_controller.paste_image_in_chrome(self.clipboardImageManager, url)
|
||||
else:
|
||||
self.logger.debug(f"{url} 이미지 번역 실패")
|
||||
|
||||
self.logger.debug(f"Progress Update")
|
||||
self.update_detail_progress(i,total_images)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ class LocatorManager:
|
|||
'option_count_text_locator': self.config.get('PriceLocators', 'option_count_text_locator').strip("'"),
|
||||
'product_cost_locator': self.config.get('PriceLocators', 'product_cost_locator').strip("'"),
|
||||
'standard_selling_price_locator': self.config.get('PriceLocators', 'standard_selling_price_locator').strip("'"),
|
||||
'product_cost_for_single_locator': self.config.get('PriceLocators', 'product_cost_for_single_locator').strip("'"),
|
||||
'standard_selling_price_for_single_locator': self.config.get('PriceLocators', 'standard_selling_price_for_single_locator').strip("'"),
|
||||
}
|
||||
|
||||
# BrowserControl 섹션
|
||||
|
|
|
|||
41
option.py
41
option.py
|
|
@ -13,6 +13,8 @@ class OptionHandler:
|
|||
self.debug_flag = debug_flag
|
||||
self.vertexAItranslator = vertexAI
|
||||
self.whale_translator = whale_translator
|
||||
self.is_percenty_success = False
|
||||
self.is_vertext_success = False
|
||||
self.init_option_info()
|
||||
|
||||
# 선택자 로드
|
||||
|
|
@ -120,10 +122,14 @@ class OptionHandler:
|
|||
async def store_selected_options(self):
|
||||
"""현재 페이지에서 선택된 옵션을 수집하여, 가격 낮은 순으로 정렬한 후 클래스 변수에 저장"""
|
||||
try:
|
||||
selected_translated_options = []
|
||||
selected_translated_options = {}
|
||||
|
||||
total_options_count = len(self.option_info['original_names'])
|
||||
|
||||
# checked_states 딕셔너리에서 값이 True인 항목의 개수를 계산
|
||||
checked_count = sum(value is True for value in self.option_info['checked_states'].values())
|
||||
self.logger.debug(f"체크된 옵션 수: {checked_count}")
|
||||
|
||||
await self.low_order_click()
|
||||
|
||||
for i in range(1, total_options_count + 1):
|
||||
|
|
@ -135,10 +141,7 @@ class OptionHandler:
|
|||
option_input_element = await self.page.query_selector(option_input_selector)
|
||||
if option_input_element:
|
||||
option_name_value = (await option_input_element.get_attribute('value')).strip()
|
||||
selected_translated_options.append(
|
||||
# (option_name_value, self.option_info['prices'].get(option_name_value, {}).get('low_price', 0))
|
||||
(option_name_value)
|
||||
)
|
||||
selected_translated_options[option_name_value] = self.option_info['prices'].get(option_name_value, {}).get('low_price', 0)
|
||||
|
||||
self.option_info['selected_translated_options'] = selected_translated_options
|
||||
self.logger.debug(f"선택된 옵션 저장 완료: {selected_translated_options}")
|
||||
|
|
@ -182,7 +185,7 @@ class OptionHandler:
|
|||
# 2. 전체 옵션 체크박스 상태 확인
|
||||
click_to_check_to_all = True
|
||||
|
||||
self.logger.debug(f"일부 체크된 옵션상품에 대한 처리 방법 : {"전체체크에서 시작" if click_to_check_to_all else "일부 체크시 수정완료로 판단"}")
|
||||
self.logger.debug(f"일부 체크된 옵션상품에 대한 처리 방법 : {'전체체크에서 시작' if click_to_check_to_all else '일부 체크시 수정완료로 판단'}")
|
||||
|
||||
if click_to_check_to_all:
|
||||
self.logger.debug("옵션이 일부만 체크된 상태입니다. 전체 체크로 바꿉니다.")
|
||||
|
|
@ -202,15 +205,13 @@ class OptionHandler:
|
|||
self.logger.debug(f"옵션 AI번역 : {toggle_states['optionTrnas']}")
|
||||
self.option_info = await self.collect_options_info()
|
||||
|
||||
translation_success = False # 성공/실패 플래그
|
||||
|
||||
try:
|
||||
# Vertex AI를 통한 번역 시도
|
||||
translated_options = await self.vertexAItranslator.translate_options(self.option_info['original_names'], product_name)
|
||||
self.logger.debug(f"번역된 옵션 입력")
|
||||
await self.apply_translated_options(translated_options, self.option_info['edit_fields'])
|
||||
|
||||
translation_success = True # 번역 성공
|
||||
self.is_vertext_success = True # 번역 성공
|
||||
|
||||
except ValueError as ve:
|
||||
# 안전 필터 예외 처리
|
||||
|
|
@ -220,7 +221,8 @@ class OptionHandler:
|
|||
await self.page.click(self.ai_option_btn_selector)
|
||||
self.logger.debug("번역을 위한 5초간 대기")
|
||||
await asyncio.sleep(5)
|
||||
translation_success = False # 번역 실패
|
||||
self.is_vertext_success = False
|
||||
self.is_percenty_success = True
|
||||
|
||||
# except google.api_core.exceptions.ResourceExhausted as re:
|
||||
# # 할당량 초과 예외 처리
|
||||
|
|
@ -238,10 +240,11 @@ class OptionHandler:
|
|||
await self.page.click(self.ai_option_btn_selector)
|
||||
self.logger.debug("번역을 위한 5초간 대기")
|
||||
await asyncio.sleep(5)
|
||||
translation_success = False # 번역 실패
|
||||
self.is_vertext_success = False # 번역 실패
|
||||
self.is_percenty_success = True
|
||||
|
||||
# 번역 성공 여부에 따른 로그
|
||||
self.logger.debug(f"[{'VertexAI' if translation_success else '퍼센티AI'}] 를 이용한 옵션번역 성공")
|
||||
self.logger.debug(f"[{'VertexAI' if self.is_vertext_success else '퍼센티AI'}] 를 이용한 옵션번역 성공")
|
||||
|
||||
except Exception as e:
|
||||
# 옵션 처리 중 오류 발생 시 전체 로그 출력
|
||||
|
|
@ -252,9 +255,10 @@ class OptionHandler:
|
|||
self.logger.debug(f"옵션 필터링 및 조정 : {toggle_states['optionAutoSelect']}")
|
||||
await self.filter_and_adjust_options(max_option_count)
|
||||
|
||||
# 6. 선택된 옵션 재수집
|
||||
self.logger.debug(f"옵션 필터링 및 조정")
|
||||
await self.store_selected_options() # 페이지에서 실제 선택된 옵션을 수집하여 저장
|
||||
# 6. 선택된 옵션정보 재수집
|
||||
if self.is_percenty_success:
|
||||
self.logger.debug(f"퍼센티 옵션번역으로 인해 선택된 옵션정보 재수집")
|
||||
await self.store_selected_options() # 페이지에서 실제 선택된 옵션을 수집하여 저장
|
||||
|
||||
# 7. 옵션 이미지 업데이트 (옵션 이미지가 있는 경우)
|
||||
if toggle_states['optionIMGTrans']:
|
||||
|
|
@ -378,6 +382,12 @@ class OptionHandler:
|
|||
elements = await asyncio.gather(*tasks)
|
||||
original_name_element, edit_field_element, checkbox_element, image_element, price_element = elements
|
||||
|
||||
self.logger.debug(f"{i}번째 original_name_element : {original_name_element}")
|
||||
self.logger.debug(f"{i}번째 edit_field_element : {edit_field_element}")
|
||||
self.logger.debug(f"{i}번째 checkbox_element : {checkbox_element}")
|
||||
self.logger.debug(f"{i}번째 image_element : {image_element}")
|
||||
self.logger.debug(f"{i}번째 price_element : {price_element}")
|
||||
|
||||
if original_name_element:
|
||||
original_name = await original_name_element.inner_text()
|
||||
self.option_info['original_names'][f'origin_option_{i}'] = original_name
|
||||
|
|
@ -386,6 +396,7 @@ class OptionHandler:
|
|||
|
||||
# 체크박스 상태 수집 (체크 여부는 클래스 이름으로 판단)
|
||||
checkbox_state = None
|
||||
|
||||
if checkbox_element:
|
||||
checkbox_classes = await checkbox_element.get_attribute('class')
|
||||
if 'ant-checkbox-checked' in checkbox_classes:
|
||||
|
|
|
|||
30
price.py
30
price.py
|
|
@ -7,9 +7,10 @@ import math , re, time
|
|||
from playwright.async_api import TimeoutError
|
||||
|
||||
class PriceHandler:
|
||||
def __init__(self, locator_manager, browser_controller, logger, vertexAI, cmb_diag, debug_flag=False):
|
||||
def __init__(self, locator_manager, browser_controller, logger, optionHandler,vertexAI, cmb_diag, debug_flag=False):
|
||||
self.locator_manager = locator_manager
|
||||
self.browser_controller = browser_controller
|
||||
self.optionHandler = optionHandler
|
||||
self.page = self.browser_controller.page
|
||||
self.logger = logger
|
||||
self.debug_flag = debug_flag
|
||||
|
|
@ -32,6 +33,8 @@ class PriceHandler:
|
|||
# Locator들을 미리 로드하여 초기화 - locator_template:동적 로딩, locator:고정로딩
|
||||
self.product_cost_locator_template = self.locator_manager.get_locator('PriceLocators', 'product_cost_locator')
|
||||
self.standard_selling_price_locator_template = self.locator_manager.get_locator('PriceLocators', 'standard_selling_price_locator')
|
||||
self.product_cost_for_single_locator = self.locator_manager.get_locator('PriceLocators', 'product_cost_for_single_locator')
|
||||
self.standard_selling_price_for_single_locator = self.locator_manager.get_locator('PriceLocators', 'standard_selling_price_for_single_locator')
|
||||
self.plus_margin_locator = self.locator_manager.get_locator('PriceLocators', 'plus_margin_locator')
|
||||
self.oversea_shipping_locator = self.locator_manager.get_locator('PriceLocators', 'oversea_shipping_locator')
|
||||
self.return_fee_input_locator = self.locator_manager.get_locator('PriceLocators', 'return_fee_input_locator')
|
||||
|
|
@ -87,7 +90,10 @@ class PriceHandler:
|
|||
self.logger.debug(f"더하기마진값{initial_plusmargin}을 팔린가격{sold_price}으로 간주")
|
||||
|
||||
self.logger.debug("옵션 가격 정보를 수집합니다.")
|
||||
option_data, min_cost, max_cost, avg_cost, upper_avg_cost = await self.collect_product_costs_and_prices() # 수집된 옵션정보를 반환
|
||||
|
||||
is_single = self.optionHandler.option_info['is_single_option']
|
||||
|
||||
option_data, min_cost, max_cost, avg_cost, upper_avg_cost = await self.collect_product_costs_and_prices(is_single) # 수집된 옵션정보를 반환
|
||||
if option_data is None:
|
||||
self.logger.error("상품 옵션 정보를 수집하지 못했습니다.", exc_info=True)
|
||||
return
|
||||
|
|
@ -533,7 +539,7 @@ class PriceHandler:
|
|||
try:
|
||||
|
||||
# 요소를 기다림
|
||||
option_count_text_element = await self.page.wait_for_selector(self.option_count_text_locator)
|
||||
option_count_text_element = await self.page.wait_for_selector(self.option_count_text_locator, timeout=5000)
|
||||
|
||||
# 텍스트에서 숫자만 추출
|
||||
option_text = await option_count_text_element.text_content()
|
||||
|
|
@ -556,7 +562,7 @@ class PriceHandler:
|
|||
return 1 # 예외 발생 시 기본값 1 반환
|
||||
|
||||
|
||||
async def collect_product_costs_and_prices(self):
|
||||
async def collect_product_costs_and_prices(self, is_single):
|
||||
"""
|
||||
상품 원가와 판매가를 수집하여 반환합니다. 단위는 위안화
|
||||
|
||||
|
|
@ -572,15 +578,25 @@ class PriceHandler:
|
|||
"""
|
||||
try:
|
||||
# 옵션 개수 가져오기 (두 가지 방법 중 하나 사용)
|
||||
total_options = await self.get_option_count_from_text()
|
||||
if is_single:
|
||||
total_options = 1
|
||||
else:
|
||||
total_options = await self.get_option_count_from_text()
|
||||
|
||||
self.product_costs = [] # 모든 옵션의 상품원가를 저장할 리스트
|
||||
self.option_data = [] # 각 옵션의 상품원가와 기준판매가를 저장할 리스트
|
||||
|
||||
self.logger.debug(f"collect_product_costs_and_prices 단일옵션 : {is_single}")
|
||||
|
||||
# 옵션 개수만큼 순회하면서 값을 수집
|
||||
for i in range(1, total_options + 1):
|
||||
product_cost_locator = self.product_cost_locator_template.format(index=i+1)
|
||||
standard_selling_price_locator = self.standard_selling_price_locator_template.format(index=i+1)
|
||||
|
||||
if is_single:
|
||||
product_cost_locator = self.product_cost_for_single_locator
|
||||
standard_selling_price_locator = self.standard_selling_price_for_single_locator
|
||||
else:
|
||||
product_cost_locator = self.product_cost_locator_template.format(index=i+1)
|
||||
standard_selling_price_locator = self.standard_selling_price_locator_template.format(index=i+1)
|
||||
|
||||
# 각 선택자를 사용하여 요소 찾기
|
||||
product_cost_element = await self.page.wait_for_selector(product_cost_locator)
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -42,6 +42,8 @@ class WhaleTranslator:
|
|||
pyautogui.typewrite(url)
|
||||
pyautogui.press('enter')
|
||||
time.sleep(2)
|
||||
|
||||
size = self.get_whale_window_title()
|
||||
|
||||
# # 페이지 로딩 완료 대기
|
||||
# self.wait_for_loading_icon_to_disappear()
|
||||
|
|
@ -144,6 +146,37 @@ class WhaleTranslator:
|
|||
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):
|
||||
|
|
@ -174,6 +207,8 @@ class WhaleTranslator:
|
|||
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:
|
||||
|
|
@ -255,6 +290,7 @@ 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')
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import time
|
|||
import win32gui, win32con, win32process
|
||||
from pyvda import VirtualDesktop, get_virtual_desktops
|
||||
import subprocess
|
||||
import asyncio
|
||||
import pyscreeze
|
||||
import KO_EN
|
||||
import pyperclip # 클립보드 데이터를 확인하기 위한 라이브러리
|
||||
from PIL import ImageGrab
|
||||
|
|
@ -16,6 +16,7 @@ class WhaleTranslator:
|
|||
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
|
||||
|
|
@ -30,6 +31,7 @@ class WhaleTranslator:
|
|||
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 # 실패 횟수
|
||||
|
|
@ -75,7 +77,8 @@ class WhaleTranslator:
|
|||
# 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)
|
||||
self.set_window_position(self.whale_hwnd, 1, 1, 1280, 720) # 위치 (1, 1), 크기 (1280x720)
|
||||
self.update_whale_rect()
|
||||
|
||||
# 주소창으로 이동 후 URL 입력
|
||||
pyautogui.hotkey('ctrl', 'l')
|
||||
|
|
@ -132,10 +135,29 @@ class WhaleTranslator:
|
|||
return False
|
||||
|
||||
def find_whale_window(self):
|
||||
"""웨일 창 핸들을 찾는 메서드"""
|
||||
if not self.whale_hwnd:
|
||||
self.whale_hwnd = self.find_window_by_title(self.whale_window_name)
|
||||
return self.whale_hwnd
|
||||
"""웨일 창을 제목을 기준으로 찾는 메서드"""
|
||||
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):
|
||||
|
|
@ -253,15 +275,24 @@ class WhaleTranslator:
|
|||
self.logger.debug(f"이미지 URL 주소 입력")
|
||||
self.enter_url(url)
|
||||
pyautogui.press('enter')
|
||||
time.sleep(1) # 페이지 로딩 대기
|
||||
time.sleep(2) # 페이지 로딩 대기
|
||||
|
||||
# 현재 웨일 창의 해상도를 확인하여 기준 이하일 경우 패스
|
||||
min_width = 200
|
||||
min_height = 150
|
||||
|
||||
if not self.get_whale_window_title(min_width=min_width,min_height=min_height):
|
||||
self.logger.debug("해상도가 기준보다 낮아 작업을 패스합니다.")
|
||||
return False # 해상도 기준 미달로 작업 종료
|
||||
|
||||
|
||||
# 페이지 로딩 완료 대기
|
||||
self.logger.debug(f"페이지 로딩완료 대기")
|
||||
self.wait_for_loading_icon_to_disappear()
|
||||
# self.logger.debug(f"페이지 로딩완료 대기")
|
||||
# self.wait_for_loading_icon_to_disappear()
|
||||
|
||||
self.logger.debug(f"페이지 로딩 완료 후 웨일 창의 가운데로 마우스 커서 이동")
|
||||
# pyautogui.moveTo(960,580) # 마우스 센터로 이동
|
||||
self.move_mouse_to_center(self.whale_hwnd)
|
||||
self.move_mouse_to_center()
|
||||
time.sleep(0.5) # 마우스 이동 후 대기
|
||||
|
||||
original_color = self.get_mouse_position_color() # 현재 색상 가져오기
|
||||
|
|
@ -271,10 +302,10 @@ class WhaleTranslator:
|
|||
self.logger.debug("번역 작업을 위한 마우스 오른쪽 클릭 및 R 전송")
|
||||
if not self.right_click_and_send_key('r'):
|
||||
self.logger.error("번역 작업이 대화상자 문제로 중단되었습니다.")
|
||||
return
|
||||
return False
|
||||
|
||||
# 번역 성공 여부 확인
|
||||
result = self.check_translation_by_color_change(original_color)
|
||||
result = self.check_translation_by_color_change()
|
||||
if result == "success":
|
||||
self.logger.debug("번역이 성공적으로 완료되었습니다!")
|
||||
elif result == "error":
|
||||
|
|
@ -290,7 +321,7 @@ class WhaleTranslator:
|
|||
self.logger.debug("이미지 복사를 위한 마우스 오른쪽 클릭 및 C 전송")
|
||||
if not self.right_click_and_send_key('c'):
|
||||
self.logger.error("복사 작업이 대화상자 문제로 중단되었습니다.")
|
||||
return
|
||||
return False
|
||||
|
||||
self.logger.debug(f"클립보드에 번역된이미지 복사 대기 1s")
|
||||
time.sleep(1) # 클립보드 업데이트 대기
|
||||
|
|
@ -316,11 +347,15 @@ class WhaleTranslator:
|
|||
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):
|
||||
|
|
@ -482,68 +517,180 @@ 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) # 간격을 두고 다시 확인
|
||||
# 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 # 로딩 완료
|
||||
# except pyautogui.ImageNotFoundException:
|
||||
# # 아이콘이 화면에 없으면 로딩 완료로 간주
|
||||
# self.logger.debug("페이지 로딩이 완료되었습니다.")
|
||||
# return True # 로딩 완료
|
||||
|
||||
self.logger.debug("로딩 완료 대기 시간이 초과되었습니다.")
|
||||
return False
|
||||
# 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, min_width=200, min_height=150):
|
||||
"""
|
||||
현재 활성화된 웨일 창의 이름을 가져옵니다.
|
||||
제목에서 이미지의 해상도를 확인한 후, 일정 크기 이하인 경우 작업을 패스하도록 처리합니다.
|
||||
"""
|
||||
# hwnd = self.find_whale_window() # 웨일 창 핸들 가져오기
|
||||
if self.whale_hwnd:
|
||||
window_title = win32gui.GetWindowText(self.whale_hwnd)
|
||||
self.logger.debug(f"현재 웨일 창의 제목: {window_title}")
|
||||
|
||||
def move_mouse_to_center(self, hwnd):
|
||||
# 해상도를 추출하기 위해 제목에서 괄호 안의 (숫자×숫자) 부분을 찾음
|
||||
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 < min_width or height < min_height:
|
||||
self.logger.debug(f"이미지 해상도가 너무 작습니다. ({width}×{height}), 작업을 패스합니다.")
|
||||
return False # 작업을 수행하지 않음
|
||||
return True # 작업을 계속 진행
|
||||
|
||||
else:
|
||||
self.logger.error("이미지 해상도를 가져오지 못했습니다.")
|
||||
return False
|
||||
|
||||
self.logger.error("웨일 창을 찾을 수 없습니다.")
|
||||
return False
|
||||
|
||||
def move_mouse_to_center(self):
|
||||
"""웨일 브라우저 창의 중앙으로 마우스 커서를 이동"""
|
||||
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})")
|
||||
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, original_color):
|
||||
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()
|
||||
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):
|
||||
# 필터가 사라져서 색상이 변했는지 확인
|
||||
if self.is_color_changed(current_color):
|
||||
self.colors['after'] = current_color
|
||||
self.logger.debug("번역 성공 감지 (원래 색상과 유사)")
|
||||
return "success"
|
||||
|
||||
if not self.is_similar_color(current_color, original_color):
|
||||
self.logger.debug("번역 중 상태 감지 (필터 색상)")
|
||||
|
||||
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
|
||||
self.logger.debug("번역 성공으로 간주 (타임아웃)")
|
||||
return "success"
|
||||
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}")
|
||||
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):
|
||||
"""색상이 유사한지 확인 (허용 오차 적용)"""
|
||||
|
|
@ -560,7 +707,8 @@ class WhaleTranslator:
|
|||
"""번역 실패 이미지 확인"""
|
||||
for image_path in self.error_image_paths:
|
||||
try:
|
||||
if pyautogui.locateOnScreen(image_path, confidence=0.8):
|
||||
# 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:
|
||||
|
|
|
|||
Loading…
Reference in New Issue