764 lines
40 KiB
Python
764 lines
40 KiB
Python
import base64
|
|
import pyperclip
|
|
import win32clipboard
|
|
from io import BytesIO
|
|
from PIL import Image, ImageGrab, ImageFont, ImageDraw
|
|
import requests
|
|
import numpy as np
|
|
import cv2
|
|
import time
|
|
import os, sys
|
|
from datetime import datetime
|
|
import random
|
|
import pywinauto
|
|
import io
|
|
import logging
|
|
|
|
class ClipboardImageManager:
|
|
def __init__(self, logger, watermark_font_size=36, debug_flag=False):
|
|
self.logger = logger
|
|
self.debug = debug_flag # 디버그 플래그를 클래스 변수로 사용
|
|
|
|
# 프로그램이 위치한 경로 기준으로 폰트 경로 설정
|
|
self.base_path = self.get_base_dir()
|
|
# 먼저 현재 모듈과 같은 디렉토리에서 폰트 파일 찾기
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
self.font_path = os.path.join(current_dir, 'HakgyoansimDunggeunmisoTTFB.ttf')
|
|
|
|
# 폰트 파일이 없으면 다른 경로들을 시도
|
|
if not os.path.exists(self.font_path):
|
|
alternative_paths = [
|
|
os.path.join(self.base_path, 'HakgyoansimDunggeunmisoTTFB.ttf'),
|
|
os.path.join(self.base_path, 'src', 'modules', 'HakgyoansimDunggeunmisoTTFB.ttf'),
|
|
os.path.join(os.path.dirname(self.base_path), 'src', 'modules', 'HakgyoansimDunggeunmisoTTFB.ttf')
|
|
]
|
|
|
|
for alt_path in alternative_paths:
|
|
if os.path.exists(alt_path):
|
|
self.font_path = alt_path
|
|
break
|
|
|
|
# 폰트 로드 (예외 처리 추가)
|
|
try:
|
|
self.font = ImageFont.truetype(self.font_path, watermark_font_size)
|
|
self.logger.log(f"폰트 로드 성공: {self.font_path}", level=logging.DEBUG)
|
|
except Exception as e:
|
|
self.logger.log(f"커스텀 폰트 로드 실패 ({self.font_path}): {e}", level=logging.WARNING)
|
|
try:
|
|
# 기본 폰트 사용
|
|
self.font = ImageFont.load_default()
|
|
self.logger.log("기본 폰트를 사용합니다.", level=logging.INFO)
|
|
except Exception as e2:
|
|
self.logger.log(f"기본 폰트 로드도 실패: {e2}", level=logging.ERROR)
|
|
# 최후의 수단으로 None 설정
|
|
self.font = None
|
|
|
|
# self.debug = True
|
|
|
|
def reset_state(self):
|
|
"""클립보드 이미지 관리자의 상태를 초기화합니다."""
|
|
self.logger.log("ClipboardImageManager 상태 초기화", level=logging.DEBUG)
|
|
# 클립보드 비우기
|
|
self.clear_clipboard()
|
|
|
|
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, 'lib', 'src') # lib 디렉토리 포함
|
|
if os.path.exists(internal_dir): # lib 디렉토리가 존재하면 base_dir로 설정
|
|
return internal_dir
|
|
|
|
else: # 일반 Python 실행 환경
|
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
|
debug_dir = os.path.join(base_dir, 'src') # lib 디렉토리 포함
|
|
|
|
return debug_dir
|
|
|
|
|
|
def get_clipboard_data(self):
|
|
"""클립보드의 텍스트 또는 이미지 데이터를 가져옵니다."""
|
|
self.logger.log("클립보드의 텍스트 또는 이미지 데이터를 가져옵니다", level=logging.DEBUG)
|
|
|
|
max_attempts = 5
|
|
attempt = 0
|
|
|
|
while attempt < max_attempts:
|
|
try:
|
|
# 1. 텍스트 데이터 우선 시도
|
|
clipboard_text = pyperclip.paste()
|
|
if clipboard_text:
|
|
return clipboard_text
|
|
|
|
# 2. 텍스트가 없으면 이미지 확인
|
|
self.logger.log("텍스트 데이터가 없어 이미지 데이터 확인 시도", level=logging.DEBUG)
|
|
image = ImageGrab.grabclipboard()
|
|
if isinstance(image, Image.Image): # 이미지 데이터가 있는 경우
|
|
self.logger.log("클립보드에 이미지 데이터가 확인되었습니다.", level=logging.DEBUG)
|
|
return image # PIL 이미지 객체 반환
|
|
else:
|
|
self.logger.log("클립보드에 텍스트 또는 이미지 데이터가 없습니다.", level=logging.DEBUG)
|
|
return None
|
|
|
|
except Exception as e:
|
|
attempt += 1
|
|
self.logger.log(f"클립보드 데이터를 가져오는 중 오류 발생 (시도 {attempt}/{max_attempts}): {e}", level=logging.WARNING)
|
|
if attempt < max_attempts:
|
|
time.sleep(0.5) # 0.5초 대기 후 재시도
|
|
else:
|
|
self.logger.log(f"클립보드 데이터를 가져오는 중 최대 시도 횟수 초과: {e}", level=logging.ERROR, exc_info=True)
|
|
return None
|
|
|
|
def set_image_to_clipboard(self, image):
|
|
"""이미지를 클립보드에 넣는 함수 (Windows 전용)"""
|
|
output = BytesIO()
|
|
image.save(output, "BMP")
|
|
self.logger.log(f"이미지 데이터 BMP 변환", level=logging.DEBUG)
|
|
|
|
data = output.getvalue()[14:] # BMP 헤더 제거
|
|
output.close()
|
|
self.logger.log(f"이미지 BMP 헤더 제거", level=logging.DEBUG)
|
|
|
|
# 클립보드 접근 재시도 로직
|
|
max_attempts = 5
|
|
attempt = 0
|
|
success = False
|
|
|
|
while attempt < max_attempts and not success:
|
|
try:
|
|
# 클립보드에 이미지 데이터 넣기
|
|
win32clipboard.OpenClipboard()
|
|
win32clipboard.EmptyClipboard()
|
|
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
|
|
win32clipboard.CloseClipboard()
|
|
success = True
|
|
self.logger.log(f"클립보드 데이터 저장 성공 (시도 {attempt+1}/{max_attempts})", level=logging.DEBUG)
|
|
except Exception as e:
|
|
attempt += 1
|
|
self.logger.log(f"클립보드 데이터 저장 실패 (시도 {attempt}/{max_attempts}): {e}", level=logging.WARNING)
|
|
if attempt < max_attempts:
|
|
time.sleep(0.5) # 0.5초 대기 후 재시도
|
|
|
|
# 클립보드가 제대로 설정되었는지 확인하는 로그
|
|
if success:
|
|
try:
|
|
time.sleep(0.1) # 아주 짧은 대기 시간
|
|
win32clipboard.OpenClipboard()
|
|
if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_DIB):
|
|
self.logger.log("클립보드 데이터 확인 성공", level=logging.DEBUG)
|
|
else:
|
|
self.logger.log("클립보드 데이터 확인 실패", level=logging.ERROR)
|
|
win32clipboard.CloseClipboard()
|
|
except Exception as e:
|
|
self.logger.log(f"클립보드 데이터 확인 중 오류: {e}", level=logging.ERROR)
|
|
|
|
def save_image_to_path(self, image, path):
|
|
try:
|
|
if image:
|
|
# 이미지를 저장 경로에 저장
|
|
self.logger.log(f"이미지 저장 완료 : {path}", level=logging.INFO)
|
|
image.save(path, format='PNG')
|
|
return path
|
|
|
|
except Exception as e:
|
|
raise RuntimeError(f"이미지 저장 중 오류 발생: {e}")
|
|
|
|
def add_watermark(self, image, watermark_text="Watermark", opacity_percent=30, angle=30, font_size=36):
|
|
"""
|
|
이미지에 텍스트 워터마크를 이미지 전체에 걸쳐서 추가하는 함수
|
|
:param image: PIL 이미지 객체
|
|
:param watermark_text: 워터마크로 추가할 텍스트
|
|
:param opacity_percent: 워터마크의 투명도 (0~100)
|
|
:param angle: 워터마크 텍스트 회전 각도 (기본 30도)
|
|
:param font_size: 워터마크 텍스트의 폰트 크기 (기본 36)
|
|
:return: 워터마크가 추가된 이미지
|
|
"""
|
|
# 폰트가 로드되지 않은 경우 원본 이미지 반환
|
|
if self.font is None:
|
|
self.logger.log("폰트가 로드되지 않아 워터마크를 추가할 수 없습니다. 원본 이미지를 반환합니다.", level=logging.WARNING)
|
|
return image
|
|
|
|
# 이미지 복사본 생성
|
|
watermark_image = image.copy()
|
|
|
|
# 폰트 설정 (안전한 폰트 로딩)
|
|
try:
|
|
# self.font가 있으면 크기만 조정해서 새 폰트 생성
|
|
if hasattr(self, 'font_path') and os.path.exists(self.font_path):
|
|
font = ImageFont.truetype(self.font_path, font_size)
|
|
else:
|
|
# 크기를 조정할 수 없으면 기존 폰트 사용
|
|
font = self.font
|
|
except Exception as e:
|
|
self.logger.log(f"폰트 크기 조정 실패: {e}. 기본 폰트를 사용합니다.", level=logging.WARNING)
|
|
font = self.font
|
|
|
|
# 텍스트 투명도를 0~255로 변환
|
|
opacity = int(255 * (opacity_percent / 100))
|
|
|
|
# 텍스트 크기 측정 (textbbox 사용)
|
|
draw = ImageDraw.Draw(watermark_image)
|
|
bbox = draw.textbbox((0, 0), watermark_text, font=font)
|
|
text_width, text_height = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
|
|
|
# 이미지 크기
|
|
width, height = image.size
|
|
|
|
# 워터마크 레이어 생성
|
|
watermark_layer = Image.new("RGBA", (width, height)) # RGBA 이미지 생성
|
|
|
|
# 지그재그 간격 설정
|
|
zigzag_step = int(text_height * 2) # Y축의 지그재그 간격
|
|
|
|
|
|
# 이미지 전체에 반복적으로 워터마크 텍스트 그리기 (지그재그 형태)
|
|
for y in range(0, height, zigzag_step):
|
|
for x in range(0, width, int(text_width * 3)): # 3배 너비 간격으로 반복
|
|
# 텍스트가 한 줄씩 지그재그 형태로 X축을 교차하여 이동
|
|
x_offset = (y // zigzag_step) % 2 * int(text_width * 1.5) # 짝수 행에서는 X축을 약간 이동
|
|
|
|
# 텍스트 레이어 생성
|
|
text_layer = Image.new("RGBA", (text_width, text_height), (255, 255, 255, 0))
|
|
text_draw = ImageDraw.Draw(text_layer)
|
|
|
|
# 텍스트 그리기
|
|
text_draw.text((0, 0), watermark_text, fill=(255, 255, 255, opacity), font=font)
|
|
|
|
# 텍스트 회전
|
|
rotated_text_layer = text_layer.rotate(angle, expand=1)
|
|
|
|
# 회전된 텍스트를 워터마크 레이어에 추가
|
|
watermark_layer.paste(rotated_text_layer, (x + x_offset, y), rotated_text_layer)
|
|
|
|
# 원본 이미지와 워터마크 레이어 합성
|
|
watermark_image = Image.alpha_composite(watermark_image.convert("RGBA"), watermark_layer)
|
|
|
|
# 최종적으로 RGB 형식으로 변환 후 반환
|
|
return watermark_image.convert("RGB")
|
|
|
|
def base64_to_image(self, base64_data):
|
|
"""Base64 데이터를 이미지로 변환하는 함수"""
|
|
if base64_data.startswith('data:image'):
|
|
header, encoded = base64_data.split(',', 1)
|
|
img_data = base64.b64decode(encoded)
|
|
image = Image.open(BytesIO(img_data))
|
|
return image
|
|
else:
|
|
self.logger.log("유효하지 않은 Base64 이미지 데이터입니다.", level=logging.DEBUG)
|
|
return None
|
|
|
|
def image_to_base64(self, image):
|
|
# 이미지 Base64로 변환
|
|
buffer = io.BytesIO()
|
|
image.save(buffer, format="PNG")
|
|
base64_image = base64.b64encode(buffer.getvalue()).decode('utf-8')
|
|
return base64_image
|
|
|
|
def download_image_from_url(self, url, max_retries=3):
|
|
"""URL에서 이미지를 다운로드하고 PIL 이미지 객체로 반환"""
|
|
headers = {
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
|
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
|
|
"Accept-Language": "en-US,en;q=0.9",
|
|
"Accept-Encoding": "gzip, deflate, br",
|
|
"DNT": "1", # Do Not Track 요청 헤더
|
|
"Connection": "keep-alive",
|
|
"Upgrade-Insecure-Requests": "1",
|
|
"Cache-Control": "max-age=0"
|
|
}
|
|
|
|
retries = 0
|
|
while retries < max_retries:
|
|
try:
|
|
self.logger.log(f"이미지 URL 다운로드 중: {url}", level=logging.DEBUG)
|
|
response = requests.get(url, headers=headers, stream=True)
|
|
|
|
# 상태 코드가 200이 아니면 재시도
|
|
if response.status_code == 200:
|
|
# OpenCV로 이미지를 로드하여 변환
|
|
image = np.asarray(bytearray(response.content), dtype="uint8")
|
|
image = cv2.imdecode(image, cv2.IMREAD_COLOR)
|
|
|
|
# OpenCV에서 이미지를 PIL로 변환
|
|
if image is not None:
|
|
pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
|
|
return pil_image
|
|
else:
|
|
self.logger.log(f"이미지 파일 형식이 올바르지 않습니다. 대상 URL: {url}", level=logging.DEBUG)
|
|
return None
|
|
else:
|
|
self.logger.log(f"이미지 로딩 실패, HTTP 상태 코드: {response.status_code}. 재시도 {retries + 1}/{max_retries}", level=logging.DEBUG)
|
|
retries += 1
|
|
# await asyncio.sleep(random.randint(2, 5)) # 2~5초 대기 후 재시도
|
|
time.sleep(random.randint(2, 5))
|
|
|
|
except Exception as e:
|
|
self.logger.log(f"이미지 로딩 중 오류 발생: {e}. 재시도 {retries + 1}/{max_retries}", level=logging.DEBUG)
|
|
retries += 1
|
|
# await asyncio.sleep(random.randint(2, 5)) # 예외 발생 시 대기 후 재시도
|
|
time.sleep(random.randint(2, 5))
|
|
|
|
self.logger.log("이미지 다운로드 최대 재시도 횟수를 초과했습니다.", level=logging.DEBUG)
|
|
return None
|
|
|
|
def process_clipboard(self, original_url, is_success_translated, toggle_states, path=None, is_thumb=False):
|
|
"""클립보드의 내용을 처리하고, 필요한 경우 이미지 변환, 크롭 또는 클립보드 비우기"""
|
|
|
|
try:
|
|
is_watermark = toggle_states.get('watermark')
|
|
self.logger.log(f"is_watermark : {is_watermark}", level=logging.DEBUG)
|
|
|
|
watermark_text = toggle_states.get('watermark_text')
|
|
self.logger.log(f"watermark_text : {watermark_text}", level=logging.DEBUG)
|
|
|
|
opacity_percent = toggle_states.get('opacity_percent')
|
|
self.logger.log(f"opacity_percent : {opacity_percent}", level=logging.DEBUG)
|
|
|
|
clipboard_data = self.get_clipboard_data()
|
|
|
|
self.logger.log("clipboard_data", level=logging.DEBUG)
|
|
self.logger.log(f"{clipboard_data}", level=logging.DEBUG)
|
|
self.logger.log(f"============================", level=logging.DEBUG)
|
|
|
|
# 1. 클립보드의 데이터가 Base64 이미지일 경우
|
|
if isinstance(clipboard_data, str) and clipboard_data.startswith('data:image'):
|
|
self.logger.log("[process_clipboard] data:image 감지 : 이미지 데이터로 변환", level=logging.INFO)
|
|
image = self.base64_to_image(clipboard_data)
|
|
if image:
|
|
width, _ = image.size
|
|
self.logger.log(f"Base64 이미지 크기: {width}px", level=logging.DEBUG)
|
|
|
|
# 가로 크기가 200픽셀 이상이면 크롭
|
|
if width >= 200:
|
|
self.logger.log("이미지 가로 크기 200픽셀 이상: 크롭 진행 중...", level=logging.DEBUG)
|
|
cropped_image = self.crop_image(image, is_thumb) # 크롭 메서드 사용
|
|
|
|
# 워터마크 추가
|
|
if is_watermark and not is_thumb: # is_thumb가 True라면 워터마크 추가를 건너뜁니다
|
|
self.logger.log("워터마크 추가 중...", level=logging.DEBUG)
|
|
cropped_watermark_image = self.add_watermark(cropped_image, watermark_text, opacity_percent) # 워터마크 추가
|
|
cropped_image = cropped_watermark_image
|
|
|
|
self.set_image_to_clipboard(cropped_image) # 클립보드에 저장
|
|
if path:
|
|
self.logger.log("이미지 저장 시도...", level=logging.DEBUG)
|
|
self.save_image_to_path(cropped_image, path)
|
|
else:
|
|
self.logger.log("이미지 가로 크기 200픽셀 이하: 클립보드 비움.", level=logging.DEBUG)
|
|
self.clear_clipboard()
|
|
else:
|
|
self.logger.log("Base64 이미지 변환 실패.", level=logging.DEBUG)
|
|
|
|
# 2. 클립보드에 이미지가 있을 경우
|
|
elif isinstance(clipboard_data, Image.Image):
|
|
self.logger.log("[process_clipboard] 클립보드 이미지 확인", level=logging.INFO)
|
|
|
|
image = clipboard_data
|
|
width, _ = image.size
|
|
self.logger.log(f"클립보드에 있는 이미지 크기: {width}px", level=logging.DEBUG)
|
|
|
|
if width >= 200:
|
|
self.logger.log("이미지 가로 크기 200픽셀 이상: 크롭 진행 중...", level=logging.DEBUG)
|
|
cropped_image = self.crop_image(image, is_thumb) # 크롭 메서드 사용
|
|
|
|
# 워터마크 추가
|
|
if is_watermark and not is_thumb: # is_thumb가 True라면 워터마크 추가를 건너뜁니다
|
|
self.logger.log("워터마크 추가 중...", level=logging.DEBUG)
|
|
cropped_watermark_image = self.add_watermark(cropped_image, watermark_text, opacity_percent) # 워터마크 추가
|
|
cropped_image = cropped_watermark_image
|
|
|
|
self.set_image_to_clipboard(cropped_image) # 클립보드에 저장
|
|
if path:
|
|
self.logger.log("이미지 저장 시도...", level=logging.DEBUG)
|
|
self.save_image_to_path(cropped_image, path)
|
|
|
|
else:
|
|
self.logger.log("이미지 가로 크기 200픽셀 이하: 클립보드 비움.", level=logging.DEBUG)
|
|
self.clear_clipboard()
|
|
|
|
# 3. 클립보드에 데이터가 없거나 html > whale-ocr 처리
|
|
elif clipboard_data == "html > whale-ocr" or clipboard_data is None or not is_success_translated:
|
|
if clipboard_data == "html > whale-ocr":
|
|
self.logger.log("[process_clipboard] html > whale-ocr 감지 : 이미지 번역 실패 확인", level=logging.INFO)
|
|
elif clipboard_data is None:
|
|
self.logger.log("[process_clipboard] 클립보드에 이미지 없음", level=logging.INFO)
|
|
elif is_success_translated is None:
|
|
self.logger.log("[process_clipboard] 번역 실패로 인한 원본이미지 다운로드", level=logging.INFO)
|
|
|
|
if original_url:
|
|
image = self.download_image_from_url(original_url)
|
|
if image:
|
|
self.logger.log("원본 이미지 다운로드 성공!", level=logging.DEBUG)
|
|
|
|
self.set_image_to_clipboard(image) # 크롭 없이 저장
|
|
if path:
|
|
self.logger.log("이미지 저장 시도...", level=logging.DEBUG)
|
|
self.save_image_to_path(image, path)
|
|
else:
|
|
self.logger.log("원본 이미지 다운로드 실패.", level=logging.DEBUG)
|
|
else:
|
|
self.logger.log("원본 이미지 URL을 찾을 수 없습니다.", level=logging.DEBUG)
|
|
|
|
except Exception as e:
|
|
self.logger.log(f"클립보드에서 이미지를 처리하는 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
|
|
|
|
|
|
def process_clipboard_to_save_path(self, original_url, is_success_translated, toggle_states, path=None, is_thumb=False):
|
|
"""클립보드의 내용을 처리하고, 필요한 경우 이미지 변환, 크롭 또는 클립보드 비우기"""
|
|
|
|
try:
|
|
is_watermark = toggle_states.get('watermark')
|
|
self.logger.log(f"is_watermark : {is_watermark}", level=logging.DEBUG)
|
|
|
|
watermark_text = toggle_states.get('watermark_text')
|
|
self.logger.log(f"watermark_text : {watermark_text}", level=logging.DEBUG)
|
|
|
|
opacity_percent = toggle_states.get('opacity_percent')
|
|
self.logger.log(f"opacity_percent : {opacity_percent}", level=logging.DEBUG)
|
|
|
|
clipboard_data = self.get_clipboard_data()
|
|
|
|
self.logger.log("clipboard_data", level=logging.DEBUG)
|
|
self.logger.log(f"{clipboard_data}", level=logging.DEBUG)
|
|
self.logger.log(f"============================", level=logging.DEBUG)
|
|
|
|
# 1. 클립보드의 데이터가 Base64 이미지일 경우
|
|
if isinstance(clipboard_data, str) and clipboard_data.startswith('data:image'):
|
|
self.logger.log("[process_clipboard] data:image 감지 : 이미지 데이터로 변환", level=logging.INFO)
|
|
image = self.base64_to_image(clipboard_data)
|
|
if image:
|
|
width, _ = image.size
|
|
self.logger.log(f"Base64 이미지 크기: {width}px", level=logging.DEBUG)
|
|
|
|
# 가로 크기가 200픽셀 이상이면 크롭
|
|
if width >= 200:
|
|
self.logger.log("이미지 가로 크기 200픽셀 이상: 크롭 진행 중...", level=logging.DEBUG)
|
|
cropped_image = self.crop_image(image, is_thumb) # 크롭 메서드 사용
|
|
|
|
# 워터마크 추가
|
|
if is_watermark and not is_thumb: # is_thumb가 True라면 워터마크 추가를 건너뜁니다
|
|
self.logger.log("워터마크 추가 중...", level=logging.DEBUG)
|
|
cropped_watermark_image = self.add_watermark(cropped_image, watermark_text, opacity_percent) # 워터마크 추가
|
|
cropped_image = cropped_watermark_image
|
|
|
|
if path:
|
|
self.logger.log("이미지 저장 시도...", level=logging.DEBUG)
|
|
return self.save_image_to_path(cropped_image, path)
|
|
else:
|
|
self.set_image_to_clipboard(cropped_image) # 클립보드에 저장
|
|
|
|
else:
|
|
self.logger.log("이미지 가로 크기 200픽셀 이하로 처리불가: 클립보드 비움.", level=logging.DEBUG)
|
|
self.clear_clipboard()
|
|
else:
|
|
self.logger.log("Base64 이미지 변환 실패.", level=logging.DEBUG)
|
|
|
|
# 2. 클립보드에 이미지가 있을 경우
|
|
elif isinstance(clipboard_data, Image.Image):
|
|
self.logger.log("[process_clipboard] 클립보드 이미지 확인", level=logging.INFO)
|
|
|
|
image = clipboard_data
|
|
width, _ = image.size
|
|
self.logger.log(f"클립보드에 있는 이미지 크기: {width}px", level=logging.DEBUG)
|
|
|
|
if width >= 200:
|
|
self.logger.log("이미지 가로 크기 200픽셀 이상: 크롭 진행 중...", level=logging.DEBUG)
|
|
cropped_image = self.crop_image(image, is_thumb) # 크롭 메서드 사용
|
|
|
|
# 워터마크 추가
|
|
if is_watermark and not is_thumb: # is_thumb가 True라면 워터마크 추가를 건너뜁니다
|
|
self.logger.log("워터마크 추가 중...", level=logging.DEBUG)
|
|
cropped_watermark_image = self.add_watermark(cropped_image, watermark_text, opacity_percent) # 워터마크 추가
|
|
cropped_image = cropped_watermark_image
|
|
|
|
if path:
|
|
self.logger.log("이미지 저장 시도...", level=logging.DEBUG)
|
|
return self.save_image_to_path(cropped_image, path)
|
|
else:
|
|
self.set_image_to_clipboard(cropped_image) # 클립보드에 저장
|
|
|
|
else:
|
|
self.logger.log("이미지 가로 크기 200픽셀 이하로 처리불가: 클립보드 비움.", level=logging.DEBUG)
|
|
self.clear_clipboard()
|
|
|
|
# 3. 클립보드에 데이터가 없거나 html > whale-ocr 처리
|
|
elif clipboard_data == "html > whale-ocr" or clipboard_data is None or not is_success_translated or clipboard_data.startswith("https://") or clipboard_data.startswith("http://"):
|
|
if clipboard_data == "html > whale-ocr":
|
|
self.logger.log("[process_clipboard] html > whale-ocr 감지 : 이미지 번역 실패 확인", level=logging.INFO)
|
|
elif clipboard_data is None:
|
|
self.logger.log("[process_clipboard] 클립보드에 이미지 없음", level=logging.INFO)
|
|
elif is_success_translated is None:
|
|
self.logger.log("[process_clipboard] 번역 실패로 인한 원본이미지 다운로드", level=logging.INFO)
|
|
elif clipboard_data.startswith("https://") or clipboard_data.startswith("http://"):
|
|
self.logger.log("[process_clipboard] 타임아웃으로 인한 번역 실패 - 원본이미지 다운로드", level=logging.INFO)
|
|
|
|
if original_url:
|
|
image = self.download_image_from_url(original_url)
|
|
if image:
|
|
self.logger.log("원본 이미지 다운로드 성공!", level=logging.DEBUG)
|
|
|
|
if path:
|
|
self.logger.log("이미지 저장 시도...", level=logging.DEBUG)
|
|
return self.save_image_to_path(image, path)
|
|
else:
|
|
self.set_image_to_clipboard(cropped_image) # 클립보드에 저장
|
|
else:
|
|
self.logger.log("원본 이미지 다운로드 실패.", level=logging.DEBUG)
|
|
else:
|
|
self.logger.log("원본 이미지 URL을 찾을 수 없습니다.", level=logging.DEBUG)
|
|
|
|
except Exception as e:
|
|
self.logger.log(f"클립보드에서 이미지를 처리하는 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
|
|
|
|
def process_clipboard_to_save_path_with_local_hosted_image(self, local_image_path, is_success_translated, toggle_states, path=None, is_thumb=False):
|
|
"""클립보드의 내용을 처리하고, 필요한 경우 이미지 변환, 크롭 또는 클립보드 비우기
|
|
|
|
Returns:
|
|
str: 처리된 이미지 파일 경로 (성공 시)
|
|
str: 원본 이미지 파일 경로 (실패 시)
|
|
"""
|
|
|
|
# 매개변수 유효성 검사
|
|
if not local_image_path or not os.path.exists(local_image_path):
|
|
self.logger.log(f"유효하지 않은 로컬 이미지 경로: {local_image_path}", level=logging.ERROR)
|
|
return local_image_path if local_image_path else None
|
|
|
|
if not toggle_states:
|
|
self.logger.log("toggle_states가 제공되지 않았습니다", level=logging.WARNING)
|
|
toggle_states = {}
|
|
|
|
try:
|
|
is_watermark = toggle_states.get('watermark', False)
|
|
self.logger.log(f"is_watermark : {is_watermark}", level=logging.DEBUG)
|
|
|
|
watermark_text = toggle_states.get('watermark_text', '')
|
|
self.logger.log(f"watermark_text : {watermark_text}", level=logging.DEBUG)
|
|
|
|
opacity_percent = toggle_states.get('opacity_percent', 20)
|
|
self.logger.log(f"opacity_percent : {opacity_percent}", level=logging.DEBUG)
|
|
|
|
clipboard_data = self.get_clipboard_data()
|
|
|
|
self.logger.log(f"type(clipboard_data) : {type(clipboard_data)}", level=logging.DEBUG)
|
|
self.logger.log(f"============================", level=logging.DEBUG)
|
|
|
|
# 1. 클립보드의 데이터가 Base64 이미지일 경우
|
|
if isinstance(clipboard_data, str) and clipboard_data.startswith('data:image'):
|
|
self.logger.log("[process_clipboard] data:image 감지 : 이미지 데이터로 변환", level=logging.INFO)
|
|
image = self.base64_to_image(clipboard_data)
|
|
if image:
|
|
width, _ = image.size
|
|
self.logger.log(f"Base64 이미지 크기: {width}px", level=logging.DEBUG)
|
|
|
|
# 가로 크기가 200픽셀 이상이면 크롭
|
|
if width >= 200:
|
|
self.logger.log("이미지 가로 크기 200픽셀 이상: 크롭 진행 중...", level=logging.DEBUG)
|
|
cropped_image = self.crop_image(image, is_thumb) # 크롭 메서드 사용
|
|
|
|
# 워터마크 추가
|
|
if is_watermark and not is_thumb: # is_thumb가 True라면 워터마크 추가를 건너뜁니다
|
|
self.logger.log("워터마크 추가 중...", level=logging.DEBUG)
|
|
cropped_watermark_image = self.add_watermark(cropped_image, watermark_text, opacity_percent) # 워터마크 추가
|
|
cropped_image = cropped_watermark_image
|
|
|
|
if path:
|
|
self.logger.log("이미지 저장 시도...", level=logging.DEBUG)
|
|
saved_path = self.save_image_to_path(cropped_image, path)
|
|
return saved_path if saved_path else local_image_path
|
|
else:
|
|
self.set_image_to_clipboard(cropped_image) # 클립보드에 저장
|
|
return local_image_path # path가 없으면 원본 경로 반환
|
|
|
|
else:
|
|
self.logger.log("이미지 가로 크기 200픽셀 이하로 처리불가: 클립보드 비움.", level=logging.DEBUG)
|
|
self.clear_clipboard()
|
|
return local_image_path
|
|
else:
|
|
self.logger.log("Base64 이미지 변환 실패.", level=logging.DEBUG)
|
|
return local_image_path
|
|
|
|
# 2. 클립보드에 이미지가 있을 경우
|
|
elif isinstance(clipboard_data, Image.Image):
|
|
self.logger.log("[process_clipboard] 클립보드 이미지 확인", level=logging.INFO)
|
|
|
|
image = clipboard_data
|
|
width, _ = image.size
|
|
self.logger.log(f"클립보드에 있는 이미지 크기: {width}px", level=logging.DEBUG)
|
|
|
|
if width >= 200:
|
|
self.logger.log("이미지 가로 크기 200픽셀 이상: 크롭 진행 중...", level=logging.DEBUG)
|
|
cropped_image = self.crop_image(image, is_thumb) # 크롭 메서드 사용
|
|
|
|
# 워터마크 추가
|
|
if is_watermark and not is_thumb: # is_thumb가 True라면 워터마크 추가를 건너뜁니다
|
|
self.logger.log("워터마크 추가 중...", level=logging.DEBUG)
|
|
cropped_watermark_image = self.add_watermark(cropped_image, watermark_text, opacity_percent) # 워터마크 추가
|
|
cropped_image = cropped_watermark_image
|
|
|
|
if path:
|
|
self.logger.log("이미지 저장 시도...", level=logging.DEBUG)
|
|
saved_path = self.save_image_to_path(cropped_image, path)
|
|
return saved_path if saved_path else local_image_path
|
|
else:
|
|
self.set_image_to_clipboard(cropped_image) # 클립보드에 저장
|
|
return local_image_path # path가 없으면 원본 경로 반환
|
|
|
|
else:
|
|
self.logger.log("이미지 가로 크기 200픽셀 이하로 처리불가: 클립보드 비움.", level=logging.DEBUG)
|
|
self.clear_clipboard()
|
|
return local_image_path
|
|
|
|
# 3. 클립보드에 데이터가 없거나 html > whale-ocr 처리
|
|
elif clipboard_data == "html > whale-ocr" or clipboard_data is None or not is_success_translated or clipboard_data.startswith("https://") or clipboard_data.startswith("http://"):
|
|
if clipboard_data == "html > whale-ocr":
|
|
self.logger.log("[process_clipboard] html > whale-ocr 감지 : 이미지 번역 실패 확인", level=logging.INFO)
|
|
elif clipboard_data is None:
|
|
self.logger.log("[process_clipboard] 클립보드에 이미지 없음", level=logging.INFO)
|
|
elif not is_success_translated:
|
|
self.logger.log("[process_clipboard] 번역 실패로 인한 원본이미지 사용", level=logging.INFO)
|
|
elif clipboard_data.startswith("https://") or clipboard_data.startswith("http://"):
|
|
self.logger.log("[process_clipboard] 타임아웃으로 인한 번역 실패 - 원본이미지 사용", level=logging.INFO)
|
|
|
|
return local_image_path
|
|
|
|
# 4. 기타 예상하지 못한 클립보드 데이터
|
|
else:
|
|
self.logger.log(f"[process_clipboard] 예상하지 못한 클립보드 데이터 타입: {type(clipboard_data)}", level=logging.WARNING)
|
|
return local_image_path
|
|
|
|
except Exception as e:
|
|
self.logger.log(f"클립보드에서 이미지를 처리하는 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
|
|
return local_image_path # 오류 시 원본 경로 반환
|
|
|
|
def is_clipboard_image(self):
|
|
"""클립보드에 이미지가 있는지 확인하는 함수"""
|
|
max_attempts = 5
|
|
attempt = 0
|
|
|
|
while attempt < max_attempts:
|
|
try:
|
|
win32clipboard.OpenClipboard()
|
|
is_clipboard_image_flag = win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_DIB)
|
|
win32clipboard.CloseClipboard()
|
|
|
|
if is_clipboard_image_flag:
|
|
self.logger.log("클립보드에 이미지가 존재합니다.", level=logging.DEBUG)
|
|
else:
|
|
self.logger.log("클립보드에 이미지가 없습니다.", level=logging.DEBUG)
|
|
|
|
return is_clipboard_image_flag
|
|
|
|
except Exception as e:
|
|
attempt += 1
|
|
# 클립보드가 열려있으면 닫기 시도
|
|
try:
|
|
win32clipboard.CloseClipboard()
|
|
except:
|
|
pass
|
|
|
|
self.logger.log(f"클립보드 이미지 확인 중 오류 발생 (시도 {attempt}/{max_attempts}): {e}", level=logging.WARNING)
|
|
if attempt < max_attempts:
|
|
time.sleep(0.5) # 0.5초 대기 후 재시도
|
|
else:
|
|
self.logger.log(f"클립보드 이미지 확인 중 최대 시도 횟수 초과: {e}", level=logging.ERROR, exc_info=True)
|
|
return False
|
|
|
|
def get_image_from_clipboard(self):
|
|
"""클립보드에서 이미지를 가져오는 함수"""
|
|
max_attempts = 5
|
|
attempt = 0
|
|
|
|
while attempt < max_attempts:
|
|
try:
|
|
win32clipboard.OpenClipboard()
|
|
if self.is_clipboard_image():
|
|
dib_data = win32clipboard.GetClipboardData(win32clipboard.CF_DIB)
|
|
win32clipboard.CloseClipboard()
|
|
image = Image.open(BytesIO(dib_data))
|
|
return image
|
|
else:
|
|
win32clipboard.CloseClipboard()
|
|
self.logger.log("클립보드에 이미지가 없습니다.", level=logging.DEBUG)
|
|
return None
|
|
except Exception as e:
|
|
attempt += 1
|
|
# 클립보드가 열려있으면 닫기 시도
|
|
try:
|
|
win32clipboard.CloseClipboard()
|
|
except:
|
|
pass
|
|
|
|
self.logger.log(f"클립보드에서 이미지를 가져오는 중 오류 발생 (시도 {attempt}/{max_attempts}): {e}", level=logging.WARNING)
|
|
if attempt < max_attempts:
|
|
time.sleep(0.5) # 0.5초 대기 후 재시도
|
|
else:
|
|
self.logger.log(f"클립보드에서 이미지를 가져오는 중 최대 시도 횟수 초과: {e}", level=logging.ERROR, exc_info=True)
|
|
return None
|
|
|
|
def clear_clipboard(self):
|
|
"""클립보드를 비우는 함수"""
|
|
max_attempts = 5
|
|
attempt = 0
|
|
success = False
|
|
|
|
while attempt < max_attempts and not success:
|
|
try:
|
|
# 먼저 pywinauto로 시도
|
|
try:
|
|
pywinauto.clipboard.EmptyClipboard()
|
|
success = True
|
|
except:
|
|
# pywinauto 실패 시 win32clipboard로 시도
|
|
win32clipboard.OpenClipboard()
|
|
win32clipboard.EmptyClipboard()
|
|
win32clipboard.CloseClipboard()
|
|
success = True
|
|
|
|
self.logger.log(f"클립보드가 비워졌습니다. (시도 {attempt+1}/{max_attempts})", level=logging.DEBUG)
|
|
except Exception as e:
|
|
attempt += 1
|
|
self.logger.log(f"클립보드를 비우는 중 오류 발생 (시도 {attempt}/{max_attempts}): {e}", level=logging.WARNING)
|
|
if attempt < max_attempts:
|
|
time.sleep(0.5) # 0.5초 대기 후 재시도
|
|
|
|
if not success:
|
|
self.logger.log("최대 시도 횟수를 초과하여 클립보드를 비우지 못했습니다.", level=logging.ERROR)
|
|
|
|
def crop_image(self, image, is_thumb=False, crop_percentage=0.01):
|
|
"""이미지를 주어진 퍼센트만큼 크롭하는 함수"""
|
|
if is_thumb:
|
|
crop_percentage = 0.03
|
|
self.logger.log(f"썸네일 이미지 이므로 크롭 3%로 조정", level=logging.DEBUG)
|
|
|
|
width, height = image.size
|
|
left = width * crop_percentage
|
|
top = height * crop_percentage
|
|
right = width * (1 - crop_percentage)
|
|
bottom = height * (1 - crop_percentage)
|
|
|
|
cropped_image = image.crop((left, top, right, bottom))
|
|
|
|
if self.debug:
|
|
# 디버그 모드일 경우 크롭 전후 다양한 비율로 이미지 저장
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
original_image_path = os.path.join(os.getcwd(), f"original_image_{timestamp}.png")
|
|
image.save(original_image_path) # 크롭 전 이미지 저장
|
|
self.logger.log(f"크롭 전 이미지 저장됨: {original_image_path}", level=logging.DEBUG)
|
|
|
|
# 1%, 2%, 3% 크롭 이미지 저장
|
|
crop_alternatives = [0.01, 0.02, 0.03]
|
|
for crop in crop_alternatives:
|
|
left_alt = width * crop
|
|
top_alt = height * crop
|
|
right_alt = width * (1 - crop)
|
|
bottom_alt = height * (1 - crop)
|
|
|
|
cropped_alt_image = image.crop((left_alt, top_alt, right_alt, bottom_alt))
|
|
cropped_image_path = os.path.join(os.getcwd(), f"cropped_image_{int(crop*100)}_{timestamp}.png")
|
|
cropped_alt_image.save(cropped_image_path)
|
|
self.logger.log(f"{int(crop*100)}% 크롭된 이미지 저장됨: {cropped_image_path}", level=logging.DEBUG)
|
|
|
|
return cropped_image |