macOS에서 화면의 텍스트를 실시간으로 캡처하고 싶은데 TextSniper 같은 유료 앱을 구매하기는 부담스러워요. 파이썬과 PyObjC를 활용하면 직접 만들 수 있어요. Apple의 Vision 프레임워크가 이미 강력한 OCR 엔진을 제공하고 있으니 이를 활용해보세요.
PyObjC 환경 설정과 필요한 라이브러리 준비하기
먼저 PyObjC와 필요한 패키지들을 설치해요. 터미널을 열고 다음 명령어를 실행하세요.
pip install pyobjc-core pyobjc-framework-Cocoa pyobjc-framework-Vision
pip install pyobjc-framework-Quartz pyperclip
PyObjC는 파이썬에서 macOS의 네이티브 프레임워크를 직접 호출할 수 있게 해주는 브릿지 역할을 해요. Vision 프레임워크는 2023년부터 한국어 인식도 공식 지원하기 시작했으니 한글 텍스트도 문제없이 추출할 수 있어요.
CoreGraphics로 화면 영역 캡처하기
화면의 특정 영역을 캡처하는 기능부터 구현해봐요. CoreGraphics API를 사용하면 지정한 좌표의 화면을 스크린샷으로 가져올 수 있어요.
import Quartz
from Foundation import NSRect
def capture_screen_region(x, y, width, height):
"""지정된 영역의 화면을 캡처해요"""
# CGRect 형태로 캡처할 영역 정의
rect = NSRect((x, y), (width, height))
# 화면 캡처 실행
# kCGWindowListOptionOnScreenOnly: 화면에 보이는 창만 캡처
# kCGNullWindowID: 모든 창을 대상으로
# kCGWindowImageDefault: 기본 이미지 옵션 사용
image = Quartz.CGWindowListCreateImage(
rect,
Quartz.kCGWindowListOptionOnScreenOnly,
Quartz.kCGNullWindowID,
Quartz.kCGWindowImageDefault
)
return image
이 함수는 화면의 특정 좌표 (x, y)에서 시작해 width와 height 크기만큼의 영역을 캡처해요. Retina 디스플레이에서도 정확한 픽셀 단위로 작동하도록 CoreGraphics가 자동으로 처리해줘요.
Vision 프레임워크로 OCR 처리하기
캡처한 이미지에서 텍스트를 추출하는 OCR 엔진을 구현해요. Vision 프레임워크의 VNRecognizeTextRequest를 사용하면 높은 정확도로 텍스트를 인식할 수 있어요.
import Vision
from Cocoa import NSImage, NSBitmapImageRep, CIImage
def perform_ocr(cg_image):
"""캡처한 이미지에서 텍스트를 추출해요"""
# CGImage를 CIImage로 변환
# Vision 프레임워크는 CIImage 형식을 요구해요
ci_image = CIImage.imageWithCGImage_(cg_image)
# OCR 요청 핸들러 생성
request_handler = Vision.VNImageRequestHandler.alloc().initWithCIImage_options_(
ci_image, None
)
# 텍스트 인식 요청 생성
# accurate 모드: 정확도 우선 (fast 모드도 있어요)
text_request = Vision.VNRecognizeTextRequest.alloc().init()
text_request.setRecognitionLevel_(Vision.VNRequestTextRecognitionLevelAccurate)
# 한국어 인식 설정 추가
# 지원 언어: ko-KR, en-US, ja-JP, zh-CN 등
text_request.setRecognitionLanguages_(["ko-KR", "en-US"])
# OCR 요청 실행
error = None
success = request_handler.performRequests_error_([text_request], error)
if not success:
print(f"OCR 처리 실패: {error}")
return ""
# 인식된 텍스트 추출
extracted_text = []
observations = text_request.results()
for observation in observations:
# confidence가 0.5 이상인 텍스트만 추출
if observation.confidence() > 0.5:
extracted_text.append(observation.text())
return "\n".join(extracted_text)
Vision 프레임워크는 로컬에서 완전히 처리되기 때문에 네트워크 지연이 없고 개인정보 유출 걱정도 없어요. 텍스트 인식 정확도가 매우 높아서 인쇄된 문서, 영수증, 명함 등 다양한 텍스트를 정확하게 추출할 수 있어요.
실시간 모니터링과 주기적 캡처 구현하기
지정된 영역을 실시간으로 모니터링하려면 타이머를 사용해 주기적으로 캡처와 OCR을 반복 실행해야 해요.
import threading
import time
class ScreenTextMonitor:
def __init__(self, x, y, width, height, interval=1.0):
"""실시간 화면 텍스트 모니터 초기화"""
self.rect = (x, y, width, height)
self.interval = interval # 캡처 주기 (초 단위)
self.is_monitoring = False
self.last_text = "" # 이전 텍스트 저장 (중복 방지)
def start_monitoring(self):
"""모니터링 시작"""
self.is_monitoring = True
self.monitor_thread = threading.Thread(target=self._monitor_loop)
self.monitor_thread.start()
def _monitor_loop(self):
"""실제 모니터링 루프"""
while self.is_monitoring:
# 화면 캡처
image = capture_screen_region(*self.rect)
if image:
# OCR 처리
text = perform_ocr(image)
# 텍스트가 변경되었을 때만 처리
if text and text != self.last_text:
self.last_text = text
self._handle_new_text(text)
# 지정된 간격만큼 대기
time.sleep(self.interval)
def _handle_new_text(self, text):
"""새로운 텍스트 감지 시 처리"""
print(f"새 텍스트 감지: {text}")
# 클립보드에 자동 복사
self.copy_to_clipboard(text)
캡처 주기를 너무 짧게 설정하면 CPU 사용량이 높아져요. 일반적으로 0.5초에서 1초 간격이 적절해요. 텍스트가 변경되었을 때만 처리하도록 해서 불필요한 작업을 줄였어요.
NSPasteboard로 클립보드 자동 복사 구현하기
추출한 텍스트를 클립보드에 자동으로 복사하는 기능을 추가해요. macOS의 네이티브 클립보드 API인 NSPasteboard를 사용하면 안정적으로 작동해요.
from AppKit import NSPasteboard, NSPasteboardTypeString
def copy_to_clipboard(text):
"""텍스트를 시스템 클립보드에 복사해요"""
# 일반 클립보드 가져오기
pasteboard = NSPasteboard.generalPasteboard()
# 기존 내용 지우기 - 이 단계가 중요해요!
# macOS에서 클립보드 업데이트 문제를 방지
pasteboard.clearContents()
# 새 텍스트 설정
success = pasteboard.setString_forType_(text, NSPasteboardTypeString)
if success:
print("클립보드 복사 완료")
# 알림 표시 (선택사항)
show_notification("텍스트 복사됨", text[:50])
return success
pyperclip 같은 서드파티 라이브러리도 사용할 수 있지만, NSPasteboard를 직접 사용하면 더 안정적이고 빠르게 작동해요. clearContents() 메서드를 호출하지 않으면 가끔 클립보드가 업데이트되지 않는 문제가 발생할 수 있어요.
메뉴바 앱으로 만들기
이제 이 모든 기능을 메뉴바 앱으로 묶어봐요. 사용자가 쉽게 접근할 수 있도록 상태 표시와 제어 메뉴를 추가해요.
from AppKit import NSApplication, NSStatusBar, NSMenu, NSMenuItem
import rumps
class TextExtractorApp(rumps.App):
def __init__(self):
super(TextExtractorApp, self).__init__("📝") # 메뉴바 아이콘
self.monitor = None
self.monitoring_area = None
# 메뉴 항목 설정
self.menu = [
rumps.MenuItem("영역 선택", callback=self.select_area),
rumps.MenuItem("모니터링 시작/중지", callback=self.toggle_monitoring),
None, # 구분선
rumps.MenuItem("설정", callback=self.open_settings),
]
@rumps.clicked("영역 선택")
def select_area(self, _):
"""마우스로 영역을 선택해요"""
# 영역 선택 UI 구현
# 마우스 드래그 이벤트 처리
print("마우스로 캡처할 영역을 드래그하세요")
@rumps.clicked("모니터링 시작/중지")
def toggle_monitoring(self, sender):
"""모니터링 시작/중지 토글"""
if self.monitor and self.monitor.is_monitoring:
self.monitor.stop_monitoring()
sender.title = "모니터링 시작"
self.icon = "📝" # 기본 아이콘
else:
if self.monitoring_area:
self.monitor = ScreenTextMonitor(*self.monitoring_area)
self.monitor.start_monitoring()
sender.title = "모니터링 중지"
self.icon = "🔴" # 활성 상태 표시
else:
rumps.alert("먼저 영역을 선택하세요")
rumps 라이브러리를 사용하면 파이썬으로 간단하게 macOS 메뉴바 앱을 만들 수 있어요. 아이콘으로 현재 상태를 표시하고, 메뉴에서 주요 기능에 접근할 수 있도록 구성했어요.
단축키 자동화 연동하기
글로벌 단축키를 등록해서 빠르게 실행할 수 있도록 해요. Quartz Event Tap을 사용하면 시스템 전역에서 키보드 이벤트를 감지할 수 있어요.
import Quartz
from Cocoa import NSEvent
def register_hotkey():
"""Command+Shift+T 단축키 등록"""
def keyboard_callback(proxy, type, event, refcon):
# 키보드 이벤트 감지
if type == Quartz.kCGEventKeyDown:
keycode = Quartz.CGEventGetIntegerValueField(
event, Quartz.kCGKeyboardEventKeycode
)
flags = Quartz.CGEventGetFlags(event)
# Command+Shift+T 체크 (T 키코드: 17)
if keycode == 17 and (flags & Quartz.kCGEventFlagMaskCommand) and \
(flags & Quartz.kCGEventFlagMaskShift):
# OCR 실행
trigger_ocr_capture()
return None # 이벤트 소비
return event
# 이벤트 탭 생성 및 시작
mask = Quartz.CGEventMaskBit(Quartz.kCGEventKeyDown)
tap = Quartz.CGEventTapCreate(
Quartz.kCGSessionEventTap,
Quartz.kCGHeadInsertEventTap,
Quartz.kCGEventTapOptionDefault,
mask,
keyboard_callback,
None
)
# 런루프에 추가
source = Quartz.CFMachPortCreateRunLoopSource(None, tap, 0)
Quartz.CFRunLoopAddSource(
Quartz.CFRunLoopGetCurrent(),
source,
Quartz.kCFRunLoopDefaultMode
)
시스템 환경설정에서 접근성 권한을 허용해야 단축키가 작동해요. 사용자가 직접 단축키를 커스터마이징할 수 있도록 설정 메뉴를 추가하는 것도 좋은 방법이에요.
성능 최적화와 예외 처리
실제 사용 환경에서는 다양한 예외 상황이 발생할 수 있어요. 안정적인 작동을 위한 처리를 추가해요.
import logging
class RobustTextExtractor:
def __init__(self):
# 로깅 설정
logging.basicConfig(level=logging.INFO)
self.logger = logging.getLogger(__name__)
# 캐시로 중복 처리 방지
self.text_cache = {}
self.cache_timeout = 5 # 5초간 캐시 유지
def extract_with_retry(self, image, max_retries=3):
"""실패 시 재시도하는 OCR 처리"""
for attempt in range(max_retries):
try:
text = perform_ocr(image)
if text:
return text
except Exception as e:
self.logger.warning(f"OCR 시도 {attempt+1} 실패: {e}")
if attempt < max_retries - 1:
time.sleep(0.5) # 잠시 대기 후 재시도
return None
def handle_permission_error(self):
"""권한 오류 처리"""
# 화면 녹화 권한 확인
# 시스템 환경설정으로 안내
rumps.alert(
"권한 필요",
"시스템 환경설정 > 보안 및 개인 정보 보호 > 화면 녹화에서 "
"이 앱을 허용해주세요"
)
캐시를 활용해 동일한 텍스트를 반복 처리하지 않도록 최적화했어요. OCR 처리가 실패할 경우 자동으로 재시도하고, 권한 문제가 발생하면 사용자에게 안내 메시지를 표시해요.
Vision 프레임워크의 정확도는 이미지 품질에 크게 좌우되므로, 고해상도 Retina 디스플레이에서 더 좋은 결과를 얻을 수 있어요. 손글씨나 복잡한 배경의 텍스트는 인식률이 떨어질 수 있지만, 일반적인 화면 텍스트는 거의 완벽하게 추출해요.
파이썬과 PyObjC로 만든 이 실시간 화면 텍스트 추출기는 TextSniper 같은 유료 앱의 기능을 대부분 구현할 수 있어요. 무엇보다 오픈소스로 직접 만들었기 때문에 필요에 따라 기능을 추가하거나 수정할 수 있다는 장점이 있어요. 다국어 지원, 클립보드 히스토리, 텍스트 후처리 기능 등을 추가하면 더욱 강력한 도구로 발전시킬 수 있어요.