Merge branch 'opt_img' into pyside6

This commit is contained in:
R5600U_PC 2024-10-29 14:05:47 +09:00
commit 8cf3bcdab8
9 changed files with 31716 additions and 154 deletions

File diff suppressed because one or more lines are too long

View File

@ -680,12 +680,12 @@ class BrowserController:
self.logger.error(f"이미지 URL 추출 & 옵션데이터 입력 처리 중 오류: {e}", exc_info=True)
return image_urls if image_urls else []
def paste_image_in_chrome(self, clipboardImageManager, url, is_success_translated, toggle_states, is_watermark=False, watermark_text= "", opacity_percent=25):
def paste_image_in_chrome(self, clipboardImageManager, url, is_success_translated, toggle_states, is_watermark=False, watermark_text= ""):
"""크롬으로 포커스를 옮기고 클립보드의 이미지를 붙여넣고 엔터 입력"""
self.logger.debug("크롬으로 포커스를 옮기고 클립보드의 이미지를 붙여넣고 엔터 입력")
try:
self.switch_to_chrome() # 크롬으로 포커스 이동
clipboardImageManager.process_clipboard(original_url=url, is_success_translated=is_success_translated, toggle_states=toggle_states, opacity_percent=opacity_percent) # 클립보드 내용을 처리
clipboardImageManager.process_clipboard(original_url=url, is_success_translated=is_success_translated, toggle_states=toggle_states) # 클립보드 내용을 처리
# clipboard_content = pyperclip.paste()
if clipboardImageManager.is_clipboard_image():
pyautogui.hotkey('ctrl', 'v') # 클립보드 이미지 붙여넣기

View File

@ -81,7 +81,7 @@ class ClipboardImageManager:
if image:
# 이미지를 저장 경로에 저장
self.logger.info(f"이미지 저장 완료 : {path}")
image.save(path)
image.save(path, format='PNG')
return path
except Exception as e:
@ -204,7 +204,7 @@ class ClipboardImageManager:
self.logger.debug("이미지 다운로드 최대 재시도 횟수를 초과했습니다.")
return None
def process_clipboard(self, original_url, is_success_translated, toggle_states, opacity_percent, path=None):
def process_clipboard(self, original_url, is_success_translated, toggle_states, path=None):
"""클립보드의 내용을 처리하고, 필요한 경우 이미지 변환, 크롭 또는 클립보드 비우기"""
try:
@ -214,6 +214,9 @@ class ClipboardImageManager:
watermark_text = toggle_states['watermark_text']
self.logger.debug(f"watermark_text : {watermark_text}")
opacity_percent = toggle_states['opacity_percent']
self.logger.debug(f"opacity_percent : {opacity_percent}")
clipboard_data = self.get_clipboard_data()
self.logger.debug("clipboard_data")
@ -242,7 +245,7 @@ class ClipboardImageManager:
self.set_image_to_clipboard(cropped_image) # 클립보드에 저장
if path:
self.logger.debug("이미지 저장 시도...")
self.save_image_to_path(path)
self.save_image_to_path(cropped_image, path)
else:
self.logger.debug("이미지 가로 크기 200픽셀 이하: 클립보드 비움.")
self.clear_clipboard()
@ -270,7 +273,7 @@ class ClipboardImageManager:
self.set_image_to_clipboard(cropped_image) # 클립보드에 저장
if path:
self.logger.debug("이미지 저장 시도...")
self.save_image_to_path(path)
self.save_image_to_path(cropped_image, path)
else:
self.logger.debug("이미지 가로 크기 200픽셀 이하: 클립보드 비움.")
@ -293,7 +296,7 @@ class ClipboardImageManager:
self.set_image_to_clipboard(image) # 크롭 없이 저장
if path:
self.logger.debug("이미지 저장 시도...")
self.save_image_to_path(path)
self.save_image_to_path(image, path)
else:
self.logger.debug("원본 이미지 다운로드 실패.")
else:

View File

@ -30,10 +30,27 @@ checkbox_selector_template = '#productMainContentContainerId li:nth-child({index
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'
file_input_locator = 'input[type="file"]'
# 옵션 상자
; option_box_selector = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc'
option_box_selector = '//*[@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[2]/div'
; excluded_option_marker = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) > .sc-dfauwV.bXsMpn'
excluded_option_marker = '.bXsMpn.sc-dfauwV'
; delete_button_selector = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) span:has-text("삭제")'
; delete_button_selector = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) div.sc-igZIGL.kQDmyq'
delete_button_selector = '.kQDmyq.sc-igZIGL'
confirm_delete_button_selector = '.ant-modal.css-1li46mu.ant-modal-confirm.ant-modal-confirm-confirm button:has-text("삭제")'
add_button_selector2 = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) > .sc-dRGYJT.hmQUGb'
add_button_selector = '//*[@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[2]/div/div/img'
; add_button_selector = 'div.hmQUGb.sc-dRGYJT'
; add_button_selector = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) > .sc-krITIZ.ckztYT'
; file_upload_button_selector = '.ant-modal-content button:has-text("클릭 or 드레그로 파일 업로드")'
file_upload_button_selector = 'span.ant-upload-btn input[type="file"]'
; confirm_upload_button_selector = '.ant-modal-content button:has-text("이미지 삽입")'
confirm_upload_button_selector = 'button.ant-btn-primary span:text('이미지 삽입')'
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")'

52
gui.py
View File

@ -1,4 +1,4 @@
from PySide6.QtWidgets import QInputDialog, QWidget, QPushButton, QVBoxLayout, QGridLayout, QTextEdit, QLabel, QLineEdit, QHBoxLayout, QProgressBar, QSizePolicy
from PySide6.QtWidgets import QInputDialog, QWidget, QSpinBox, QPushButton, QVBoxLayout, QGridLayout, QTextEdit, QLabel, QLineEdit, QHBoxLayout, QProgressBar, QSizePolicy
from PySide6.QtCore import Qt, QRect, QSettings, QTimer
from toggleSwitch import ToggleSwitch
from browser_control import BrowserController
@ -55,7 +55,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, watermark_font_size=36, debug=self.debug)
self.optionHandler = OptionHandler(self.locator_manager, self.browser_controller, self.whale_translator, self.logger, self.vertexAI, self.debug)
self.optionHandler = OptionHandler(self.locator_manager, self.browser_controller, self.whale_translator, self.clipboardImageManager, self.logger, self.vertexAI, 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
@ -93,7 +93,8 @@ class TranslationApp(QWidget):
'ed_mode': False, # 등록된 상품을 수정할때
'watermark': False, # 워터마크 토글 추가
'watermark_text': "WaterMark", # 워터마크 텍스트 저장
'opacity_percent': 35, # 워터마크 투명도
'opacity_percent': 25, # 워터마크 투명도
'max_option_count': 20, # 최대 선택가능한 옵션 수
}
# 이전에 저장된 설정 불러오기
@ -185,7 +186,7 @@ class TranslationApp(QWidget):
def initUI(self):
self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.setGeometry(QRect(1240, 750, 280, 600))
self.setGeometry(QRect(1240, 900, 280, 600))
self.setWindowTitle('AutoPecenty2')
# 로그
@ -332,6 +333,15 @@ class TranslationApp(QWidget):
# 확인 버튼 클릭 시 watermark_text 업데이트
self.watermark_confirm_button.clicked.connect(self.update_watermark_text)
# 최대 옵션수
self.max_option_count_label = QLabel("최대옵션수", self)
self.max_option_count_input = QSpinBox(self)
self.max_option_count_input.setMinimum(0) # 최소값 0
self.max_option_count_input.setMaximum(100) # 최대값 100
self.max_option_count_input.setValue(20) # 기본값 0
self.max_option_count_input.setToolTip("0으로 설정시 최대") # 툴팁 설정
self.max_option_count_input.valueChanged.connect(self.update_max_option_count) # 값 변경 시 update_max_option_count 메서드 호출
# 워터마크 관련 요소들을 하나의 QHBoxLayout에 추가 (비율 2:3:1)
watermark_layout = QHBoxLayout()
watermark_layout.addWidget(self.watermark_text_label, 2)
@ -340,6 +350,8 @@ class TranslationApp(QWidget):
# 필요한 레이아웃에 추가 (toggle_layout에 추가)
self.toggle_layout.addLayout(watermark_layout, 7, 0, 1, 4)
self.toggle_layout.addWidget(self.max_option_count_label, 8, 0)
self.toggle_layout.addWidget(self.max_option_count_input, 8, 1)
# 초기에는 워터마크 입력창과 버튼 숨김
self.toggle_visibility(False, [(self.watermark_text_input, self.watermark_text_label), (self.watermark_confirm_button, None)])
@ -368,7 +380,23 @@ class TranslationApp(QWidget):
# 크롬 실행 버튼 및 번역 버튼
self.start_chrome_button = QPushButton('크롬 실행', self)
self.translate_button = QPushButton('번역 시작', self)
self.translate_button.setEnabled(False)
self.translate_button.setStyleSheet("""
QPushButton:disabled {
color: gray;
background-color: lightgray;
border: 1px solid gray;
}
""")
self.pause_button = QPushButton('일시정지', self)
self.pause_button.setEnabled(False)
self.pause_button.setStyleSheet("""
QPushButton:disabled {
color: gray;
background-color: lightgray;
border: 1px solid gray;
}
""")
self.cmb_button = QPushButton('크무비설정', self)
self.cmb_test_button = QPushButton('크무비테스트', self)
@ -591,6 +619,11 @@ class TranslationApp(QWidget):
self.toggle_states['watermark_text'] = self.watermark_text_input.text()
self.logger.debug(f"Updated watermark text: {self.toggle_states['watermark_text']}")
def update_max_option_count(self):
"""QSpinBox에 입력된 값을 toggle_states['max_option_count']에 저장"""
self.toggle_states['max_option_count'] = self.max_option_count_input.value() # 정수 값 가져오기
self.logger.debug(f"최대 선택 가능 옵션 수 업데이트: {self.toggle_states['max_option_count']}")
def on_admin_toggle_clicked(self, is_checked):
"""관리자 토글 상태에 따라 관리자와 직원 필드를 표시/숨김"""
if is_checked:
@ -689,11 +722,15 @@ class TranslationApp(QWidget):
self.logger.info('신규 상품 등록 페이지로 이동 중...')
await self.browser_controller.go_to_new_product_page()
# 옵션핸들러에 초기화된 page 객체 전달.
# 핸들러에 초기화된 page 객체 전달.
self.optionHandler.update_page(self.browser_controller.page)
self.optionHandler.update_whale(self.whale_translator)
self.titleHandler.update_page(self.browser_controller.page)
self.priceHandler.update_page(self.browser_controller.page)
self.translate_button.setEnabled(True)
self.pause_button.setEnabled(True)
def save_settings(self):
"""QSettings에 사용자 정보 저장"""
self.settings.setValue("admin/id", self.admin_id_input.text())
@ -771,7 +808,7 @@ class TranslationApp(QWidget):
if total_products == 0:
self.logger.debug('수집할 상품이 없습니다. 작업을 종료합니다.')
return
self.total_progress_bar.setMaximum(total_products)
self.total_progress_bar.setValue(0)
completed_count = 0
@ -932,8 +969,7 @@ class TranslationApp(QWidget):
self.logger.debug(f"웨일 브라우저를 활용한 이미지 번역 프로세스")
is_success_translated = self.whale_translator.translate_image(url)
opacity_percent=35
is_paste_success = self.browser_controller.paste_image_in_chrome(self.clipboardImageManager, url, is_success_translated, self.toggle_states, opacity_percent=opacity_percent)
is_paste_success = self.browser_controller.paste_image_in_chrome(self.clipboardImageManager, url, is_success_translated, self.toggle_states)
if is_paste_success:
self.logger.debug(f"{url} gui 이미지 붙여넣기 성공")
else:

View File

@ -88,10 +88,13 @@ class LocatorManager:
'checkbox_selector_template': self.config.get('OptionLocators', 'checkbox_selector_template').strip("'"),
'image_selector_template': self.config.get('OptionLocators', 'image_selector_template').strip("'"),
'price_selector_template': self.config.get('OptionLocators', 'price_selector_template').strip("'"),
'delete_button_selector_template': self.config.get('OptionLocators', 'delete_button_selector_template').strip("'"),
'confirm_delete_button_locator': self.config.get('OptionLocators', 'confirm_delete_button_locator').strip("'"),
'add_button_selector_template': self.config.get('OptionLocators', 'add_button_selector_template').strip("'"),
'file_input_locator': self.config.get('OptionLocators', 'file_input_locator').strip("'"),
'option_box_selector': self.config.get('OptionLocators', 'option_box_selector').strip("'"),
'excluded_option_marker': self.config.get('OptionLocators', 'excluded_option_marker').strip("'"),
'delete_button_selector': self.config.get('OptionLocators', 'delete_button_selector').strip("'"),
'confirm_delete_button_selector': self.config.get('OptionLocators', 'confirm_delete_button_selector').strip("'"),
'add_button_selector': self.config.get('OptionLocators', 'add_button_selector').strip("'"),
'file_upload_button_selector': self.config.get('OptionLocators', 'file_upload_button_selector').strip("'"),
'confirm_upload_button_selector': self.config.get('OptionLocators', 'confirm_upload_button_selector').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("'"),

310
option.py
View File

@ -3,12 +3,14 @@ import pyautogui
from datetime import datetime
import numpy as np
import asyncio, time, math
import tempfile, os
class OptionHandler:
def __init__(self, locator_manager, browser_controller, whale_translator, logger, vertexAI, debug_flag=False):
def __init__(self, locator_manager, browser_controller, whale_translator, clipboardImageManager, logger, vertexAI, debug_flag=False):
self.locator_manager = locator_manager
self.browser_controller = browser_controller
self.page = self.browser_controller.page
self.clipboardImageManager = clipboardImageManager
self.logger = logger
self.debug_flag = debug_flag
self.vertexAItranslator = vertexAI
@ -30,10 +32,15 @@ class OptionHandler:
self.checkbox_selector_template = self.locator_manager.get_locator('OptionLocators', 'checkbox_selector_template')
self.image_selector_template = self.locator_manager.get_locator('OptionLocators', 'image_selector_template')
self.price_selector_template = self.locator_manager.get_locator('OptionLocators', 'price_selector_template')
self.delete_button_selector_template = self.locator_manager.get_locator('OptionLocators', 'delete_button_selector_template')
self.confirm_delete_button_locator = self.locator_manager.get_locator('OptionLocators', 'confirm_delete_button_locator')
self.add_button_selector_template = self.locator_manager.get_locator('OptionLocators', 'add_button_selector_template')
self.file_input_locator = self.locator_manager.get_locator('OptionLocators', 'file_input_locator')
self.option_box_selector = self.locator_manager.get_locator('OptionLocators', 'option_box_selector')
self.excluded_option_marker = self.locator_manager.get_locator('OptionLocators', 'excluded_option_marker')
self.delete_button_selector = self.locator_manager.get_locator('OptionLocators', 'delete_button_selector')
self.confirm_delete_button_selector = self.locator_manager.get_locator('OptionLocators', 'confirm_delete_button_selector')
self.add_button_selector = self.locator_manager.get_locator('OptionLocators', 'add_button_selector')
self.file_upload_button_selector = self.locator_manager.get_locator('OptionLocators', 'file_upload_button_selector')
self.confirm_upload_button_selector = self.locator_manager.get_locator('OptionLocators', 'confirm_upload_button_selector')
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')
@ -41,6 +48,9 @@ class OptionHandler:
def update_page(self, page1):
self.page = page1
self.logger.debug(f"page객체 업데이트 : {page1}")
def update_whale(self, whale1):
self.whale_translator = whale1
self.logger.debug(f"whale_translator 객체 업데이트 : {whale1}")
def init_option_info(self):
self.option_info = {
@ -197,58 +207,62 @@ class OptionHandler:
return self.option_info
# 3. 가격 낮은 순 정렬 클릭
await self.low_order_click()
# 4. 옵션 정보 수집 및 번역
# await self.low_order_click()
try:
if toggle_states['optionTrnas']:
self.logger.debug(f"옵션 AI번역 : {toggle_states['optionTrnas']}")
self.option_info = await self.collect_options_info()
self.logger.info(f"옵션 정보 수집")
self.option_info = await self.collect_options_info()
self.logger.debug(f"수집된 옵션 정보 : {self.option_info}")
except Exception as e:
# 옵션 처리 중 오류 발생 시 전체 로그 출력
self.logger.error(f"옵션 정보 수집 중 오류 발생: {e}", exc_info=True)
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'])
# 4. 옵션 정보 수집 및 번역
self.is_vertext_success = True # 번역 성공
if toggle_states['optionTrnas']:
self.logger.debug(f"옵션 AI번역 : {toggle_states['optionTrnas']}")
except ValueError as ve:
# 안전 필터 예외 처리
if "SAFETY" in str(ve):
self.logger.error(f"안전 필터에 의해 번역 요청이 차단되었습니다. {ve}")
self.logger.debug("퍼센티 자체 AI번역 사용 시도")
await self.page.click(self.ai_option_btn_selector)
self.logger.debug("번역을 위한 5초간 대기")
await asyncio.sleep(5)
self.is_vertext_success = False
self.is_percenty_success = True
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'])
# except google.api_core.exceptions.ResourceExhausted as re:
# # 할당량 초과 예외 처리
# self.logger.error(f"Vertex AI 할당량 초과: {re}")
# self.logger.debug("퍼센티 자체 AI번역 사용 시도")
# pyautogui.hotkey('alt', 'q')
# self.logger.debug("번역을 위한 5초간 대기")
# time.sleep(5)
# translation_success = False # 번역 실패
self.is_vertext_success = True # 번역 성공
except Exception as e:
# 기타 예외 처리
self.logger.error(f"번역 처리 중 알 수 없는 오류 발생: {e}", exc_info=True)
except ValueError as ve:
# 안전 필터 예외 처리
if "SAFETY" in str(ve):
self.logger.error(f"안전 필터에 의해 번역 요청이 차단되었습니다. {ve}")
self.logger.debug("퍼센티 자체 AI번역 사용 시도")
await self.page.click(self.ai_option_btn_selector)
self.logger.debug("번역을 위한 5초간 대기")
await asyncio.sleep(5)
self.is_vertext_success = False # 번역 실패
self.is_vertext_success = False
self.is_percenty_success = True
# 번역 성공 여부에 따른 로그
self.logger.debug(f"[{'VertexAI' if self.is_vertext_success else '퍼센티AI'}] 를 이용한 옵션번역 성공")
# except google.api_core.exceptions.ResourceExhausted as re:
# # 할당량 초과 예외 처리
# self.logger.error(f"Vertex AI 할당량 초과: {re}")
# self.logger.debug("퍼센티 자체 AI번역 사용 시도")
# pyautogui.hotkey('alt', 'q')
# self.logger.debug("번역을 위한 5초간 대기")
# time.sleep(5)
# translation_success = False # 번역 실패
except Exception as e:
# 기타 예외 처리
self.logger.error(f"번역 처리 중 알 수 없는 오류 발생: {e}", exc_info=True)
self.logger.debug("퍼센티 자체 AI번역 사용 시도")
await self.page.click(self.ai_option_btn_selector)
self.logger.debug("번역을 위한 5초간 대기")
await asyncio.sleep(5)
self.is_vertext_success = False # 번역 실패
self.is_percenty_success = True
# 번역 성공 여부에 따른 로그
self.logger.debug(f"[{'VertexAI' if self.is_vertext_success else '퍼센티AI'}] 를 이용한 옵션번역 성공")
except Exception as e:
# 옵션 처리 중 오류 발생 시 전체 로그 출력
self.logger.error(f"옵션 처리 중 오류 발생: {e}", exc_info=True)
# 5. 옵션 필터링 및 조정
if toggle_states['optionAutoSelect']:
@ -261,11 +275,9 @@ class OptionHandler:
await self.store_selected_options() # 페이지에서 실제 선택된 옵션을 수집하여 저장
# 7. 옵션 이미지 업데이트 (옵션 이미지가 있는 경우)
if toggle_states['optionIMGTrans']:
self.logger.debug(f"옵션 이미지번역(옵션 이미지가 있는 경우만) : {toggle_states['optionIMGTrans']}")
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_flag)
if toggle_states.get('optionIMGTrans'):
self.logger.debug(f"옵션 이미지 번역을 시작합니다.")
await self.update_option_image(toggle_states, debug_flag=self.debug_flag)
# 8. A-Z or 1-99 button 클릭
@ -608,8 +620,44 @@ class OptionHandler:
except Exception as e:
self.logger.error(f"옵션 필터링 및 조정 중 오류 발생: {e}", exc_info=True)
async def adjust_options(self, filtered_option_names, max_option_count):
"""
필터링된 옵션에 맞게 체크박스 상태를 조정하는 메서드.
:param filtered_option_names: 필터링된 옵션 리스트
:param max_option_count: 최대 선택 가능한 옵션 (0이면 제한 없음)
"""
try:
selected_count = 0 # 현재까지 선택된 옵션 수
for i, name in enumerate(self.option_info['original_names'].values()):
# 최대 옵션 수에 도달하면 더 이상 선택하지 않음
if max_option_count > 0 and selected_count >= max_option_count:
break
checkbox_selector = self.checkbox_selector_template.format(index=i+1)
checkbox_element = await self.page.query_selector(checkbox_selector)
# 디버깅 로그: 현재 옵션 이름과 필터링된 옵션 이름 확인
self.logger.debug(f"옵션 이름: {name}, 필터링된 옵션에 포함 여부: {name in filtered_option_names}")
if checkbox_element:
# 필터링된 옵션에 포함되고, 선택 가능한 수량 내라면 선택
if name in filtered_option_names and (max_option_count == 0 or selected_count < max_option_count):
await checkbox_element.click()
self.logger.debug(f"옵션 '{name}' 체크함")
self.option_info['checked_states'][name] = True
selected_count += 1
# 필터링된 옵션에 포함되지 않으면 선택 해제
else:
await checkbox_element.click()
self.logger.debug(f"옵션 '{name}' 체크 해제함")
self.option_info['checked_states'][name] = False
self.logger.debug(f"옵션 체크 상태 조정 완료. 선택된 옵션 수: {selected_count}/{max_option_count if max_option_count > 0 else '무제한'}")
except Exception as e:
self.logger.error(f"옵션 체크 상태 조정 중 오류 발생: {e}", exc_info=True)
async def adjust_options_without_max_count(self, filtered_option_names, max_option_count):
"""
필터링된 옵션에 맞게 체크박스 상태를 조정하는 메서드.
:param filtered_options: 필터링된 옵션 리스트
@ -654,92 +702,110 @@ class OptionHandler:
async def low_order_click(self):
self.logger.debug("가격 낮은 순 정렬을 클릭합니다.")
await self.page.click(self.low_order_button_locator)
async def update_option_image(self, index, option_image_url, product_name, option_name, debug_flag=False):
async def update_option_image(self, toggle_states, debug_flag=False):
"""
옵션 이미지가 존재할 경우, 기존 이미지를 삭제하고 번역된 이미지를 추가하는 메서드.
옵션 이미지가 존재할 경우, 제외된 옵션이 아닌 경우 번역하여 업데이트하는 메서드.
:param index: 옵션 인덱스.
:param option_image_url: 옵션 이미지 URL.
:param product_name: 상품명.
:param option_name: 옵션명.
:param debug: 디버그 모드일 경우 이미지를 삭제하지 않음 (기본값 False).
:param debug_flag: 디버그 모드일 경우 임시 이미지를 삭제하지 않음 (기본값 False).
"""
# main.py 실행 폴더의 tmp_images 폴더 경로 설정
base_dir = os.path.dirname(os.path.abspath(__file__)) # main.py 위치 확인
temp_dir = os.path.join(base_dir, "tmp_images") # tmp_images 폴더 경로 설정
os.makedirs(temp_dir, exist_ok=True) # 폴더가 없으면 생성
try:
# 이미지가 없을 경우 메서드 종료
if not option_image_url:
self.logger.debug(f"{index}번째 옵션의 이미지가 존재하지 않아 작업을 종료합니다.")
return
# 모든 옵션 상자 요소 가져오기
# option_boxes = await self.page.query_selector_all(self.option_box_selector)
self.logger.debug(f"{index}번째 옵션의 이미지를 업데이트합니다.")
option_boxes = await self.page.query_selector_all("div#productMainContentContainerId li > div > div:nth-child(1) > div > div:nth-child(2) > div")
delete_button_selector = self.delete_button_selector_template.format(index=index)
confirm_delete_button_locator = self.confirm_delete_button_locator
add_button_selector = self.add_button_selector_template.format(index=index)
file_input_locator = self.file_input_locator
# 기존 이미지 삭제 (삭제 버튼이 존재할 경우)
delete_button = await self.page.query_selector(delete_button_selector)
# option_image_element = await self.page.locator("xpath=//*[@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[1]/div/div[1]/div/div[2]/div/img").element_handle()
if delete_button:
self.logger.debug(f"{index}번째 옵션의 기존 이미지를 삭제합니다.")
await delete_button.click()
total_options = len(option_boxes)
self.logger.debug(f"{total_options}개의 옵션 이미지 번역을 시작합니다.")
# 삭제 확인 버튼 클릭
confirm_delete_button = await self.page.query_selector(confirm_delete_button_locator)
if confirm_delete_button:
self.logger.debug(f"삭제 확인버튼 클릭")
await confirm_delete_button.click()
self.logger.debug(f"{index}번째 옵션의 이미지가 삭제되었습니다.")
else:
self.logger.debug(f"삭제 확인 버튼을 찾을 수 없습니다.")
# 실제 옵션 이미지가 존재하는 항목에만 인덱스를 적용하기 위해 별도 카운터 사용
translated_index = 1
# 각 옵션 상자를 순회
for index, option_box in enumerate(option_boxes, start=1):
# 선택자에서 인덱스를 반영해 동적 선택자를 생성
delete_button_selector = self.delete_button_selector.format(index=index)
add_button_selector = self.add_button_selector.format(index=index)
# 이미지 번역 후 추가
# 옵션 이미지가 존재하는지 확인
option_image = await option_box.query_selector("img")
if option_image is None:
self.logger.debug(f"{index}번째 옵션에 이미지가 없습니다. 다음 옵션으로 이동합니다.")
continue
option_image_url = await option_image.get_attribute("src")
self.logger.debug(f"{index}번째 옵션 이미지 URL: {option_image_url}")
# 디렉토리가 존재하지 않으면 생성
self.logger.debug("이미지 저장 경로 설정")
translated_image_path = f"tmp_image/{product_name}-{option_name}-{index}.png"
if not os.path.exists('tmp_image'):
os.makedirs('tmp_image')
self.logger.debug("이미지 임시저장폴더가 존재하지 않아 생성.")
self.logger.debug(f"옵션 이미지 번역")
await self.whale_translator.translate_image(option_image_url)
self.clipboardImageManager.process_clipboard(option_image_url, translated_image_path)
# 이미지가 SVG 형식일 경우 번역을 건너뜀
if option_image_url.endswith(".svg"):
self.logger.debug(f"{index}번째 옵션은 SVG 이미지입니다. 번역을 생략합니다.")
continue
# 이미지 업로드 버튼 클릭 (옵션 이미지가 없는 경우)
add_button = await self.page.query_selector(add_button_selector)
# 이미지 번역 및 업로드
translated_image_path = os.path.join(temp_dir, f"translated_option_{translated_index}.png") # 이미지 저장 경로 설정
if add_button:
await add_button.click()
try:
# 이미지 번역
self.logger.debug(f"{index}번째 옵션의 이미지 번역 시도")
is_success_translated = self.whale_translator.translate_image(option_image_url)
self.clipboardImageManager.process_clipboard(option_image_url, is_success_translated, toggle_states, translated_image_path)
self.logger.debug(f"{index}번째 옵션의 번역 이미지 저장 완료: {translated_image_path}")
self.browser_controller.switch_to_chrome() # 크롬으로 포커스 이동
# 파일 선택 다이얼로그에서 번역된 이미지 파일 입력
file_input = await self.page.wait_for_selector(file_input_locator, timeout=5000)
await file_input.set_input_files(translated_image_path)
self.logger.debug(f"{index}번째 옵션에 번역된 이미지가 추가되었습니다.")
if is_success_translated and os.path.exists(translated_image_path):
# 삭제 버튼 클릭
# delete_button = await self.page.query_selector(delete_button_selector)
delete_button = await option_box.query_selector(".sc-igZIGL.kQDmyq")
# 디버그 모드가 아닐 경우, 성공적으로 업로드 후 임시 파일 삭제
if not debug_flag:
import os
if os.path.exists(translated_image_path):
self.logger.debug(f"{index}번째 옵션의 이미지 삭제 버튼 가져오기")
if delete_button:
await delete_button.click()
self.logger.debug(f"{index}번째 옵션의 이미지 삭제 버튼 클릭")
confirm_delete_button = await self.page.wait_for_selector(self.confirm_delete_button_selector)
self.logger.debug(f"{index}번째 옵션의 이미지 삭제확인 버튼 가져오기")
if confirm_delete_button:
await confirm_delete_button.click()
self.logger.debug(f"{index}번째 옵션의 기존 이미지가 삭제되었습니다.")
# '+ 버튼' 클릭 후 파일 업로드
self.logger.debug(f"{index}번째 옵션의 이미지추가 버튼 가져오기")
add_button = self.page.locator(add_button_selector)
if add_button:
await add_button.click()
self.logger.debug(f"{index}번째 옵션의 이미지추가 버튼 클릭")
# 파일 업로드 영역의 input 요소 직접 선택 (수정된 부분)
file_input = await self.page.query_selector(self.file_upload_button_selector) # Ant Design의 클래스 사용
if file_input:
# Playwright의 set_input_files를 사용하여 파일 업로드 처리
await file_input.set_input_files(translated_image_path)
self.logger.debug(f"{index}번째 옵션의 파일 업로드 완료")
# '이미지 삽입' 버튼 클릭
confirm_upload_button = await self.page.wait_for_selector(self.confirm_upload_button_selector)
await confirm_upload_button.click()
self.logger.debug(f"{index}번째 옵션에 이미지가 업로드되었습니다.")
else:
self.logger.error(f"{index}번째 옵션의 파일 입력 요소를 찾을 수 없습니다.")
finally:
# 파일 사용 후 0.5초 대기하여 접근 완료 보장
time.sleep(0.5)
# 디버그 모드가 아닐 경우 임시 파일 삭제
if not debug_flag and os.path.exists(translated_image_path):
os.remove(translated_image_path)
self.logger.debug(f"번역된 이미지 파일이 삭제되었습니다: {translated_image_path}")
self.logger.debug(f"{index}번째 옵션의 임시 번역 이미지 파일 삭제 완료: {translated_image_path}")
# 실제 번역이 완료된 경우에만 인덱스 증가
translated_index += 1
except Exception as e:
self.logger.error(f"{index}번째 옵션 이미지 업데이트 중 오류 발생: {e}", exc_info=True)
self.logger.debug(f"원본이미지를 다시 넣습니다")
# 이미지 번역이 실패하면 원본 이미지를 다시 다운로드하여 PIL 이미지로 변환
original_image = await self.download_image_from_url(option_image_url)
if original_image:
# original_image를 로컬에 저장
original_image_path = f"tmp_image/{product_name}-original-{index}.png"
original_image.save(original_image_path)
# 저장된 원본 이미지를 다시 업로드
file_input = await self.page.wait_for_selector(file_input_locator, timeout=5000)
await file_input.set_input_files(original_image_path)
self.logger.debug(f"{index}번째 옵션에 원본 이미지가 업로드되었습니다.")
else:
self.logger.error(f"원본 이미지를 다운로드하는 데 실패했습니다: {option_image_url}")
self.logger.error(f"옵션 이미지 업데이트 중 오류 발생: {e}", exc_info=True)

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@ -82,12 +82,12 @@ class WhaleTranslator:
self.update_whale_rect()
# 주소창으로 이동 후 URL 입력
pyautogui.hotkey('ctrl', 'l')
time.sleep(0.4)
pyautogui.typewrite('https://daum.net')
self.change_lang()
self.enter_url("about:newtab", change=True)
self.logger.debug("URL 입력 완료")
# pyautogui.hotkey('ctrl', 'l')
# time.sleep(0.4)
# pyautogui.typewrite('https://daum.net')
# self.change_lang()
# self.enter_url("about:newtab", change=True)
# self.logger.debug("URL 입력 완료")
# 가상 데스크탑 복귀 처리
if self.vd_mode:
@ -280,7 +280,7 @@ class WhaleTranslator:
self.logger.debug(f"이미지 URL 주소 {url} 입력")
self.enter_url_for_clipboard(url)
pyautogui.press('enter')
time.sleep(2) # 페이지 로딩 대기
time.sleep(1) # 페이지 로딩 대기
# 현재 웨일 창의 해상도를 확인하여 기준 이하일 경우 패스
min_width = 200
@ -354,8 +354,18 @@ class WhaleTranslator:
# 경로를 인자로 받을경우 해당경로에 파일 저장
if path:
pass # 클립보드의 이미지를 path의 파일로 저장하고 저장경로를 리턴하는 메서드
# path에는 현재 폴더의 tmp_img폴더에 상품명-옵션명 형태로 제공됨
try:
# 클립보드에서 이미지 가져오기
clipboard_image = ImageGrab.grabclipboard()
if clipboard_image:
clipboard_image.save(path, format='PNG')
self.logger.info(f"번역된 이미지가 {path}에 저장되었습니다.")
else:
self.logger.error("클립보드에 이미지가 존재하지 않아 파일로 저장할 수 없습니다.")
return False
except Exception as e:
self.logger.error(f"이미지 저장 중 오류 발생: {e}", exc_info=True)
return False
return True if self.translation_success_flag else False