297 lines
14 KiB
Python
297 lines
14 KiB
Python
import base64
|
|
import pyperclip
|
|
import win32clipboard
|
|
from io import BytesIO
|
|
from PIL import Image
|
|
import requests
|
|
import numpy as np
|
|
import cv2
|
|
import time
|
|
import os
|
|
from datetime import datetime
|
|
import random
|
|
|
|
class ClipboardImageManager:
|
|
def __init__(self, app, logger, browser_controller, debug=False):
|
|
self.app = app
|
|
self.logger = logger
|
|
self.browser_controller = browser_controller # BrowserController 인스턴스를 전달받음
|
|
self.debug = debug # 디버그 플래그를 클래스 변수로 사용
|
|
|
|
self.debug = False
|
|
|
|
def get_clipboard_data(self):
|
|
"""클립보드의 텍스트 데이터를 가져옵니다."""
|
|
try:
|
|
return pyperclip.paste() # 클립보드의 텍스트 데이터를 가져옴
|
|
except Exception as e:
|
|
self.logger.debug(f"클립보드 데이터를 가져오는 중 오류 발생: {e}", exc_info=True)
|
|
return None
|
|
|
|
# def set_image_to_clipboard(self, image):
|
|
# """이미지를 클립보드에 넣는 함수 (Windows 전용)"""
|
|
# output = BytesIO()
|
|
# image.save(output, "BMP")
|
|
# data = output.getvalue()[14:] # BMP 헤더 제거
|
|
# output.close()
|
|
|
|
# # 클립보드에 이미지 데이터 넣기
|
|
# win32clipboard.OpenClipboard()
|
|
# win32clipboard.EmptyClipboard()
|
|
# win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
|
|
# win32clipboard.CloseClipboard()
|
|
|
|
def set_image_to_clipboard(self, image, crop_percentage=0.03, debug=False):
|
|
"""
|
|
이미지를 클립보드에 넣는 함수 (Windows 전용, 크롭 후)
|
|
|
|
:param image: PIL 이미지 객체
|
|
:param crop_percentage: 크롭할 비율 (0.05는 5% 크롭을 의미)
|
|
:param debug: True일 경우 크롭 전후 다양한 비율(3%, 5%, 7%)의 이미지를 디버그 용도로 저장
|
|
"""
|
|
# 이미지의 크기 계산 (크롭 비율 적용)
|
|
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 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.debug(f"크롭 전 이미지 저장됨: {original_image_path}")
|
|
|
|
# 3%, 5%, 7% 크롭 이미지 저장
|
|
crop_alternatives = [0.03, 0.05, 0.07]
|
|
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.debug(f"{int(crop*100)}% 크롭된 이미지 저장됨: {cropped_image_path}")
|
|
|
|
# 크롭된 이미지를 BMP 형식으로 변환하여 클립보드에 넣기
|
|
output = BytesIO()
|
|
cropped_image.save(output, "BMP")
|
|
data = output.getvalue()[14:] # BMP 헤더 제거
|
|
output.close()
|
|
|
|
# 클립보드에 이미지 데이터 넣기
|
|
win32clipboard.OpenClipboard()
|
|
win32clipboard.EmptyClipboard()
|
|
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
|
|
win32clipboard.CloseClipboard()
|
|
|
|
self.logger.debug(f"{crop_percentage*100}% 크롭된 이미지가 클립보드에 저장되었습니다.")
|
|
|
|
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.debug("유효하지 않은 Base64 이미지 데이터입니다.")
|
|
return None
|
|
|
|
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.debug(f"이미지 URL 다운로드 중: {url}")
|
|
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.debug(f"이미지 파일 형식이 올바르지 않습니다. 대상 URL: {url}")
|
|
return None
|
|
else:
|
|
self.logger.debug(f"이미지 로딩 실패, HTTP 상태 코드: {response.status_code}. 재시도 {retries + 1}/{max_retries}")
|
|
retries += 1
|
|
time.sleep(random.randint(2, 5)) # 2~5초 대기 후 재시도
|
|
except Exception as e:
|
|
self.logger.debug(f"이미지 로딩 중 오류 발생: {e}. 재시도 {retries + 1}/{max_retries}")
|
|
retries += 1
|
|
time.sleep(random.randint(2, 5)) # 예외 발생 시 대기 후 재시도
|
|
|
|
self.logger.debug("이미지 다운로드 최대 재시도 횟수를 초과했습니다.")
|
|
return None
|
|
|
|
def process_clipboard(self, original_url):
|
|
"""클립보드의 내용을 처리하고, 필요한 경우 이미지 변환, 크롭 또는 클립보드 비우기"""
|
|
clipboard_data = self.get_clipboard_data()
|
|
|
|
# 1. 클립보드의 데이터가 Base64 이미지일 경우
|
|
if clipboard_data.startswith('data:image'):
|
|
self.logger.info("data:image 감지 : 이미지 데이터로 변환")
|
|
image = self.base64_to_image(clipboard_data)
|
|
if image:
|
|
width, _ = image.size
|
|
self.logger.debug(f"Base64 이미지 크기: {width}px")
|
|
|
|
# 가로 크기가 200픽셀 이상이면 크롭
|
|
if width >= 200:
|
|
self.logger.debug("이미지 가로 크기 200픽셀 이상: 크롭 진행 중...")
|
|
cropped_image = self.crop_image(image) # 크롭 메서드 사용
|
|
self.set_image_to_clipboard(cropped_image) # 클립보드에 저장
|
|
else:
|
|
self.logger.debug("이미지 가로 크기 200픽셀 이하: 클립보드 비움.")
|
|
self.clear_clipboard()
|
|
else:
|
|
self.logger.debug("Base64 이미지 변환 실패.")
|
|
|
|
# 2. 클립보드에 이미지가 있을 경우
|
|
elif self.is_clipboard_image():
|
|
self.logger.info("클립보드 이미지 확인")
|
|
image = self.get_image_from_clipboard()
|
|
if image:
|
|
width, _ = image.size
|
|
self.logger.debug(f"클립보드에 있는 이미지 크기: {width}px")
|
|
|
|
if width >= 200:
|
|
self.logger.debug("이미지 가로 크기 200픽셀 이상: 크롭 진행 중...")
|
|
cropped_image = self.crop_image(image) # 크롭 메서드 사용
|
|
self.set_image_to_clipboard(cropped_image) # 클립보드에 저장
|
|
else:
|
|
self.logger.debug("이미지 가로 크기 200픽셀 이하: 클립보드 비움.")
|
|
self.clear_clipboard()
|
|
|
|
# 3. html > whale-ocr 처리
|
|
elif clipboard_data.strip() == "html > whale-ocr":
|
|
self.logger.info("html > whale-ocr 감지 : 이미지 번역 실패 확인")
|
|
if original_url:
|
|
image = self.download_image_from_url(original_url)
|
|
if image:
|
|
self.logger.debug("원본 이미지 다운로드 성공, 클립보드에 저장 중...")
|
|
self.set_image_to_clipboard(image) # 크롭 없이 저장
|
|
else:
|
|
self.logger.debug("원본 이미지 다운로드 실패.")
|
|
else:
|
|
self.logger.debug("원본 이미지 URL을 찾을 수 없습니다.")
|
|
|
|
else:
|
|
self.logger.debug("클립보드에 처리할 수 있는 데이터가 없습니다.")
|
|
|
|
def is_clipboard_image(self):
|
|
"""클립보드에 이미지가 있는지 확인하는 함수"""
|
|
return win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_DIB)
|
|
|
|
def get_image_from_clipboard(self):
|
|
"""클립보드에서 이미지를 가져오는 함수"""
|
|
try:
|
|
win32clipboard.OpenClipboard()
|
|
if self.is_clipboard_image():
|
|
dib_data = win32clipboard.GetClipboardData(win32clipboard.CF_DIB)
|
|
image = Image.open(BytesIO(dib_data))
|
|
return image
|
|
else:
|
|
self.logger.debug("클립보드에 이미지가 없습니다.")
|
|
except Exception as e:
|
|
self.logger.error(f"클립보드에서 이미지를 가져오는 중 오류 발생: {e}", exc_info=True)
|
|
finally:
|
|
win32clipboard.CloseClipboard()
|
|
|
|
return None
|
|
|
|
def clear_clipboard(self):
|
|
"""클립보드를 비우는 함수"""
|
|
try:
|
|
win32clipboard.OpenClipboard()
|
|
win32clipboard.EmptyClipboard()
|
|
self.logger.debug("클립보드가 비워졌습니다.")
|
|
except Exception as e:
|
|
self.logger.error(f"클립보드를 비우는 중 오류 발생: {e}", exc_info=True)
|
|
finally:
|
|
win32clipboard.CloseClipboard()
|
|
|
|
def crop_image(self, image, crop_percentage=0.01):
|
|
"""이미지를 주어진 퍼센트만큼 크롭하는 함수"""
|
|
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.debug(f"크롭 전 이미지 저장됨: {original_image_path}")
|
|
|
|
# 3%, 5%, 7% 크롭 이미지 저장
|
|
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.debug(f"{int(crop*100)}% 크롭된 이미지 저장됨: {cropped_image_path}")
|
|
|
|
return cropped_image
|
|
|
|
def set_image_to_clipboard(self, image):
|
|
"""이미지를 클립보드에 넣는 함수"""
|
|
output = BytesIO()
|
|
image.save(output, "BMP")
|
|
data = output.getvalue()[14:] # BMP 헤더 제거
|
|
output.close()
|
|
|
|
try:
|
|
win32clipboard.OpenClipboard()
|
|
win32clipboard.EmptyClipboard()
|
|
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
|
|
self.logger.debug("이미지가 클립보드에 저장되었습니다.")
|
|
except Exception as e:
|
|
self.logger.error(f"이미지를 클립보드에 저장하는 중 오류 발생: {e}", exc_info=True)
|
|
finally:
|
|
win32clipboard.CloseClipboard()
|
|
|
|
def base64_to_image(self, base64_data):
|
|
"""Base64 데이터를 이미지로 변환하는 함수"""
|
|
try:
|
|
header, encoded = base64_data.split(',', 1)
|
|
img_data = base64.b64decode(encoded)
|
|
image = Image.open(BytesIO(img_data))
|
|
return image
|
|
except Exception as e:
|
|
self.logger.error(f"Base64 이미지를 변환하는 중 오류 발생: {e}", exc_info=True)
|
|
return None
|