Merge branch 'opt_img' into pyside6
This commit is contained in:
commit
8cf3bcdab8
31427
appTranslator.log
31427
appTranslator.log
File diff suppressed because one or more lines are too long
|
|
@ -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') # 클립보드 이미지 붙여넣기
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
25
config.ini
25
config.ini
|
|
@ -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
52
gui.py
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
310
option.py
|
|
@ -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 |
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue