diff --git a/browser_control.py b/browser_control.py index 2426cccf..144ee1c3 100644 --- a/browser_control.py +++ b/browser_control.py @@ -267,31 +267,19 @@ class BrowserController: def fetch_image_urls(self, html_content): """ - HTML 콘텐츠에서 모든 태그의 URL을 순서대로 추출하고 중복 제거. + HTML 콘텐츠에서 모든 태그의 URL을 HTML 소스의 순서대로 추출. """ soup = BeautifulSoup(html_content, 'html.parser') - # 중복된 이미지를 제거하기 위해 set 사용 - image_urls_set = set() - - # class="image_resized"를 가진 모든 태그 찾기 - images_resized = soup.find_all('img', class_='image_resized') - for img in images_resized: + # HTML 소스에서 순서대로 태그를 찾음 + image_urls = [] + img_tags = soup.find_all('img') + for img in img_tags: if img and 'src' in img.attrs: - image_urls_set.add(img['src']) # 중복을 방지하기 위해 set에 추가 + image_urls.append(img['src']) # URL을 리스트에 추가 - #
내부의 모든 태그 찾기 - 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: - image_urls_set.add(img_tag['src']) # 중복을 방지하기 위해 set에 추가 - - # set을 list로 변환하여 반환 (순서 유지가 필요하면 set 대신 리스트로 처리해야 함) - image_urls = list(image_urls_set) return image_urls - async def close_ad_if_exists(self): """광고 다이얼로그가 있으면 닫기 버튼을 클릭하는 메서드""" @@ -521,7 +509,8 @@ class BrowserController: # 나머지 옵션들은 - 없이 숫자 접두사로 표시 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 = f"{i}. " + option_prefix = "" await input_field.type(option_prefix + option_text) await input_field.press('Enter') # 엔터 키를 입력하여 줄바꿈 @@ -545,12 +534,12 @@ class BrowserController: self.logger.debug(f"이미지 URL 추출 중 오류: {e}", exc_info=True) return [] - async def paste_image_in_chrome(self, clipboardImageManager, url): + def paste_image_in_chrome(self, clipboardImageManager, url): """크롬으로 포커스를 옮기고 클립보드의 이미지를 붙여넣고 엔터 입력""" self.logger.debug("크롬으로 포커스를 옮기고 클립보드의 이미지를 붙여넣고 엔터 입력") try: self.switch_to_chrome() # 크롬으로 포커스 이동 - await clipboardImageManager.process_clipboard(url) # 클립보드 내용을 처리 + clipboardImageManager.process_clipboard(url) # 클립보드 내용을 처리 # clipboard_content = pyperclip.paste() if clipboardImageManager.is_clipboard_image(): pyautogui.hotkey('ctrl', 'v') # 클립보드 이미지 붙여넣기 diff --git a/clipboardImageManager.py b/clipboardImageManager.py index 2e55b92a..15fbced1 100644 --- a/clipboardImageManager.py +++ b/clipboardImageManager.py @@ -179,16 +179,19 @@ class ClipboardImageManager: else: self.logger.debug(f"이미지 로딩 실패, HTTP 상태 코드: {response.status_code}. 재시도 {retries + 1}/{max_retries}") retries += 1 - await asyncio.sleep(random.randint(2, 5)) # 2~5초 대기 후 재시도 + # await asyncio.sleep(random.randint(2, 5)) # 2~5초 대기 후 재시도 + time.sleep(random.randint(2, 5)) + except Exception as e: self.logger.debug(f"이미지 로딩 중 오류 발생: {e}. 재시도 {retries + 1}/{max_retries}") retries += 1 - await asyncio.sleep(random.randint(2, 5)) # 예외 발생 시 대기 후 재시도 + # await asyncio.sleep(random.randint(2, 5)) # 예외 발생 시 대기 후 재시도 + time.sleep(random.randint(2, 5)) self.logger.debug("이미지 다운로드 최대 재시도 횟수를 초과했습니다.") return None - async def process_clipboard(self, original_url, path=None): + def process_clipboard(self, original_url, path=None): """클립보드의 내용을 처리하고, 필요한 경우 이미지 변환, 크롭 또는 클립보드 비우기""" try: @@ -248,7 +251,7 @@ class ClipboardImageManager: self.logger.info("[process_clipboard] 클립보드에 이미지 없음") if original_url: - image = await self.download_image_from_url(original_url) + image = self.download_image_from_url(original_url) if image: self.logger.debug("원본 이미지 다운로드 성공, 클립보드에 저장 중...") self.set_image_to_clipboard(image) # 크롭 없이 저장 diff --git a/gui.py b/gui.py index 036dbdc2..5093d036 100644 --- a/gui.py +++ b/gui.py @@ -846,7 +846,7 @@ class TranslationApp(QWidget): self.whale_translator.translate_image(url) self.logger.debug(f"paste_image_in_chrome - 이미지 붙여넣기") - await self.browser_controller.paste_image_in_chrome(self.clipboardImageManager, url) + self.browser_controller.paste_image_in_chrome(self.clipboardImageManager, url) self.logger.debug(f"Progress Update") self.update_detail_progress(i,total_images) diff --git a/price.py b/price.py index 3c6d3780..046463b2 100644 --- a/price.py +++ b/price.py @@ -27,6 +27,7 @@ class PriceHandler: self.first_delv_fee = 0 self.exchange_fee = 0 self.sold_price = 0 + self.initail_margin_cost = 0 # Locator들을 미리 로드하여 초기화 - locator_template:동적 로딩, locator:고정로딩 self.product_cost_locator_template = self.locator_manager.get_locator('PriceLocators', 'product_cost_locator') @@ -331,6 +332,10 @@ class PriceHandler: - adjusted_margin (int): 적정 판매가에 맞게 조정된 더하기 마진. """ try: + if not option_data or len(option_data) == 0: + self.logger.warning(f"옵션 데이터가 없거나 비어있습니다. 계산원가 기준 더하기마진({self.initail_margin_cost}) 을 반환합니다.") + return self.initail_margin_cost # 기본 더하기 마진 설정 + total_option_price = math.ceil(sum([option['price'] for option in option_data]) / len(option_data)) self.logger.debug(f"총 옵션 기준 판매가 평균: {total_option_price}") @@ -503,11 +508,11 @@ class PriceHandler: initail_shipping_cost = self.round_to_UP(initail_shipping_cost) self.logger.debug(f"계산원가 기준 해외배송비: {initail_shipping_cost}") - initail_margin_cost = self.calculate_shipping_cost_with_extended_thresholds(10000, initial_cost_price) - initail_margin_cost = self.round_to_UP(initail_margin_cost) - self.logger.debug(f"계산원가 기준 더하기마진: {initail_margin_cost}") + self.initail_margin_cost = self.calculate_shipping_cost_with_extended_thresholds(10000, initial_cost_price) + self.initail_margin_cost = self.round_to_UP(self.initail_margin_cost) + self.logger.debug(f"계산원가 기준 더하기마진: {self.initail_margin_cost}") - result = int(initial_cost_price + initail_shipping_cost + initail_margin_cost) + result = int(initial_cost_price + initail_shipping_cost + self.initail_margin_cost) self.logger.debug(f"원가기반 가격: {result}") return result diff --git a/src/img/translating.png b/src/img/translating.png new file mode 100644 index 00000000..a24d4fbe Binary files /dev/null and b/src/img/translating.png differ diff --git a/test/img_test.py b/test/img_test.py index 7d574fbb..6673c3a5 100644 --- a/test/img_test.py +++ b/test/img_test.py @@ -3,12 +3,17 @@ 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" @@ -16,6 +21,7 @@ class WhaleTranslator: 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" @@ -26,8 +32,9 @@ class WhaleTranslator: hwnd = self.find_whale_window() if hwnd: - self.set_window_position(hwnd, 100, 100, 1280, 720) # 위치 (100, 100), 크기 (1280x720) - + 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') @@ -36,10 +43,10 @@ class WhaleTranslator: pyautogui.press('enter') time.sleep(2) - # 페이지 로딩 완료 대기 - self.wait_for_loading_icon_to_disappear() + # # 페이지 로딩 완료 대기 + # self.wait_for_loading_icon_to_disappear() - self.move_mouse_to_center(hwnd) # 마우스 중앙으로 이동 + self.move_mouse_to_center() # 마우스 중앙으로 이동 time.sleep(0.5) # 마우스 이동 후 대기 original_color = self.get_mouse_position_color() # 현재 색상 가져오기 @@ -50,21 +57,24 @@ class WhaleTranslator: 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("번역 상태를 확인하지 못했습니다.") + # 번역 시작 감지 (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"번역 전 색상: {self.colors['before']}") - self.logger.debug(f"번역 중 색상: {self.colors['during']}") - self.logger.debug(f"번역 후 색상: {self.colors['after']}") + 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): """ @@ -75,19 +85,65 @@ class WhaleTranslator: while time.time() - start_time < max_wait: try: # 화면에서 아이콘 위치 확인 시도 - icon_location = pyautogui.locateOnScreen(self.page_loading_icon_path, confidence=0.9) + icon_location = self.find_image_with_confidence(self.page_loading_icon_path, confidence=0.9) if icon_location: - print("페이지 로딩 중...") + self.logger.debug("페이지 로딩 중...") time.sleep(0.5) # 간격을 두고 다시 확인 - except pyautogui.ImageNotFoundException: + except pyautogui.ImageNotFoundException as e: # 아이콘이 화면에 없으면 로딩 완료로 간주 - print("페이지 로딩이 완료되었습니다.") + # self.logger.debug(f"페이지 로딩이 완료되었습니다. {e}", exc_info=True) + self.logger.debug(f"페이지 로딩이 완료되었습니다.") return True # 로딩 완료 - print("로딩 완료 대기 시간이 초과되었습니다.") + 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"이미지를 찾지 못했습니다만 유사한 부분을 찾았습니다. : [{e.args[0]}]") + else: + self.logger.error("이미지를 찾지 못했습니다. 이미지와 유사한 부분을 찾지 못했습니다.") + 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.8) + 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 find_whale_window(self): """웨일 창을 제목을 기준으로 찾는 메서드""" def callback(hwnd, extra): @@ -99,8 +155,10 @@ class WhaleTranslator: hwnd_list = [] win32gui.EnumWindows(callback, hwnd_list) if hwnd_list: - self.logger.debug(f"웨일 창을 찾았습니다: {hwnd_list[0]}") - return hwnd_list[0] + 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 @@ -111,40 +169,60 @@ class WhaleTranslator: 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): + 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 # 세로 중앙 계산 + 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 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() - 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.8) + 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.8) + if result: + self.logger.debug("번역 성공으로 간주 (타임아웃 후 translating.png 존재)") + return "success" + else: + self.logger.debug("번역 실패로 간주 (타임아웃 후 translating.png 없음)") + return "error" def is_similar_color(self, color1, color2): """색상이 유사한지 확인 (허용 오차 적용)""" @@ -160,12 +238,10 @@ class WhaleTranslator: 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)}") + 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 # 예제 사용법 @@ -179,4 +255,7 @@ 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") +# 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') + diff --git a/test/page_loading_2.png b/test/page_loading_2.png new file mode 100644 index 00000000..0bcca4b1 Binary files /dev/null and b/test/page_loading_2.png differ diff --git a/test/translating.png b/test/translating.png new file mode 100644 index 00000000..f3f7aa8c Binary files /dev/null and b/test/translating.png differ diff --git a/whale_translator.py b/whale_translator.py index 40be90eb..4ca454dc 100644 --- a/whale_translator.py +++ b/whale_translator.py @@ -32,7 +32,7 @@ class WhaleTranslator: self.page_loading_icon_path = os.path.join(img_dir, 'page_loading.png') self.translation_success_flag = False # 번역 성공 플래그 - # self.failure_count = 0 # 실패 횟수 + self.failure_count = 0 # 실패 횟수 if isSecret: self.whale_window_name = "새 시크릿 탭 - Whale" @@ -89,10 +89,10 @@ 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): # """번역 실패 시 처리"""