import os import sys import logging import asyncio import glob class ThumbnailHandler: def __init__(self, locator_manager, browser_controller, logger, whale_translator, clipboardImageManager, toggle_states, update_detail_progress_signal, set_progress_visible_signal): self.update_detail_progress_signal = update_detail_progress_signal self.set_progress_visible_signal = set_progress_visible_signal self.locator_manager = locator_manager self.browser_controller = browser_controller self.page = self.browser_controller.page self.whale_translator = whale_translator self.clipboardImageManager = clipboardImageManager self.toggle_states = toggle_states self.logger = logger self.thumbnail_selector_template = "div#productMainContentContainerId div:nth-child({index}) > div > div:nth-child(2) > div > img" self.delete_button_selector_template = "div#productMainContentContainerId div:nth-child({index}) > div > div.ant-row.ant-row-no-wrap.ant-row-space-between.ant-row-middle.css-1li46mu > div:nth-child(1) > div > span" # self.delete_button_selector_for_Num1 = "div#productMainContentContainerId div:nth-child(1) > div > div.ant-row.ant-row-no-wrap.ant-row-space-between.ant-row-middle.css-1li46mu > div:nth-child(1) > div > span" self.upload_button_selector = "div#productMainContentContainerId div:nth-child({index}) > div > div:nth-child(2) > span" self.file_upload_button_selector = 'span.ant-upload-btn input[type="file"]' self.confirm_upload_button_selector = "div.ant-modal-footer > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary" # # main.py 실행 폴더의 tmp_images 폴더 경로 설정 # base_dir = os.path.dirname(os.path.abspath(__file__)) # main.py 위치 확인 # self.temp_dir = os.path.join(base_dir, "tmp_images") # tmp_images 폴더 경로 설정 # os.makedirs(self.temp_dir, exist_ok=True) # 폴더가 없으면 생성 # main.py 실행 폴더의 tmp_images 폴더 경로 설정 base_dir = self.get_base_dir() # get_base_dir() 메서드 호출 self.temp_dir = os.path.join(base_dir, "tmp_images") # tmp_images 폴더 경로 설정 os.makedirs(self.temp_dir, exist_ok=True) # 폴더가 없으면 생성 self.logger.log(f"임시 디렉토리 생성: {self.temp_dir}", level=logging.INFO) # 디렉토리 생성 로그 출력 def get_base_dir(self): """ 실행 환경에 따라 base_dir을 설정하는 메서드. cx_Freeze로 패키징된 경우 실행 파일의 경로, 일반 Python 환경일 경우 __file__을 기준으로 설정. """ if getattr(sys, 'frozen', False): # 패키징된 경우 base_dir = os.path.dirname(sys.executable) internal_dir = os.path.join(base_dir, '_internal') # _internal 디렉토리 포함 if os.path.exists(internal_dir): # _internal 디렉토리가 존재하면 base_dir로 설정 return internal_dir else: # 일반 Python 실행 환경 base_dir = os.path.dirname(os.path.abspath(__file__)) return base_dir def update_page(self, page1): self.page = page1 self.logger.log(f"page객체 업데이트 : {page1}", level=logging.DEBUG) def update_whale(self): self.whale_translator = self.browser_controller.get_whale() self.logger.log(f"whale_translator 업데이트 : {self.whale_translator}", level=logging.DEBUG) def reset_state(self): """썸네일 핸들러의 상태를 초기화합니다.""" self.logger.log("ThumbnailHandler 상태 초기화", level=logging.DEBUG) # 임시 파일 처리 try: # 임시 디렉토리의 모든 파일 삭제 for file_path in glob.glob(os.path.join(self.temp_dir, "*.png")): try: os.remove(file_path) self.logger.log(f"썸네일 임시 파일 삭제: {file_path}", level=logging.DEBUG) except Exception as e: self.logger.log(f"썸네일 임시 파일 삭제 실패: {file_path}, 오류: {e}", level=logging.WARNING) except Exception as e: self.logger.log(f"썸네일 임시 파일 처리 중 오류 발생: {e}", level=logging.ERROR) async def process_thumbnails(self, title_infos): thumb_trans_type = title_infos['thumb_trans_type'] thumb_rmb_count = title_infos['thumb_rmb_count'] self.update_whale() # 썸네일 카드 개수 수집 thumbnails = await self.page.query_selector_all("div#productMainContentContainerId div.ant-row.ant-row-bottom.css-1li46mu > div") total_thumbnails = len(thumbnails) # 마지막 카드는 '추가' 카드이므로 실제 썸네일 이미지 개수는 1개를 뺍니다 actual_thumbnails = total_thumbnails - 1 self.logger.log(f"총 썸네일 카드 수집 완료: {total_thumbnails}개 (실제 이미지: {actual_thumbnails}개)", level=logging.DEBUG) self.set_progress_visible_signal.emit(True) if thumb_trans_type: for index in range(actual_thumbnails): thumbnail_img_selector = self.thumbnail_selector_template.format(index=1) delete_button_selector = self.delete_button_selector_template.format(index=1) # 첫번째카드만 삭제를 반복하면 되므로 # 이미지 URL 수집 thumbnail_img_element = await self.page.query_selector(thumbnail_img_selector) if thumbnail_img_element: image_url = await thumbnail_img_element.get_attribute("src") self.logger.log(f"{index+1}번째 썸네일 이미지 URL: {image_url}", level=logging.DEBUG) # 이미지 번역 실행 translated_image_path = await self.translate_thumbnail_image(image_url, index) # 기존 썸네일 삭제 await self.delete_thumbnail(delete_button_selector) # 번역된 이미지 업로드 await self.upload_translated_image(translated_image_path, thumbnails) self.update_detail_progress_signal.emit(index+1, actual_thumbnails) else: # 첫번째 편집 버튼 클릭하여 에디터 열기 try: # XPath를 사용하여 편집하기 버튼 직접 클릭 edit_button = self.page.locator("xpath=//*[@id='productMainContentContainerId']/div/div[1]/div[4]/div[1]/div[1]/div/div[3]/div[2]/div/span[2]") await edit_button.wait_for(state="attached", timeout=5000) await edit_button.click() self.logger.log("XPath를 사용하여 편집하기 버튼 클릭 성공", level=logging.DEBUG) # 약간의 대기 시간 추가 (페이지 반응 시간 고려) await asyncio.sleep(1) except Exception as e: self.logger.log(f"XPath를 사용한 편집 버튼 클릭 실패: {e}", level=logging.ERROR, exc_info=True) self.set_progress_visible_signal.emit(False) return # '퍼센티 이미지 에디터' 확인 및 포커스 설정 try: # 에디터 찾기 - 텍스트 내용으로 더 구체적으로 선택 editor_locator = self.page.locator("div.ant-drawer-title:has-text('퍼센티 이미지 에디터')") await editor_locator.wait_for(timeout=5000) await editor_locator.focus() self.logger.log("퍼센티 이미지 에디터 오픈 및 포커스 설정 완료", level=logging.DEBUG) except Exception as e: self.logger.log(f"에디터 포커스 설정 실패: {e}", level=logging.WARNING) # 실패해도 계속 진행 self.logger.log("퍼센티 이미지 에디터 오픈 및 포커스 설정 시도 완료", level=logging.DEBUG) # 작업 대상 결정 (type==1: 첫번째 이미지만 배경지우기, type==2: 두번째 이미지까지 배경지우기) bg_removal_count = thumb_rmb_count if actual_thumbnails >= 2 else 1 # 첫번째 이미지는 이미 열려 있으므로 작업 시작 for i in range(actual_thumbnails): self.logger.log(f"{i+1}번째 썸네일 작업 시작", level=logging.DEBUG) if i == 0: # 첫번째 이미지는 현재 포커스된 상태 pass else: # 탭 키를 눌러 다음 이미지로 전환 await self.page.keyboard.press("Tab") await asyncio.sleep(2) # 이미지 로딩 시간 대기 self.logger.log(f"{i+1}번째 썸네일로 탭 이동", level=logging.DEBUG) # 각 이미지 작업 수행 if i < bg_removal_count: self.logger.log(f"{i+1}번째 썸네일 배경지우기 시작 (단축키 M)", level=logging.DEBUG) await self.page.keyboard.down("M") # M 키 누르기 await asyncio.sleep(0.5) # 0.5초 대기 await self.page.keyboard.up("M") # M 키 떼기 await asyncio.sleep(2) # 2초 대기 # 배경지우기 완료 확인 try: max_attempts = 60 for attempt in range(max_attempts): # loading 상태 확인 loading_count = await self.page.get_by_role("img", name="loading").count() # border-inner 상태 확인 border_count = await self.page.get_by_role("img", name="border-inner").count() if border_count > 0: self.logger.log(f"배경지우기 완료 감지 (시도: {attempt+1})", level=logging.DEBUG) break elif loading_count > 0: self.logger.log(f"배경지우기 작업 중... (시도: {attempt+1})", level=logging.DEBUG) if attempt < max_attempts - 1: await asyncio.sleep(1) self.logger.log("배경지우기 작업 완료 확인", level=logging.DEBUG) except Exception as e: self.logger.log(f"배경지우기 작업 완료 확인 실패: {e}, 작업 계속 진행", level=logging.WARNING) await asyncio.sleep(1) else: self.logger.log(f"{i+1}번째 썸네일 이미지 번역 시작 (단축키 T)", level=logging.DEBUG) await self.page.keyboard.down("T") # T 키 누르기 await asyncio.sleep(0.5) # 0.5초 대기 await self.page.keyboard.up("T") # T 키 떼기 # 이미지 번역 완료 확인 try: max_attempts = 60 for attempt in range(max_attempts): # loading 상태 확인 loading_count = await self.page.get_by_role("img", name="loading").count() # thunderbolt 상태 확인 thunder_count = await self.page.get_by_role("img", name="thunderbolt").count() if thunder_count > 0: self.logger.log(f"이미지 번역 완료 감지 (시도: {attempt+1})", level=logging.DEBUG) break elif loading_count > 0: self.logger.log(f"이미지 번역 작업 중... (시도: {attempt+1})", level=logging.DEBUG) if attempt < max_attempts - 1: await asyncio.sleep(1) self.logger.log("이미지 번역 작업 완료 확인", level=logging.DEBUG) except Exception as e: self.logger.log(f"이미지 번역 작업 완료 확인 실패: {e}, 작업 계속 진행", level=logging.WARNING) await asyncio.sleep(1) # 각 이미지 작업 후 개별 저장 없이, 마지막 이미지까지 진행 후 전체 저장을 위해 진행률 업데이트 self.update_detail_progress_signal.emit(i+1, actual_thumbnails) # 모든 이미지 작업 완료 후 전체 저장 (Ctrl+S) 및 에디터 닫기 (ESC) await self.page.keyboard.press("Control+s") self.logger.log("전체 수정사항 저장 (Ctrl+S) 전송", level=logging.DEBUG) await asyncio.sleep(1) await self.page.keyboard.press("Escape") self.logger.log("이미지 에디터 종료 (ESC 전송)", level=logging.DEBUG) self.set_progress_visible_signal.emit(False) async def translate_thumbnail_image(self, image_url, index): try: # 이미지 번역 수행 및 임시 저장 경로 반환 translated_image_path = os.path.join(self.temp_dir, f"translated_thumb_{index+1}.png") # 이미지 저장 경로 설정 is_success_translated = self.whale_translator.translate_image(image_url) self.clipboardImageManager.process_clipboard(image_url, is_success_translated, self.toggle_states, translated_image_path, is_thumb=True) self.logger.log(f"{index+1}번째 썸네일 이미지 번역 완료: {translated_image_path}", level=logging.DEBUG) # self.browser_controller.switch_to_chrome() # 크롬으로 포커스 이동 return translated_image_path except Exception as e: self.logger.log(f"{index+1}번째 썸네일 이미지 번역 중 오류 발생: {e}", level=logging.ERROR, exc_info=True) return None async def delete_thumbnail(self, delete_button_selector): try: delete_button = await self.page.query_selector(delete_button_selector) if delete_button: await delete_button.click() self.logger.log("썸네일 삭제 버튼 클릭 완료", level=logging.DEBUG) except Exception as e: self.logger.log(f"썸네일 삭제 중 오류 발생: {e}", level=logging.ERROR, exc_info=True) async def upload_translated_image(self, image_path, thumbnails): try: # 썸네일 카드 목록의 마지막 요소가 업로드 카드 last_thumbnail = thumbnails[-1] upload_button = await last_thumbnail.query_selector("span:has-text('Upload')") if upload_button: await upload_button.click() self.logger.log("업로드 버튼 클릭 완료", level=logging.DEBUG) # 파일 업로드 수행 file_input = await self.page.query_selector(self.file_upload_button_selector) if file_input: await file_input.set_input_files(image_path) self.logger.log("이미지 파일 업로드 완료", level=logging.DEBUG) # '이미지 삽입' 버튼 클릭 confirm_button = await self.page.query_selector(self.confirm_upload_button_selector) await confirm_button.click() self.logger.log("이미지 삽입 버튼 클릭 완료", level=logging.DEBUG) except Exception as e: self.logger.log(f"이미지 업로드 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)