diff --git a/browser_control.py b/browser_control.py index 84afe983..8630117f 100644 --- a/browser_control.py +++ b/browser_control.py @@ -2901,96 +2901,96 @@ class BrowserController(QThread): self.logger.log(f"메모 입력 중 오류 발생: {e}", level=logging.ERROR, exc_info=True) # ESC 키 2번 전송하여 팝업 제거 시도 - self.logger.log("ESC 키를 2번 전송하여 팝업 제거 후 메모 입력 재시도합니다.", level=logging.WARNING) + # self.logger.log("ESC 키를 2번 전송하여 팝업 제거 후 메모 입력 재시도합니다.", level=logging.WARNING) await self.page.keyboard.press("Escape") await self.page.keyboard.press("Escape") await asyncio.sleep(0.5) # 잠시 대기 - # 메모 입력 재시도 - try: - await memo_button.click() - self.logger.log("재시도: 메모 버튼을 클릭했습니다.", level=logging.DEBUG) - memo_input = self.page.locator(self.memo_input_locator) - await memo_input.wait_for(state="visible") - self.logger.log("재시도: 메모 입력란 대기 완료", level=logging.DEBUG) + # # 메모 입력 재시도 + # try: + # await memo_button.click() + # self.logger.log("재시도: 메모 버튼을 클릭했습니다.", level=logging.DEBUG) + # memo_input = self.page.locator(self.memo_input_locator) + # await memo_input.wait_for(state="visible") + # self.logger.log("재시도: 메모 입력란 대기 완료", level=logging.DEBUG) - # 5. 기존 메모 읽어오기 - existing = await memo_input.input_value() - combined = existing.strip() + # # 5. 기존 메모 읽어오기 + # existing = await memo_input.input_value() + # combined = existing.strip() - # 6. 새 메모 조합 - suffix = "\n" + "-"*10 + "\n" + f"수집방법:[{collect_method}]\n" - if combined: - if is_new_memo_first: - combined = new_memo + suffix + combined - else: - combined = combined + suffix + new_memo - else: - combined = f"수집방법:[{collect_method}]\n" + new_memo + # # 6. 새 메모 조합 + # suffix = "\n" + "-"*10 + "\n" + f"수집방법:[{collect_method}]\n" + # if combined: + # if is_new_memo_first: + # combined = new_memo + suffix + combined + # else: + # combined = combined + suffix + new_memo + # else: + # combined = f"수집방법:[{collect_method}]\n" + new_memo - # 7. 길이 제한 (2000자) - if len(combined) > 2000: - combined = combined[:2000] + # # 7. 길이 제한 (2000자) + # if len(combined) > 2000: + # combined = combined[:2000] - # 8. 채우기 - await memo_input.fill(combined) - self.logger.log(f"재시도: 메모 입력: {combined!r}", level=logging.DEBUG) + # # 8. 채우기 + # await memo_input.fill(combined) + # self.logger.log(f"재시도: 메모 입력: {combined!r}", level=logging.DEBUG) - # 9. 노출 체크박스 처리 - # chk = self.page.locator( - # 'label:has-text("상품 목록에 메모 내용 노출하기") ' - # 'input[type="checkbox"]' - # ) - # await chk.wait_for(state="attached") - # checked = await chk.is_checked() - # if is_memo_exposure and not checked: - # await chk.click() - # self.logger.log("재시도: 메모 노출 체크 ON", level=logging.DEBUG) - # elif not is_memo_exposure and checked: - # await chk.click() - # self.logger.log("재시도: 메모 노출 체크 OFF", level=logging.DEBUG) + # # 9. 노출 체크박스 처리 + # # chk = self.page.locator( + # # 'label:has-text("상품 목록에 메모 내용 노출하기") ' + # # 'input[type="checkbox"]' + # # ) + # # await chk.wait_for(state="attached") + # # checked = await chk.is_checked() + # # if is_memo_exposure and not checked: + # # await chk.click() + # # self.logger.log("재시도: 메모 노출 체크 ON", level=logging.DEBUG) + # # elif not is_memo_exposure and checked: + # # await chk.click() + # # self.logger.log("재시도: 메모 노출 체크 OFF", level=logging.DEBUG) - is_memo_exposure = self.toggle_states.get("memo_exposure", False) - exposer = self.page.locator(self.memo_exposer_locator) + # is_memo_exposure = self.toggle_states.get("memo_exposure", False) + # exposer = self.page.locator(self.memo_exposer_locator) - # 노출 체크박스 존재 대기 - await exposer.wait_for(state="attached") + # # 노출 체크박스 존재 대기 + # await exposer.wait_for(state="attached") - # 노출 체크박스가 보이고 활성화될 때까지 대기 - await exposer.wait_for(state="visible", timeout=5000) + # # 노출 체크박스가 보이고 활성화될 때까지 대기 + # await exposer.wait_for(state="visible", timeout=5000) - # 활성화 상태 확인 (disabled 속성이 없으면 활성화된 것) - max_attempts = 10 - for attempt in range(max_attempts): - is_disabled = await exposer.get_attribute('disabled') - if is_disabled is None: # disabled 속성이 없으면 활성화됨 - break - if attempt < max_attempts - 1: - await asyncio.sleep(0.5) - else: - self.logger.log("노출 체크박스가 활성화되지 않았습니다.", level=logging.WARNING) + # # 활성화 상태 확인 (disabled 속성이 없으면 활성화된 것) + # max_attempts = 10 + # for attempt in range(max_attempts): + # is_disabled = await exposer.get_attribute('disabled') + # if is_disabled is None: # disabled 속성이 없으면 활성화됨 + # break + # if attempt < max_attempts - 1: + # await asyncio.sleep(0.5) + # else: + # self.logger.log("노출 체크박스가 활성화되지 않았습니다.", level=logging.WARNING) - if is_memo_exposure: - await exposer.check() - else: - await exposer.uncheck() + # if is_memo_exposure: + # await exposer.check() + # else: + # await exposer.uncheck() - # 10. 저장 - save_btn = self.page.locator(self.memo_save_btn_locator) - await save_btn.wait_for(state="visible") - await save_btn.click() - self.logger.log("재시도: 메모 저장 클릭", level=logging.DEBUG) - await asyncio.sleep(0.3) - except Exception as e2: - screenshot_path = await self.save_error_screenshot() - self.logger.log(f"재시도 후에도 메모 입력 중 오류 발생: {e2}", level=logging.ERROR, exc_info=True) - # ESC 키 2번 전송하여 팝업 제거 후 재시도 로직을 제거하고, 이번 상품의 메모 입력을 건너뛰고 반환합니다. - self.logger.log("ESC 키를 2번 전송하여 메모 창을 닫고 해당 상품의 메모 입력을 건너뜁니다.", level=logging.WARNING) - await self.page.keyboard.press("Escape") - await self.page.keyboard.press("Escape") - await asyncio.sleep(0.5) # 잠시 대기 - await asyncio.sleep(0.5) - return + # # 10. 저장 + # save_btn = self.page.locator(self.memo_save_btn_locator) + # await save_btn.wait_for(state="visible") + # await save_btn.click() + # self.logger.log("재시도: 메모 저장 클릭", level=logging.DEBUG) + # await asyncio.sleep(0.3) + # except Exception as e2: + # screenshot_path = await self.save_error_screenshot() + # self.logger.log(f"재시도 후에도 메모 입력 중 오류 발생: {e2}", level=logging.ERROR, exc_info=True) + # # ESC 키 2번 전송하여 팝업 제거 후 재시도 로직을 제거하고, 이번 상품의 메모 입력을 건너뛰고 반환합니다. + # self.logger.log("ESC 키를 2번 전송하여 메모 창을 닫고 해당 상품의 메모 입력을 건너뜁니다.", level=logging.WARNING) + # await self.page.keyboard.press("Escape") + # await self.page.keyboard.press("Escape") + # await asyncio.sleep(0.5) # 잠시 대기 + # await asyncio.sleep(0.5) + # return def generate_titles_with_prices(self, title_infos): """top_5_titles와 top_5_prices를 매칭하여 텍스트 생성""" diff --git a/loggerModule.py b/loggerModule.py index 3466ea4c..c2f3b03d 100644 --- a/loggerModule.py +++ b/loggerModule.py @@ -121,79 +121,58 @@ class Logger(QObject): self.gui_log_level = gui_log_level -class CustomRotatingFileHandler(BaseRotatingHandler): - """로그 파일을 모두 .log 확장자로 생성하는 커스텀 핸들러""" - - def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None): - super().__init__(filename, mode, encoding) - self.maxBytes = maxBytes - self.backupCount = backupCount - # 기존 로그 파일 확인 및 인덱스 설정 - self._base_filename = filename - self._extension = '.log' - +class CustomRotatingFileHandler(RotatingFileHandler): + """`.log` 확장자를 강제하고 파일명을 `base_번호.log` 형식으로 롤오버하는 핸들러""" + + def __init__(self, filename, mode="a", maxBytes=0, backupCount=0, encoding=None): + # 확장자를 무조건 .log 로 통일 + if not filename.endswith(".log"): + base, _ = os.path.splitext(filename) + filename = base + ".log" + + # 부모(RotatingFileHandler) 초기화 + super().__init__(filename, mode, maxBytes, backupCount, encoding) + + # 편의를 위해 확장자 없는 베이스 이름 저장 + self._base_name, _ = os.path.splitext(self.baseFilename) + def doRollover(self): - """롤오버 수행 - 파일이 최대 크기에 도달하면 새 파일 생성""" + """`RotatingFileHandler`의 롤오버 방식(emit → shouldRollover) 그대로 사용하되 + 파일명을 `base_번호.log` 패턴으로 변경한다.""" + + # 현재 스트림 닫기 if self.stream: self.stream.close() self.stream = None - - # 기존 로그 파일 이름을 기반으로 백업 파일 생성 - base_name, ext = os.path.splitext(self._base_filename) - - # 현재 디렉토리의 모든 로그 파일 확인 - log_dir = os.path.dirname(self._base_filename) or '.' - existing_logs = glob.glob(f"{base_name}*.log") + + # existing backup logs + existing_logs = glob.glob(f"{self._base_name}_*.log") existing_logs.sort() - - # 최대 백업 수 초과하는 파일 제거 - while len(existing_logs) >= self.backupCount: - try: - oldest_file = existing_logs.pop(0) - os.remove(oldest_file) - except Exception: - pass - - # 새 로그 파일 이름 생성 (주 로그 파일 이름은 그대로 유지) - # 예: log.log, log_1.log, log_2.log, ... - if os.path.exists(self._base_filename): - # 인덱스가 있는 로그 파일들 찾기 - indexed_logs = [f for f in existing_logs if f != self._base_filename] - max_index = 0 - - for log_file in indexed_logs: + + # backupCount 초과 시 오래된 파일 삭제 + if self.backupCount > 0 and len(existing_logs) >= self.backupCount: + for old_log in existing_logs[: len(existing_logs) - self.backupCount + 1]: try: - # 파일 이름에서 인덱스 부분 추출 - name_part = os.path.basename(log_file) - name_without_ext = os.path.splitext(name_part)[0] - if '_' in name_without_ext: - idx_str = name_without_ext.split('_')[-1] - if idx_str.isdigit(): - max_index = max(max_index, int(idx_str)) + os.remove(old_log) except Exception: pass - - # 새 인덱스로 파일 이름 설정 - new_index = max_index + 1 - new_log_file = f"{base_name}_{new_index}.log" - - # 기존 파일 이름 변경 - try: - os.rename(self._base_filename, new_log_file) - except Exception: - pass - - # 스트림 다시 열기 - self.mode = 'w' + + # 새 인덱스 계산 + max_idx = 0 + for path in existing_logs: + idx_str = os.path.splitext(os.path.basename(path))[0].split("_")[-1] + if idx_str.isdigit(): + max_idx = max(max_idx, int(idx_str)) + + new_idx = max_idx + 1 + rollover_target = f"{self._base_name}_{new_idx}.log" + + # 현재 파일명을 rollover_target 으로 변경 + try: + os.rename(self.baseFilename, rollover_target) + except Exception: + pass + + # 새로운 스트림 오픈 (write 모드) + self.mode = "w" self.stream = self._open() - - def shouldRollover(self, record): - """롤오버가 필요한지 확인""" - if self.stream is None: # 첫 번째 로그 쓰기 시도 - self.stream = self._open() - - if self.maxBytes > 0: # 최대 크기가 지정된 경우만 검사 - self.stream.seek(0, 2) # 파일 끝으로 이동 - if self.stream.tell() >= self.maxBytes: - return True - return False diff --git a/mainUI_SP.py b/mainUI_SP.py index 95933a9a..6a11b1f6 100644 --- a/mainUI_SP.py +++ b/mainUI_SP.py @@ -539,6 +539,18 @@ class MAIN_GUI(QMainWindow): "off": [], } }, + "font_type_combo": { + "key": "global/font_type_combo", # 폰트 타입 + "state_key": "font_type", + "dependents": { # ON/OFF별 enable 위젯 리스트 + "on": [], + "off": [] + }, + "visible": { # ON/OFF별 visible 위젯 리스트 + "on": [], + "off": [], + } + }, "debug_toggle": { "key": "global/debug_enabled", # 디버그 사용 여부 "state_key": "debug_mode", @@ -943,7 +955,7 @@ class MAIN_GUI(QMainWindow): } }, "watermark_toggle": { - "key": "detail/watermark_enabled", # 상세페이지 이미지 워터마크 사용 여부 + "key": "detail/watermark", # 상세페이지 이미지 워터마크 사용 여부 "state_key": "watermark", "dependents": { # ON/OFF별 enable 위젯 리스트 "on": ["watermark_text_input", "opacity_percent_input"], @@ -1084,7 +1096,7 @@ class MAIN_GUI(QMainWindow): 'ed_mode': False, 'discord': False, 'is_localServer': False, - 'watermark': False, + 'watermark_toggle': False, 'clientID': "", 'clientSecret': "", 'discord_webhook': "", @@ -1234,52 +1246,55 @@ class MAIN_GUI(QMainWindow): def get_allowed_options_for_tier(self, tier): """등급별로 허용되는 옵션들을 반환합니다.""" tier_options = { - 'basic': ['대체', 'CPU'], - 'premium': ['대체', 'CPU'], - 'vip': ['대체', 'CPU', '자체서버'] + 'basic': ['CPU'], + 'premium': ['CPU', '자체서버'], + 'vip': ['CPU', '자체서버'] } - return tier_options.get(tier, ['대체']) + return tier_options.get(tier, ['CPU']) def get_best_option_for_tier(self, tier): """등급별로 가장 좋은 옵션을 반환합니다.""" allowed_options = self.get_allowed_options_for_tier(tier) - # 자체서버 > CPU > 대체 순서로 최고방식 선택 - for option in ['자체서버', 'CPU', '대체']: + # 자체서버 > CPU 순서로 최고방식 선택 + for option in ['CPU', '자체서버']: if option in allowed_options: return option - return '대체' + return 'CPU' def on_detailIMGTrans_type_combo_changed(self, selected_option): """상세페이지 이미지번역 엔진 드롭박스 변경 핸들러""" if self.authenticated_by_admin: - return - validated_option = self.validate_and_adjust_option( - self.detail_IMGTrans_type_combo, - selected_option, - "상세페이지 이미지번역" - ) + validated_option = selected_option + else: + validated_option = self.validate_and_adjust_option( + self.detail_IMGTrans_type_combo, + selected_option, + "상세페이지 이미지번역" + ) self.universal_input_handler(self.detail_IMGTrans_type_combo, validated_option) def on_thumb_trans_type_combo_changed(self, selected_option): """썸네일 번역 엔진 드롭박스 변경 핸들러""" if self.authenticated_by_admin: - return - validated_option = self.validate_and_adjust_option( - self.thumb_trans_type_combo, - selected_option, - "썸네일 번역" - ) + validated_option = selected_option + else: + validated_option = self.validate_and_adjust_option( + self.thumb_trans_type_combo, + selected_option, + "썸네일 번역" + ) self.universal_input_handler(self.thumb_trans_type_combo, validated_option) def on_optionIMGTrans_type_combo_changed(self, selected_option): """옵션 이미지번역 엔진 드롭박스 변경 핸들러""" if self.authenticated_by_admin: - return - validated_option = self.validate_and_adjust_option( - self.optionIMGTrans_type_combo, - selected_option, - "옵션 이미지번역" - ) + validated_option = selected_option + else: + validated_option = self.validate_and_adjust_option( + self.optionIMGTrans_type_combo, + selected_option, + "옵션 이미지번역" + ) self.universal_input_handler(self.optionIMGTrans_type_combo, validated_option) def universal_input_handler(self, widget, *args): @@ -2682,7 +2697,9 @@ class MAIN_GUI(QMainWindow): self.interval_widget = QWidget() self.interval_spinbox_layout = QHBoxLayout(self.interval_widget) self.interval_spinbox_label = QLabel("번역간격", self)# 작업 완료 메서드 수정 + self.interval_spinbox_label.setEnabled(False) self.interval_spinbox = QSpinBox(self) + self.interval_spinbox.setEnabled(False) # self.interval_spinbox.setFixedHeight(30) self.interval_spinbox.setObjectName("interval") self.interval_spinbox.setRange(2, 10) @@ -2713,7 +2730,9 @@ class MAIN_GUI(QMainWindow): self.interval_widget.leaveEvent = lambda e: self.reset_manual(self.global_manual_group, self.global_manual_label) self.watingTime_spinbox_label = QLabel("번역대기", self)# 작업 완료 메서드 수정 + self.watingTime_spinbox_label.setEnabled(False) self.watingTime_spinbox = QSpinBox(self) + self.watingTime_spinbox.setEnabled(False) # self.watingTime_spinbox.setFixedHeight(30) self.watingTime_spinbox.setObjectName("watingTime") self.watingTime_spinbox.setRange(5, 60) @@ -3223,7 +3242,7 @@ class MAIN_GUI(QMainWindow): """
옵션 이미지에 포함된 중국어 텍스트를 한국어로 번역하여 새 이미지를 생성합니다.
옵션 이미지의 사이즈 표, 색상 설명 등을 번역하여 고객에게 제공합니다.
-옵션 이미지의 특성에 따라API 또는 대체 번역 방식을 선택할 수 있습니다.
+옵션 이미지의 특성에 따라 CPU 또는 자체 번역 방식을 선택할 수 있습니다.
""" ) self.optionIMGTrans_widget.leaveEvent = lambda e: self.reset_manual(self.option_manual_group, self.option_manual_label) @@ -3239,8 +3258,8 @@ class MAIN_GUI(QMainWindow): self.optionIMGTrans_type_label = QLabel("옵션 이미지번역 엔진", self) self.optionIMGTrans_type_combo = QComboBox(self) self.optionIMGTrans_type_combo.setObjectName("optionIMGTrans_type_combo") - self.optionIMGTrans_type_combo.addItems(["대체", "CPU", "자체서버"]) - self.optionIMGTrans_type_combo.setCurrentText("대체") + self.optionIMGTrans_type_combo.addItems(["CPU", "자체서버"]) + self.optionIMGTrans_type_combo.setCurrentText("CPU") self.optionIMGTrans_type_combo.currentTextChanged.connect(self.on_optionIMGTrans_type_combo_changed) self.optionIMGTrans_type_widget.enterEvent = lambda e: self.show_manual_html( self.option_manual_group, @@ -3250,7 +3269,6 @@ class MAIN_GUI(QMainWindow):옵션 이미지 번역에 사용할 번역 방식을 선택합니다:
자체서버: 자체 API 서버를 활용한 고품질 이미지 번역 (VIP 등급)
CPU: 로컬 CPU를 사용한 이미지 번역 (Basic, Premium 등급 이상)
-대체: 대체 이미지 번역 (모든 등급)
""" ) self.optionIMGTrans_type_widget.leaveEvent = lambda e: self.reset_manual(self.option_manual_group, self.option_manual_label) @@ -3633,7 +3651,7 @@ class MAIN_GUI(QMainWindow): """상품의 썸네일에 포함된 중국어 텍스트를 한국어로 번역합니다.
번역된 텍스트는 원본 이미지의 위치와 스타일을 최대한 유지합니다.
-번역 엔진(파파고/대체)을 선택가능
+번역 엔진(CPU/자체번역)을 선택가능
""" ) self.thumb_widget.leaveEvent = lambda e: self.reset_manual(self.thumbnail_manual_group, self.thumbnail_manual_label) @@ -3649,8 +3667,8 @@ class MAIN_GUI(QMainWindow): self.thumb_trans_type_label = QLabel("썸네일번역 엔진", self) self.thumb_trans_type_combo = QComboBox(self) self.thumb_trans_type_combo.setObjectName("thumb_trans_type_combo") - self.thumb_trans_type_combo.addItems(["대체", "CPU", "자체서버"]) - self.thumb_trans_type_combo.setCurrentText("대체") + self.thumb_trans_type_combo.addItems(["CPU", "자체서버"]) + self.thumb_trans_type_combo.setCurrentText("CPU") self.thumb_trans_type_combo.currentTextChanged.connect(self.on_thumb_trans_type_combo_changed) self.thumb_trans_type_widget.enterEvent = lambda e: self.show_manual_html( self.thumbnail_manual_group, @@ -3660,7 +3678,6 @@ class MAIN_GUI(QMainWindow):썸네일 이미지 번역에 사용할 번역 엔진을 선택합니다:
자체서버: 자체 API 서버를 활용한 고품질 이미지 번역 (VIP 등급)
CPU: 로컬 CPU를 사용한 이미지 번역 (Basic, Premium 등급 이상)
-대체: 대체 이미지 번역 (모든 등급)
이미지의 특성과 텍스트 스타일에 따라 적합한 방식을 선택하세요.
""" ) @@ -3705,7 +3722,7 @@ class MAIN_GUI(QMainWindow): self.thumb_rmb_count_input.setObjectName("thumb_rmb_count_input") self.thumb_rmb_count_input.setMinimum(1) self.thumb_rmb_count_input.setMaximum(10) - self.thumb_rmb_count_input.setValue(self.settings_manager.get_value("thumb_rmb_count", 3)) + self.thumb_rmb_count_input.setValue(self.settings_manager.get_value("thumb_rmb_count", 1)) self.thumb_rmb_count_input.valueChanged.connect(lambda value: self.update_thumb_rmb_count(value)) self.thumb_rmb_count_input.valueChanged.connect(lambda value: self.universal_input_handler(self.thumb_rmb_count_input, value)) self.thumb_rmb_count_input.setFixedWidth(60) @@ -3860,8 +3877,8 @@ class MAIN_GUI(QMainWindow): self.detail_IMGTrans_type_label = QLabel("상페번역엔진", self) self.detail_IMGTrans_type_combo = QComboBox(self) self.detail_IMGTrans_type_combo.setObjectName("detail_IMGTrans_type_combo") - self.detail_IMGTrans_type_combo.addItems(["대체", "CPU", "자체서버"]) - self.detail_IMGTrans_type_combo.setCurrentText("대체") + self.detail_IMGTrans_type_combo.addItems(["CPU", "자체서버"]) + self.detail_IMGTrans_type_combo.setCurrentText("CPU") self.detail_IMGTrans_type_combo.currentTextChanged.connect(self.on_detailIMGTrans_type_combo_changed) self.detail_IMGTrans_type_widget.enterEvent = lambda e: self.show_manual_html( self.detail_manual_group, @@ -3871,7 +3888,6 @@ class MAIN_GUI(QMainWindow):상세페이지 이미지 번역에 사용할 번역 방식을 선택합니다:
자체서버: 자체 API 서버를 활용한 고품질 이미지 번역 (VIP 등급)
CPU: 로컬 CPU를 사용한 이미지 번역 (Basic, Premium 등급 이상)
-대체: 자체 이미지 번역 (모든 등급)
서버의 부담을 줄이기 위해 이미지번역속도가 느릴 수 있습니다.
OCR을 설정할 경우 금지단어가 포함된 이미지는 자동삭제 됩니다.
OCR을 설정할 경우 중국어가 없는 이미지는 번역되지 않아, 처리속도가 빨라집니다.
@@ -3889,7 +3905,7 @@ class MAIN_GUI(QMainWindow): self.watermark_toggle_layout = QHBoxLayout(self.watermark_widget) self.watermark_toggle_label = QLabel("상페워터마크", self) self.watermark_toggle = ToggleSwitch(self) - self.watermark_toggle.setObjectName("watermark") + self.watermark_toggle.setObjectName("watermark_toggle") self.watermark_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.watermark_toggle, checked)) self.watermark_widget.enterEvent = lambda e: self.show_manual_html( self.detail_manual_group, @@ -3916,7 +3932,7 @@ class MAIN_GUI(QMainWindow): self.watermark_text_input = QLineEdit() self.watermark_text_input.setObjectName("watermark_text_input") self.watermark_text_input.setText(self.toggle_states.get("watermark_text", "")) - self.on_watermark_toggle_clicked + # self.watermark_text_input.connect(self.on_watermark_toggle_clicked()) self.watermark_text_input.textChanged.connect(lambda text: self.universal_input_handler(self.watermark_text_input, text)) # self.watermark_text_input.setEnabled(False) self.watermark_text_widget.enterEvent = lambda e: self.show_manual_html( diff --git a/src/contents/option.py b/src/contents/option.py index 33512313..9c0491f9 100644 --- a/src/contents/option.py +++ b/src/contents/option.py @@ -1002,6 +1002,19 @@ class OptionHandler: original_name = item.get("original_name") name_input_elem = item.get("name_input_elem") + # stale ElementHandle 방지: detach 여부 확인 후 필요 시 재조회 + try: + if name_input_elem: + await name_input_elem.get_property("tagName") + except Exception: + name_input_elem = None + + if not name_input_elem: + parent_elem = item.get("checkbox_elem") + if parent_elem: + name_input_elem = await parent_elem.query_selector("input.ant-input") + item["name_input_elem"] = name_input_elem + if not original_name or not name_input_elem: continue @@ -1478,6 +1491,12 @@ class OptionHandler: has_valid_image = True break + # 체크박스 선택 여부 확인 – 노출되지 않은 옵션이면 건너뜀 + checkbox_input = await item.locator("label.ant-checkbox-wrapper input[type='checkbox']") + is_checked = await checkbox_input.is_checked() if checkbox_input else False + if not is_checked: + continue + if has_valid_image: valid_items.append(item) diff --git a/src/contents/thumb.py b/src/contents/thumb.py index 6a653621..3f713f03 100644 --- a/src/contents/thumb.py +++ b/src/contents/thumb.py @@ -137,7 +137,8 @@ class ThumbnailHandler: original_image_url=image_url, file_prefix="thumb_rmb" ) - # result = await self.imageProcessor.process_single_image(page=self.page, original_image_url=image_url, index=idx, delay=1.0, file_prefix="thumb_rmb") # 누끼기능 충돌로 번역대체 + if not result: + result = await self.imageProcessor.process_single_image(page=self.page, original_image_url=image_url, index=idx, delay=1.0, file_prefix="thumb_rmb") # 누끼기능 충돌로 번역대체 elif thumb: result = await self.imageProcessor.process_single_image(page=self.page, original_image_url=image_url, index=idx, delay=1.0, file_prefix="thumb") diff --git a/src/modules/clipboardImageManager copy.py b/src/modules/clipboardImageManager copy.py index fefa400c..ba29a964 100644 --- a/src/modules/clipboardImageManager copy.py +++ b/src/modules/clipboardImageManager copy.py @@ -204,19 +204,25 @@ class ClipboardImageManager: bbox = draw.textbbox((0, 0), watermark_text, font=font) text_width, text_height = bbox[2] - bbox[0], bbox[3] - bbox[1] + # 텍스트 크기가 0인 경우 예외 처리 (빈 문자열 등) + if text_width == 0 or text_height == 0: + self.logger.log("워터마크 텍스트 크기를 계산할 수 없어 워터마크를 건너뜁니다.", level=logging.WARNING) + return image + # 이미지 크기 width, height = image.size # 워터마크 레이어 생성 watermark_layer = Image.new("RGBA", (width, height)) # RGBA 이미지 생성 - # 지그재그 간격 설정 - zigzag_step = int(text_height * 2) # Y축의 지그재그 간격 + # 지그재그 간격 설정 (0 방지를 위해 최소 1 보장) + zigzag_step = max(1, int(text_height * 2)) # Y축의 지그재그 간격 + step_x = max(1, int(text_width * 3)) # X축 반복 간격도 0 방지 # 이미지 전체에 반복적으로 워터마크 텍스트 그리기 (지그재그 형태) for y in range(0, height, zigzag_step): - for x in range(0, width, int(text_width * 3)): # 3배 너비 간격으로 반복 + for x in range(0, width, step_x): # 3배 너비 간격으로 반복 # 텍스트가 한 줄씩 지그재그 형태로 X축을 교차하여 이동 x_offset = (y // zigzag_step) % 2 * int(text_width * 1.5) # 짝수 행에서는 X축을 약간 이동 diff --git a/src/modules/image_processor3.py b/src/modules/image_processor3.py index 9052e050..3da22865 100644 --- a/src/modules/image_processor3.py +++ b/src/modules/image_processor3.py @@ -1,6 +1,7 @@ import os import asyncio -import aiofiles +import requests +import time import logging from urllib.parse import urlparse import sys @@ -76,7 +77,7 @@ class ImageProcessor3: self.request_rembg_server_url = self.toggle_states.get("request_rembg_server_url", None) # self.request_rembg_server_url = self.toggle_states.get("request_rembg_server_url_local", None) - if self.is_frozen(): + if not self.is_frozen(): self.request_rembg_server_url = self.toggle_states.get("request_rembg_server_url_local", None) self.request_ai_server = Request_AI_Server(logger=self.logger, inpaint_server_url=self.request_inpainting_server_url, rembg_server_url=self.request_rembg_server_url) @@ -181,7 +182,8 @@ class ImageProcessor3: self.logger.log(f"이미지 {index+1} 처리 시작: {original_image_url} - {processing_mode}", level=logging.DEBUG) # 1. 이미지 다운로드 - local_image_path = await self.download_image(page, original_image_url, index, file_prefix) + local_image_path = await self.download_image(image_url=original_image_url, index=index, file_prefix=file_prefix) + if not local_image_path: self.logger.log(f"이미지 {index+1} 다운로드 실패, 원본 URL 반환", level=logging.WARNING) return {'status': 'failed', 'path': original_image_url, 'error': '다운로드 실패'} @@ -365,7 +367,7 @@ class ImageProcessor3: img_path = os.path.join(self.TEMP_IMAGE_DIR, f"translated_img_{index+1}.png") watermark_text=self.toggle_states.get("watermark_text", "이미지 저작권 보유") - is_watermark_enabled = watermark_text != "" or self.toggle_states.get("watermark", False) + is_watermark_enabled = watermark_text != "" or self.toggle_states.get("watermark_toggle", False) self.logger.log(f"watermark_text: {watermark_text}", level=logging.DEBUG) self.logger.log(f"is_watermark_enabled: {is_watermark_enabled}", level=logging.DEBUG) @@ -394,9 +396,6 @@ class ImageProcessor3: def download_image(self, image_url, index, file_prefix="", max_retries=3): """Requests를 사용해 이미지를 다운로드합니다""" - import requests - import time - import random # 로컬 파일 경로면 바로 반환 if os.path.isfile(image_url): diff --git a/src/modules/postImageManager.py b/src/modules/postImageManager.py index 8c934070..6faaf442 100644 --- a/src/modules/postImageManager.py +++ b/src/modules/postImageManager.py @@ -104,6 +104,11 @@ class PostImageManager: bbox = draw.textbbox((0, 0), watermark_text, font=font) text_width, text_height = bbox[2] - bbox[0], bbox[3] - bbox[1] + # 텍스트 크기가 0인 경우 예외 처리 (빈 문자열 등) + if text_width == 0 or text_height == 0: + self.logger.log("워터마크 텍스트 크기를 계산할 수 없어 워터마크를 건너뜁니다.", level=logging.WARNING) + return image_data + # 이미지 크기 width, height = image_data.size @@ -116,6 +121,7 @@ class PostImageManager: zigzag_step = max(1, int(text_height * 2)) # Y축 간격 x_step = max(1, int(text_width * 3)) # X축 간격 + step_x = max(1, int(text_width * 3)) # X축 반복 간격도 0 방지 # 이미지 전체에 반복적으로 워터마크 텍스트 그리기 (지그재그 형태) for y in range(0, height, zigzag_step): diff --git a/src/user_data/user_data_909d2ef8-7053-4006-ab40-49eb49f20383.db b/src/user_data/user_data_909d2ef8-7053-4006-ab40-49eb49f20383.db index e6b35dfd..ae6ff3df 100644 Binary files a/src/user_data/user_data_909d2ef8-7053-4006-ab40-49eb49f20383.db and b/src/user_data/user_data_909d2ef8-7053-4006-ab40-49eb49f20383.db differ diff --git a/updateManager/updateLog.md b/updateManager/updateLog.md index 29ffd1fd..ef22b9ad 100644 --- a/updateManager/updateLog.md +++ b/updateManager/updateLog.md @@ -6,16 +6,21 @@ ### 오류 수정 - 옵션명 번역 중 특수문자 처리 누락 추가 - 옵션 번역이 항상 CPU 나 자체서버로 처리되는 문제 수정 +- 옵션선택시 간헐적으로 DOM을 못찾는 문제 수정 - 누끼만 선택했을 경우 상품수정이 멈추는 문제 수정 - 이미지프로세서 서버 변경 - 상세페이지 소개글 입력방법 개선 - 상품수집 오류 발생시 적정가격이 낮게 산정되는 문제 수정 +- 누끼 실패시 번역으로 백업 +- 로거모듈 오류 수정 +- 프리미엄등급에서 다른번역을 선택해도 대체번역으로 선택되는 문제 수정 +- 워터마크 선택 상태가 저장되지 않는 문제 수정 및 기본값 OFF로 변경 + ### 기능 추가 - 이미지번역(옵션/섬네일/상세페이지)에서 대체번역 완전제외 - 가격설정(크무비포함)의 가격범위 최대 100만원 -> 1000만원 으로 변경 -- 기본 워터마크 글자제거 - +- 대체번역 삭제 # 3.9.8 업데이트 로그