GameSentenceMiner 2.8.26__py3-none-any.whl → 2.8.27__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,3 @@
1
- try:
2
- from .secret import *
3
- except ImportError:
4
- pass
5
- from .config import Config
6
- from .screen_coordinate_picker import get_screen_selection
7
-
8
1
  try:
9
2
  import win32gui
10
3
  import win32ui
@@ -22,48 +15,117 @@ try:
22
15
  import platform
23
16
  from AppKit import NSData, NSImage, NSBitmapImageRep, NSDeviceRGBColorSpace, NSGraphicsContext, NSZeroPoint, NSZeroRect, NSCompositingOperationCopy
24
17
  from Quartz import CGWindowListCreateImageFromArray, kCGWindowImageBoundsIgnoreFraming, CGRectMake, CGRectNull, CGMainDisplayID, CGWindowListCopyWindowInfo, \
25
- CGWindowListCreateDescriptionFromArray, kCGWindowListOptionOnScreenOnly, kCGWindowListExcludeDesktopElements, kCGWindowName, kCGNullWindowID, \
26
- CGImageGetWidth, CGImageGetHeight, CGDataProviderCopyData, CGImageGetDataProvider, CGImageGetBytesPerRow
18
+ CGWindowListCreateDescriptionFromArray, kCGWindowListOptionOnScreenOnly, kCGWindowListExcludeDesktopElements, kCGWindowName, kCGNullWindowID, \
19
+ CGImageGetWidth, CGImageGetHeight, CGDataProviderCopyData, CGImageGetDataProvider, CGImageGetBytesPerRow
27
20
  from ScreenCaptureKit import SCContentFilter, SCScreenshotManager, SCShareableContent, SCStreamConfiguration, SCCaptureResolutionBest
28
21
  except ImportError:
29
22
  pass
30
23
 
31
-
24
+ import signal
32
25
  import threading
26
+ from pathlib import Path
27
+ import queue
28
+ import io
29
+ import re
30
+ import logging
31
+ import inspect
32
+
33
33
  import pyperclipfix
34
34
  import mss
35
35
  import asyncio
36
36
  import websockets
37
37
  import socketserver
38
38
  import queue
39
- import time
40
39
 
41
40
  from datetime import datetime
42
- from PIL import Image, ImageDraw
43
- from PIL import UnidentifiedImageError
41
+ from PIL import Image, ImageDraw, UnidentifiedImageError
42
+ from loguru import logger
44
43
  from pynput import keyboard
45
44
  from desktop_notifier import DesktopNotifierSync
46
45
  import psutil
47
46
 
48
47
  import inspect
49
48
  from .ocr import *
49
+ try:
50
+ from .secret import *
51
+ except ImportError:
52
+ pass
53
+ from .config import Config
54
+ from .screen_coordinate_picker import get_screen_selection
55
+ from ...configuration import get_temporary_directory
50
56
 
51
57
 
52
58
  config = None
53
59
 
54
60
 
55
- class WindowsClipboardThread(threading.Thread):
61
+ class ClipboardThread(threading.Thread):
56
62
  def __init__(self):
57
63
  super().__init__(daemon=True)
64
+ self.ignore_flag = config.get_general('ignore_flag')
65
+ self.delay_secs = config.get_general('delay_secs')
58
66
  self.last_update = time.time()
59
67
 
68
+ def are_images_identical(self, img1, img2):
69
+ if None in (img1, img2):
70
+ return img1 == img2
71
+
72
+ img1 = np.array(img1)
73
+ img2 = np.array(img2)
74
+
75
+ return (img1.shape == img2.shape) and (img1 == img2).all()
76
+
77
+ def normalize_macos_clipboard(self, img):
78
+ ns_data = NSData.dataWithBytes_length_(img, len(img))
79
+ ns_image = NSImage.alloc().initWithData_(ns_data)
80
+
81
+ new_image = NSBitmapImageRep.alloc().initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel_(
82
+ None, # Set to None to create a new bitmap
83
+ int(ns_image.size().width),
84
+ int(ns_image.size().height),
85
+ 8, # Bits per sample
86
+ 4, # Samples per pixel (R, G, B, A)
87
+ True, # Has alpha
88
+ False, # Is not planar
89
+ NSDeviceRGBColorSpace,
90
+ 0, # Automatically compute bytes per row
91
+ 32 # Bits per pixel (8 bits per sample * 4 samples per pixel)
92
+ )
93
+
94
+ context = NSGraphicsContext.graphicsContextWithBitmapImageRep_(new_image)
95
+ NSGraphicsContext.setCurrentContext_(context)
96
+
97
+ ns_image.drawAtPoint_fromRect_operation_fraction_(
98
+ NSZeroPoint,
99
+ NSZeroRect,
100
+ NSCompositingOperationCopy,
101
+ 1.0
102
+ )
103
+
104
+ return bytes(new_image.TIFFRepresentation())
105
+
60
106
  def process_message(self, hwnd: int, msg: int, wparam: int, lparam: int):
61
107
  WM_CLIPBOARDUPDATE = 0x031D
62
108
  timestamp = time.time()
63
109
  if msg == WM_CLIPBOARDUPDATE and timestamp - self.last_update > 1 and not paused:
64
- if win32clipboard.IsClipboardFormatAvailable(win32con.CF_BITMAP):
65
- clipboard_event.set()
66
- self.last_update = timestamp
110
+ self.last_update = timestamp
111
+ while True:
112
+ try:
113
+ win32clipboard.OpenClipboard()
114
+ break
115
+ except pywintypes.error:
116
+ pass
117
+ time.sleep(0.1)
118
+ try:
119
+ if win32clipboard.IsClipboardFormatAvailable(win32con.CF_BITMAP) and win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_DIB):
120
+ clipboard_text = ''
121
+ if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_UNICODETEXT):
122
+ clipboard_text = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)
123
+ if self.ignore_flag or clipboard_text != '*ocr_ignore*':
124
+ img = win32clipboard.GetClipboardData(win32clipboard.CF_DIB)
125
+ image_queue.put((img, False))
126
+ win32clipboard.CloseClipboard()
127
+ except pywintypes.error:
128
+ pass
67
129
  return 0
68
130
 
69
131
  def create_window(self):
@@ -76,10 +138,93 @@ class WindowsClipboardThread(threading.Thread):
76
138
  return win32gui.CreateWindow(class_atom, className, 0, 0, 0, 0, 0, 0, 0, wc.hInstance, None)
77
139
 
78
140
  def run(self):
79
- hwnd = self.create_window()
80
- self.thread_id = win32api.GetCurrentThreadId()
81
- ctypes.windll.user32.AddClipboardFormatListener(hwnd)
82
- win32gui.PumpMessages()
141
+ if sys.platform == 'win32':
142
+ hwnd = self.create_window()
143
+ self.thread_id = win32api.GetCurrentThreadId()
144
+ ctypes.windll.user32.AddClipboardFormatListener(hwnd)
145
+ win32gui.PumpMessages()
146
+ else:
147
+ is_macos = sys.platform == 'darwin'
148
+ if is_macos:
149
+ from AppKit import NSPasteboard, NSPasteboardTypeTIFF, NSPasteboardTypeString
150
+ pasteboard = NSPasteboard.generalPasteboard()
151
+ count = pasteboard.changeCount()
152
+ else:
153
+ from PIL import ImageGrab
154
+ process_clipboard = False
155
+ img = None
156
+
157
+ while not terminated:
158
+ if paused:
159
+ sleep_time = 0.5
160
+ process_clipboard = False
161
+ else:
162
+ sleep_time = self.delay_secs
163
+ if is_macos:
164
+ with objc.autorelease_pool():
165
+ old_count = count
166
+ count = pasteboard.changeCount()
167
+ if process_clipboard and count != old_count:
168
+ while len(pasteboard.types()) == 0:
169
+ time.sleep(0.1)
170
+ if NSPasteboardTypeTIFF in pasteboard.types():
171
+ clipboard_text = ''
172
+ if NSPasteboardTypeString in pasteboard.types():
173
+ clipboard_text = pasteboard.stringForType_(NSPasteboardTypeString)
174
+ if self.ignore_flag or clipboard_text != '*ocr_ignore*':
175
+ img = self.normalize_macos_clipboard(pasteboard.dataForType_(NSPasteboardTypeTIFF))
176
+ image_queue.put((img, False))
177
+ else:
178
+ old_img = img
179
+ try:
180
+ img = ImageGrab.grabclipboard()
181
+ except Exception:
182
+ pass
183
+ else:
184
+ if (process_clipboard and isinstance(img, Image.Image) and \
185
+ (self.ignore_flag or pyperclipfix.paste() != '*ocr_ignore*') and \
186
+ (not self.are_images_identical(img, old_img))):
187
+ image_queue.put((img, False))
188
+
189
+ process_clipboard = True
190
+
191
+ if not terminated:
192
+ time.sleep(sleep_time)
193
+
194
+
195
+ class DirectoryWatcher(threading.Thread):
196
+ def __init__(self, path):
197
+ super().__init__(daemon=True)
198
+ self.path = path
199
+ self.delay_secs = config.get_general('delay_secs')
200
+ self.last_update = time.time()
201
+ self.allowed_extensions = ('.png', '.jpg', '.jpeg', '.bmp', '.gif', '.webp')
202
+
203
+ def get_path_key(self, path):
204
+ return path, path.lstat().st_mtime
205
+
206
+ def run(self):
207
+ old_paths = set()
208
+ for path in self.path.iterdir():
209
+ if path.suffix.lower() in self.allowed_extensions:
210
+ old_paths.add(get_path_key(path))
211
+
212
+ while not terminated:
213
+ if paused:
214
+ sleep_time = 0.5
215
+ else:
216
+ sleep_time = self.delay_secs
217
+ for path in self.path.iterdir():
218
+ if path.suffix.lower() in self.allowed_extensions:
219
+ path_key = self.get_path_key(path)
220
+ if path_key not in old_paths:
221
+ old_paths.add(path_key)
222
+
223
+ if not paused:
224
+ image_queue.put((path, False))
225
+
226
+ if not terminated:
227
+ time.sleep(sleep_time)
83
228
 
84
229
 
85
230
  class WebsocketServerThread(threading.Thread):
@@ -104,7 +249,7 @@ class WebsocketServerThread(threading.Thread):
104
249
  try:
105
250
  async for message in websocket:
106
251
  if self.read and not paused:
107
- websocket_queue.put(message)
252
+ image_queue.put((message, False))
108
253
  try:
109
254
  await websocket.send('True')
110
255
  except websockets.exceptions.ConnectionClosedOK:
@@ -153,145 +298,12 @@ class RequestHandler(socketserver.BaseRequestHandler):
153
298
  pass
154
299
 
155
300
  if not paused:
156
- unixsocket_queue.put(img)
301
+ image_queue.put((img, False))
157
302
  conn.sendall(b'True')
158
303
  else:
159
304
  conn.sendall(b'False')
160
305
 
161
306
 
162
- class MacOSWindowTracker(threading.Thread):
163
- def __init__(self, window_id):
164
- super().__init__(daemon=True)
165
- self.stop = False
166
- self.window_id = window_id
167
- self.window_active = False
168
-
169
- def run(self):
170
- found = True
171
- while found and not self.stop:
172
- found = False
173
- is_active = False
174
- with objc.autorelease_pool():
175
- window_list = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID)
176
- for i, window in enumerate(window_list):
177
- if found and window.get(kCGWindowName, '') == 'Fullscreen Backdrop':
178
- is_active = True
179
- break
180
- if self.window_id == window['kCGWindowNumber']:
181
- found = True
182
- if i == 0 or window_list[i-1].get(kCGWindowName, '') in ('Dock', 'Color Enforcer Window'):
183
- is_active = True
184
- break
185
- if not found:
186
- window_list = CGWindowListCreateDescriptionFromArray([self.window_id])
187
- if len(window_list) > 0:
188
- found = True
189
- if found and self.window_active != is_active:
190
- on_window_activated(is_active)
191
- self.window_active = is_active
192
- time.sleep(0.2)
193
- if not found:
194
- on_window_closed(False)
195
-
196
-
197
- class WindowsWindowTracker(threading.Thread):
198
- def __init__(self, window_handle, only_active):
199
- super().__init__(daemon=True)
200
- self.stop = False
201
- self.window_handle = window_handle
202
- self.only_active = only_active
203
- self.window_active = False
204
- self.window_minimized = False
205
-
206
- def run(self):
207
- found = True
208
- while not self.stop:
209
- found = win32gui.IsWindow(self.window_handle)
210
- if not found:
211
- break
212
- if self.only_active:
213
- is_active = self.window_handle == win32gui.GetForegroundWindow()
214
- if self.window_active != is_active:
215
- on_window_activated(is_active)
216
- self.window_active = is_active
217
- else:
218
- is_minimized = win32gui.IsIconic(self.window_handle)
219
- if self.window_minimized != is_minimized:
220
- on_window_minimized(is_minimized)
221
- self.window_minimized = is_minimized
222
- time.sleep(0.2)
223
- if not found:
224
- on_window_closed(False)
225
-
226
-
227
- def capture_macos_window_screenshot(window_id):
228
- def shareable_content_completion_handler(shareable_content, error):
229
- if error:
230
- screencapturekit_queue.put(None)
231
- return
232
-
233
- target_window = None
234
- for window in shareable_content.windows():
235
- if window.windowID() == window_id:
236
- target_window = window
237
- break
238
-
239
- if not target_window:
240
- screencapturekit_queue.put(None)
241
- return
242
-
243
- with objc.autorelease_pool():
244
- content_filter = SCContentFilter.alloc().initWithDesktopIndependentWindow_(target_window)
245
-
246
- frame = content_filter.contentRect()
247
- scale = content_filter.pointPixelScale()
248
- width = frame.size.width * scale
249
- height = frame.size.height * scale
250
- configuration = SCStreamConfiguration.alloc().init()
251
- configuration.setSourceRect_(CGRectMake(0, 0, frame.size.width, frame.size.height))
252
- configuration.setWidth_(width)
253
- configuration.setHeight_(height)
254
- configuration.setShowsCursor_(False)
255
- configuration.setCaptureResolution_(SCCaptureResolutionBest)
256
- configuration.setIgnoreGlobalClipSingleWindow_(True)
257
-
258
- SCScreenshotManager.captureImageWithFilter_configuration_completionHandler_(
259
- content_filter, configuration, capture_image_completion_handler
260
- )
261
-
262
- def capture_image_completion_handler(image, error):
263
- if error:
264
- screencapturekit_queue.put(None)
265
- return
266
-
267
- screencapturekit_queue.put(image)
268
-
269
- SCShareableContent.getShareableContentWithCompletionHandler_(
270
- shareable_content_completion_handler
271
- )
272
-
273
-
274
- def get_windows_window_handle(window_title):
275
- def callback(hwnd, window_title_part):
276
- window_title = win32gui.GetWindowText(hwnd)
277
- if window_title_part in window_title:
278
- handles.append((hwnd, window_title))
279
- return True
280
-
281
- handle = win32gui.FindWindow(None, window_title)
282
- if handle:
283
- return (handle, window_title)
284
-
285
- handles = []
286
- win32gui.EnumWindows(callback, window_title)
287
- for handle in handles:
288
- _, pid = win32process.GetWindowThreadProcessId(handle[0])
289
- if psutil.Process(pid).name().lower() not in ('cmd.exe', 'powershell.exe', 'windowsterminal.exe'):
290
- return handle
291
-
292
- return (None, None)
293
-
294
-
295
307
  class TextFiltering:
296
308
  accurate_filtering = False
297
309
 
@@ -315,6 +327,7 @@ class TextFiltering:
315
327
  try:
316
328
  from transformers import pipeline, AutoTokenizer
317
329
  import torch
330
+ logging.getLogger('transformers').setLevel(logging.ERROR)
318
331
 
319
332
  model_ckpt = 'papluca/xlm-roberta-base-language-detection'
320
333
  tokenizer = AutoTokenizer.from_pretrained(
@@ -386,19 +399,330 @@ class TextFiltering:
386
399
  break
387
400
  else:
388
401
  for block in new_blocks:
389
- if self.classify(block)[0] == lang:
402
+ if lang not in ["ja", "zh"] or self.classify(block)[0] == lang:
390
403
  final_blocks.append(block)
391
404
 
392
405
  text = '\n'.join(final_blocks)
393
406
  return text, orig_text_filtered
394
407
 
395
408
 
409
+ class ScreenshotClass:
410
+ def __init__(self, screen_capture_area, screen_capture_window, screen_capture_exclusions, screen_capture_only_active_windows):
411
+ self.macos_window_tracker_instance = None
412
+ self.windows_window_tracker_instance = None
413
+ self.screencapture_window_active = True
414
+ self.screencapture_window_visible = True
415
+ self.custom_left = None
416
+ self.screen_capture_exclusions = screen_capture_exclusions
417
+ self.screen_capture_window = screen_capture_window
418
+ if screen_capture_area == '':
419
+ self.screencapture_mode = 0
420
+ elif screen_capture_area.startswith('screen_'):
421
+ parts = screen_capture_area.split('_')
422
+ if len(parts) != 2 or not parts[1].isdigit():
423
+ raise ValueError('Invalid screen_capture_area')
424
+ screen_capture_monitor = int(parts[1])
425
+ self.screencapture_mode = 1
426
+ elif len(screen_capture_area.split(',')) == 4:
427
+ self.screencapture_mode = 3
428
+ else:
429
+ self.screencapture_mode = 2
430
+ self.screen_capture_window = screen_capture_area
431
+ if self.screen_capture_window:
432
+ self.screencapture_mode = 2
433
+
434
+ if self.screencapture_mode != 2:
435
+ self.sct = mss.mss()
436
+
437
+ if self.screencapture_mode == 1:
438
+ mon = self.sct.monitors
439
+ if len(mon) <= screen_capture_monitor:
440
+ raise ValueError('Invalid monitor number in screen_capture_area')
441
+ coord_left = mon[screen_capture_monitor]['left']
442
+ coord_top = mon[screen_capture_monitor]['top']
443
+ coord_width = mon[screen_capture_monitor]['width']
444
+ coord_height = mon[screen_capture_monitor]['height']
445
+ elif self.screencapture_mode == 3:
446
+ coord_left, coord_top, coord_width, coord_height = [int(c.strip()) for c in screen_capture_area.split(',')]
447
+ else:
448
+ logger.opt(ansi=True).info('Launching screen coordinate picker')
449
+ screen_selection = get_screen_selection()
450
+ if not screen_selection:
451
+ raise ValueError('Picker window was closed or an error occurred')
452
+ screen_capture_monitor = screen_selection['monitor']
453
+ x, y, coord_width, coord_height = screen_selection['coordinates']
454
+ if coord_width > 0 and coord_height > 0:
455
+ coord_top = screen_capture_monitor['top'] + y
456
+ coord_left = screen_capture_monitor['left'] + x
457
+ else:
458
+ logger.opt(ansi=True).info('Selection is empty, selecting whole screen')
459
+ coord_left = screen_capture_monitor['left']
460
+ coord_top = screen_capture_monitor['top']
461
+ coord_width = screen_capture_monitor['width']
462
+ coord_height = screen_capture_monitor['height']
463
+
464
+ self.sct_params = {'top': coord_top, 'left': coord_left, 'width': coord_width, 'height': coord_height}
465
+ logger.opt(ansi=True).info(f'Selected coordinates: {coord_left},{coord_top},{coord_width},{coord_height}')
466
+ if len(screen_capture_area.split(',')) == 4:
467
+ self.custom_left, self.custom_top, self.custom_width, self.custom_height = [int(c.strip()) for c in
468
+ screen_capture_area.split(',')]
469
+
470
+
471
+ if self.screencapture_mode == 2 or self.screen_capture_window:
472
+ self.screen_capture_only_active_windows = screen_capture_only_active_windows
473
+ area_invalid_error = '"screen_capture_area" must be empty, "screen_N" where N is a screen number starting from 1, a valid set of coordinates, or a valid window name'
474
+ if sys.platform == 'darwin':
475
+ if config.get_general('screen_capture_old_macos_api') or int(platform.mac_ver()[0].split('.')[0]) < 14:
476
+ self.old_macos_screenshot_api = True
477
+ else:
478
+ self.old_macos_screenshot_api = False
479
+ self.screencapturekit_queue = queue.Queue()
480
+ CGMainDisplayID()
481
+ window_list = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements, kCGNullWindowID)
482
+ window_titles = []
483
+ window_ids = []
484
+ window_index = None
485
+ for i, window in enumerate(window_list):
486
+ window_title = window.get(kCGWindowName, '')
487
+ if psutil.Process(window['kCGWindowOwnerPID']).name() not in ('Terminal', 'iTerm2'):
488
+ window_titles.append(window_title)
489
+ window_ids.append(window['kCGWindowNumber'])
490
+
491
+ if screen_capture_window in window_titles:
492
+ window_index = window_titles.index(screen_capture_window)
493
+ else:
494
+ for t in window_titles:
495
+ if screen_capture_window in t:
496
+ window_index = window_titles.index(t)
497
+ break
498
+
499
+ if not window_index:
500
+ raise ValueError(area_invalid_error)
501
+
502
+ self.window_id = window_ids[window_index]
503
+ window_title = window_titles[window_index]
504
+
505
+ if self.screen_capture_only_active_windows:
506
+ self.macos_window_tracker_instance = threading.Thread(target=self.macos_window_tracker)
507
+ self.macos_window_tracker_instance.start()
508
+ logger.opt(ansi=True).info(f'Selected window: {window_title}')
509
+ elif sys.platform == 'win32':
510
+ self.window_handle, window_title = self.get_windows_window_handle(screen_capture_window)
511
+
512
+ if not self.window_handle:
513
+ raise ValueError(area_invalid_error)
514
+
515
+ ctypes.windll.shcore.SetProcessDpiAwareness(1)
516
+
517
+ self.windows_window_tracker_instance = threading.Thread(target=self.windows_window_tracker)
518
+ self.windows_window_tracker_instance.start()
519
+ logger.opt(ansi=True).info(f'Selected window: {window_title}')
520
+ else:
521
+ raise ValueError('Window capture is only currently supported on Windows and macOS')
522
+
523
+ def __del__(self):
524
+ if self.macos_window_tracker_instance:
525
+ self.macos_window_tracker_instance.join()
526
+ elif self.windows_window_tracker_instance:
527
+ self.windows_window_tracker_instance.join()
528
+
529
+ def get_windows_window_handle(self, window_title):
530
+ def callback(hwnd, window_title_part):
531
+ window_title = win32gui.GetWindowText(hwnd)
532
+ if window_title_part in window_title:
533
+ handles.append((hwnd, window_title))
534
+ return True
535
+
536
+ handle = win32gui.FindWindow(None, window_title)
537
+ if handle:
538
+ return (handle, window_title)
539
+
540
+ handles = []
541
+ win32gui.EnumWindows(callback, window_title)
542
+ for handle in handles:
543
+ _, pid = win32process.GetWindowThreadProcessId(handle[0])
544
+ if psutil.Process(pid).name().lower() not in ('cmd.exe', 'powershell.exe', 'windowsterminal.exe'):
545
+ return handle
546
+
547
+ return (None, None)
548
+
549
+ def windows_window_tracker(self):
550
+ found = True
551
+ while not terminated:
552
+ found = win32gui.IsWindow(self.window_handle)
553
+ if not found:
554
+ break
555
+ if self.screen_capture_only_active_windows:
556
+ self.screencapture_window_active = self.window_handle == win32gui.GetForegroundWindow()
557
+ else:
558
+ self.screencapture_window_visible = not win32gui.IsIconic(self.window_handle)
559
+ time.sleep(0.2)
560
+ if not found:
561
+ on_window_closed(False)
562
+
563
+ def capture_macos_window_screenshot(self, window_id):
564
+ def shareable_content_completion_handler(shareable_content, error):
565
+ if error:
566
+ self.screencapturekit_queue.put(None)
567
+ return
568
+
569
+ target_window = None
570
+ for window in shareable_content.windows():
571
+ if window.windowID() == window_id:
572
+ target_window = window
573
+ break
574
+
575
+ if not target_window:
576
+ self.screencapturekit_queue.put(None)
577
+ return
578
+
579
+ with objc.autorelease_pool():
580
+ content_filter = SCContentFilter.alloc().initWithDesktopIndependentWindow_(target_window)
581
+
582
+ frame = content_filter.contentRect()
583
+ scale = content_filter.pointPixelScale()
584
+ width = frame.size.width * scale
585
+ height = frame.size.height * scale
586
+ configuration = SCStreamConfiguration.alloc().init()
587
+ configuration.setSourceRect_(CGRectMake(0, 0, frame.size.width, frame.size.height))
588
+ configuration.setWidth_(width)
589
+ configuration.setHeight_(height)
590
+ configuration.setShowsCursor_(False)
591
+ configuration.setCaptureResolution_(SCCaptureResolutionBest)
592
+ configuration.setIgnoreGlobalClipSingleWindow_(True)
593
+
594
+ SCScreenshotManager.captureImageWithFilter_configuration_completionHandler_(
595
+ content_filter, configuration, capture_image_completion_handler
596
+ )
597
+
598
+ def capture_image_completion_handler(image, error):
599
+ if error:
600
+ self.screencapturekit_queue.put(None)
601
+ return
602
+
603
+ self.screencapturekit_queue.put(image)
604
+
605
+ SCShareableContent.getShareableContentWithCompletionHandler_(
606
+ shareable_content_completion_handler
607
+ )
608
+
609
+ def macos_window_tracker(self):
610
+ found = True
611
+ while found and not terminated:
612
+ found = False
613
+ is_active = False
614
+ with objc.autorelease_pool():
615
+ window_list = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID)
616
+ for i, window in enumerate(window_list):
617
+ if found and window.get(kCGWindowName, '') == 'Fullscreen Backdrop':
618
+ is_active = True
619
+ break
620
+ if self.window_id == window['kCGWindowNumber']:
621
+ found = True
622
+ if i == 0 or window_list[i-1].get(kCGWindowName, '') in ('Dock', 'Color Enforcer Window'):
623
+ is_active = True
624
+ break
625
+ if not found:
626
+ window_list = CGWindowListCreateDescriptionFromArray([self.window_id])
627
+ if len(window_list) > 0:
628
+ found = True
629
+ if found:
630
+ self.screencapture_window_active = is_active
631
+ time.sleep(0.2)
632
+ if not found:
633
+ on_window_closed(False)
634
+
635
+ def __call__(self):
636
+ if self.screencapture_mode == 2 or self.screen_capture_window:
637
+ if sys.platform == 'darwin':
638
+ with objc.autorelease_pool():
639
+ if self.old_macos_screenshot_api:
640
+ cg_image = CGWindowListCreateImageFromArray(CGRectNull, [self.window_id], kCGWindowImageBoundsIgnoreFraming)
641
+ else:
642
+ self.capture_macos_window_screenshot(self.window_id)
643
+ try:
644
+ cg_image = self.screencapturekit_queue.get(timeout=0.5)
645
+ except queue.Empty:
646
+ cg_image = None
647
+ if not cg_image:
648
+ return 0
649
+ width = CGImageGetWidth(cg_image)
650
+ height = CGImageGetHeight(cg_image)
651
+ raw_data = CGDataProviderCopyData(CGImageGetDataProvider(cg_image))
652
+ bpr = CGImageGetBytesPerRow(cg_image)
653
+ img = Image.frombuffer('RGBA', (width, height), raw_data, 'raw', 'BGRA', bpr, 1)
654
+ else:
655
+ try:
656
+ coord_left, coord_top, right, bottom = win32gui.GetWindowRect(self.window_handle)
657
+ coord_width = right - coord_left
658
+ coord_height = bottom - coord_top
659
+
660
+ hwnd_dc = win32gui.GetWindowDC(self.window_handle)
661
+ mfc_dc = win32ui.CreateDCFromHandle(hwnd_dc)
662
+ save_dc = mfc_dc.CreateCompatibleDC()
663
+
664
+ save_bitmap = win32ui.CreateBitmap()
665
+ save_bitmap.CreateCompatibleBitmap(mfc_dc, coord_width, coord_height)
666
+ save_dc.SelectObject(save_bitmap)
667
+
668
+ result = ctypes.windll.user32.PrintWindow(self.window_handle, save_dc.GetSafeHdc(), 2)
669
+
670
+ bmpinfo = save_bitmap.GetInfo()
671
+ bmpstr = save_bitmap.GetBitmapBits(True)
672
+ except pywintypes.error:
673
+ return 0
674
+ img = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1)
675
+ try:
676
+ win32gui.DeleteObject(save_bitmap.GetHandle())
677
+ except:
678
+ pass
679
+ try:
680
+ save_dc.DeleteDC()
681
+ except:
682
+ pass
683
+ try:
684
+ mfc_dc.DeleteDC()
685
+ except:
686
+ pass
687
+ try:
688
+ win32gui.ReleaseDC(self.window_handle, hwnd_dc)
689
+ except:
690
+ pass
691
+ else:
692
+ sct_img = self.sct.grab(self.sct_params)
693
+ img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
694
+
695
+ # import random # Ensure this is imported at the top of the file if not already
696
+ # rand_int = random.randint(1, 10) # Executes only once out of 10 times
697
+
698
+ # if rand_int == 1: # Executes only once out of 10 times
699
+ # img.show()
700
+
701
+ if self.screen_capture_exclusions:
702
+ img = img.convert("RGBA")
703
+ draw = ImageDraw.Draw(img)
704
+ for exclusion in self.screen_capture_exclusions:
705
+ left, top, width, height = exclusion
706
+ draw.rectangle((left, top, left + width, top + height), fill=(0, 0, 0, 0))
707
+
708
+ if self.custom_left:
709
+ img = img.crop((self.custom_left, self.custom_top, self.custom_left + self.custom_width, self.custom_top + self.custom_height))
710
+
711
+ # if rand_int == 1:
712
+ # img.show()
713
+
714
+ return img
715
+
716
+
396
717
  class AutopauseTimer:
397
718
  def __init__(self, timeout):
398
719
  self.stop_event = threading.Event()
399
720
  self.timeout = timeout
400
721
  self.timer_thread = None
401
722
 
723
+ def __del__(self):
724
+ self.stop()
725
+
402
726
  def start(self):
403
727
  self.stop()
404
728
  self.stop_event.clear()
@@ -411,28 +735,22 @@ class AutopauseTimer:
411
735
  self.timer_thread.join()
412
736
 
413
737
  def _countdown(self):
414
- seconds = self.timeout if self.timeout else 1
415
- while seconds > 0 and not self.stop_event.is_set():
738
+ seconds = self.timeout
739
+ while seconds > 0 and not self.stop_event.is_set() and not terminated:
416
740
  time.sleep(1)
417
741
  seconds -= 1
418
742
  if not self.stop_event.is_set():
419
743
  self.stop_event.set()
420
- if not paused:
744
+ if not (paused or terminated):
421
745
  pause_handler(True)
422
746
 
423
747
 
424
748
  def pause_handler(is_combo=True):
425
749
  global paused
426
- global just_unpaused
427
- if paused:
428
- message = 'Unpaused!'
429
- just_unpaused = True
430
- else:
431
- message = 'Paused!'
750
+ message = 'Unpaused!' if paused else 'Paused!'
432
751
 
433
752
  if auto_pause_handler:
434
753
  auto_pause_handler.stop()
435
-
436
754
  if is_combo:
437
755
  notifier.send(title='owocr', message=message)
438
756
  logger.info(message)
@@ -450,7 +768,6 @@ def engine_change_handler(user_input='s', is_combo=True):
450
768
  engine_index += 1
451
769
  elif user_input.lower() != '' and user_input.lower() in engine_keys:
452
770
  engine_index = engine_keys.index(user_input.lower())
453
-
454
771
  if engine_index != old_engine_index:
455
772
  new_engine_name = engine_instances[engine_index].readable_name
456
773
  if is_combo:
@@ -497,15 +814,6 @@ def user_input_thread_run():
497
814
  termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
498
815
 
499
816
 
500
- def on_screenshot_combo():
501
- if not paused:
502
- screenshot_event.set()
503
-
504
- def on_screenshot_bus():
505
- if not paused:
506
- screenshot_event.set()
507
-
508
-
509
817
  def signal_handler(sig, frame):
510
818
  global terminated
511
819
  logger.info('Terminated!')
@@ -519,9 +827,10 @@ def on_window_closed(alive):
519
827
  terminated = True
520
828
 
521
829
 
522
- def on_window_activated(active):
523
- global screencapture_window_active
524
- screencapture_window_active = active
830
+ def on_screenshot_combo():
831
+ if not paused:
832
+ img = take_screenshot()
833
+ image_queue.put((img, True))
525
834
 
526
835
 
527
836
  def on_window_minimized(minimized):
@@ -529,47 +838,7 @@ def on_window_minimized(minimized):
529
838
  screencapture_window_visible = not minimized
530
839
 
531
840
 
532
- def normalize_macos_clipboard(img):
533
- ns_data = NSData.dataWithBytes_length_(img, len(img))
534
- ns_image = NSImage.alloc().initWithData_(ns_data)
535
-
536
- new_image = NSBitmapImageRep.alloc().initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel_(
537
- None, # Set to None to create a new bitmap
538
- int(ns_image.size().width),
539
- int(ns_image.size().height),
540
- 8, # Bits per sample
541
- 4, # Samples per pixel (R, G, B, A)
542
- True, # Has alpha
543
- False, # Is not planar
544
- NSDeviceRGBColorSpace,
545
- 0, # Automatically compute bytes per row
546
- 32 # Bits per pixel (8 bits per sample * 4 samples per pixel)
547
- )
548
-
549
- context = NSGraphicsContext.graphicsContextWithBitmapImageRep_(new_image)
550
- NSGraphicsContext.setCurrentContext_(context)
551
-
552
- ns_image.drawAtPoint_fromRect_operation_fraction_(
553
- NSZeroPoint,
554
- NSZeroRect,
555
- NSCompositingOperationCopy,
556
- 1.0
557
- )
558
-
559
- return new_image.TIFFRepresentation()
560
-
561
-
562
- def are_images_identical(img1, img2):
563
- if None in (img1, img2):
564
- return img1 == img2
565
-
566
- img1 = np.array(img1)
567
- img2 = np.array(img2)
568
-
569
- return (img1.shape == img2.shape) and (img1 == img2).all()
570
-
571
-
572
- def process_and_write_results(img_or_path, write_to, notifications, last_result, filtering, engine=None, rectangle=None, start_time=None):
841
+ def process_and_write_results(img_or_path, write_to=None, last_result=None, filtering=None, notify=None, engine=None, rectangle=None, ocr_start_time=None):
573
842
  global engine_index
574
843
  if auto_pause_handler:
575
844
  auto_pause_handler.stop()
@@ -583,9 +852,9 @@ def process_and_write_results(img_or_path, write_to, notifications, last_result,
583
852
  else:
584
853
  engine_instance = engine_instances[engine_index]
585
854
 
586
- t0 = time.time()
855
+ start_time = time.time()
587
856
  res, text = engine_instance(img_or_path)
588
- t1 = time.time()
857
+ end_time = time.time()
589
858
 
590
859
  orig_text = []
591
860
  engine_color = config.get_general('engine_color')
@@ -594,13 +863,16 @@ def process_and_write_results(img_or_path, write_to, notifications, last_result,
594
863
  #
595
864
  # print(lang)
596
865
 
866
+ # print(last_result)
867
+ # print(engine_index)
868
+
597
869
  if res:
598
870
  if filtering:
599
871
  text, orig_text = filtering(text, last_result)
600
872
  if lang == "ja" or lang == "zh":
601
873
  text = post_process(text)
602
- logger.opt(ansi=True).info(f'Text recognized in {t1 - t0:0.03f}s using <{engine_color}>{engine_instance.readable_name}</{engine_color}>: {text}')
603
- if notifications:
874
+ logger.opt(ansi=True).info(f'Text recognized in {end_time - start_time:0.03f}s using <{engine_color}>{engine_instance.readable_name}</{engine_color}>: {text}')
875
+ if notify and config.get_general('notifications'):
604
876
  notifier.send(title='owocr', message='Text recognized: ' + text)
605
877
 
606
878
  if write_to == 'websocket':
@@ -608,7 +880,7 @@ def process_and_write_results(img_or_path, write_to, notifications, last_result,
608
880
  elif write_to == 'clipboard':
609
881
  pyperclipfix.copy(text)
610
882
  elif write_to == "callback":
611
- txt_callback(text, orig_text, rectangle, start_time, img_or_path)
883
+ txt_callback(text, orig_text, rectangle, ocr_start_time, img_or_path)
612
884
  elif write_to:
613
885
  with Path(write_to).open('a', encoding='utf-8') as f:
614
886
  f.write(text + '\n')
@@ -616,7 +888,7 @@ def process_and_write_results(img_or_path, write_to, notifications, last_result,
616
888
  if auto_pause_handler and not paused:
617
889
  auto_pause_handler.start()
618
890
  else:
619
- logger.opt(ansi=True).info(f'<{engine_color}>{engine_instance.readable_name}</{engine_color}> reported an error after {t1 - t0:0.03f}s: {text}')
891
+ logger.opt(ansi=True).info(f'<{engine_color}>{engine_instance.readable_name}</{engine_color}> reported an error after {end_time - start_time:0.03f}s: {text}')
620
892
 
621
893
  # print(orig_text)
622
894
  # print(text)
@@ -743,7 +1015,7 @@ def run(read_from=None,
743
1015
  for _, engine_class in sorted(inspect.getmembers(sys.modules[__name__],
744
1016
  lambda x: hasattr(x, '__module__') and x.__module__ and (
745
1017
  __package__ + '.ocr' in x.__module__ or __package__ + '.secret' in x.__module__) and inspect.isclass(
746
- x))):
1018
+ x))):
747
1019
  if len(config_engines) == 0 or engine_class.name in config_engines:
748
1020
  if config.get_engine(engine_class.name) == None:
749
1021
  engine_instance = engine_class()
@@ -767,6 +1039,8 @@ def run(read_from=None,
767
1039
  global first_pressed
768
1040
  global notifier
769
1041
  global auto_pause_handler
1042
+ global websocket_server_thread
1043
+ global image_queue
770
1044
  custom_left = None
771
1045
  terminated = False
772
1046
  paused = pause_at_startup
@@ -777,14 +1051,31 @@ def run(read_from=None,
777
1051
  engine_color = config.get_general('engine_color')
778
1052
  prefix_to_use = ""
779
1053
  delay_secs = config.get_general('delay_secs')
1054
+
1055
+ non_path_inputs = ('screencapture', 'clipboard', 'websocket', 'unixsocket')
1056
+ read_from_secondary = config.get_general('read_from_secondary')
1057
+ read_from_path = None
1058
+ read_from_readable = []
1059
+ terminated = False
1060
+ paused = config.get_general('pause_at_startup')
1061
+ auto_pause = config.get_general('auto_pause')
1062
+ clipboard_thread = None
1063
+ websocket_server_thread = None
1064
+ directory_watcher_thread = None
1065
+ unix_socket_server = None
1066
+ key_combo_listener = None
1067
+ filtering = None
1068
+ auto_pause_handler = None
1069
+ engine_index = engine_keys.index(default_engine) if default_engine != '' else 0
1070
+ engine_color = config.get_general('engine_color')
1071
+ combo_pause = config.get_general('combo_pause')
1072
+ combo_engine_switch = config.get_general('combo_engine_switch')
780
1073
  screen_capture_on_combo = False
781
1074
  notifier = DesktopNotifierSync()
1075
+ image_queue = queue.Queue()
782
1076
  key_combos = {}
783
1077
 
784
- if read_from != 'screencapture' and auto_pause != 0:
785
- auto_pause_handler = AutopauseTimer(auto_pause)
786
-
787
- if combo_pause:
1078
+ if combo_pause != '':
788
1079
  key_combos[combo_pause] = pause_handler
789
1080
  if combo_engine_switch:
790
1081
  if combo_pause:
@@ -792,190 +1083,51 @@ def run(read_from=None,
792
1083
  else:
793
1084
  raise ValueError('combo_pause must also be specified')
794
1085
 
795
- if read_from == 'websocket' or write_to == 'websocket':
796
- global websocket_server_thread
797
- websocket_server_thread = WebsocketServerThread(read_from == 'websocket')
1086
+ if 'websocket' in (read_from, read_from_secondary) or write_to == 'websocket':
1087
+ websocket_server_thread = WebsocketServerThread('websocket' in (read_from, read_from_secondary))
798
1088
  websocket_server_thread.start()
799
1089
 
800
1090
  if write_to == "callback" and text_callback:
801
1091
  global txt_callback
802
1092
  txt_callback = text_callback
803
1093
 
804
- if read_from == 'websocket':
805
- global websocket_queue
806
- websocket_queue = queue.Queue()
807
- read_from_readable = 'websocket'
808
- elif read_from == 'unixsocket':
1094
+ if 'screencapture' in (read_from, read_from_secondary):
1095
+ global take_screenshot
1096
+ screen_capture_combo = config.get_general('screen_capture_combo')
1097
+ last_screenshot_time = 0
1098
+ last_result = ([], engine_index)
1099
+ if screen_capture_combo != '':
1100
+ screen_capture_on_combo = True
1101
+ key_combos[screen_capture_combo] = on_screenshot_combo
1102
+ take_screenshot = ScreenshotClass(screen_capture_area, screen_capture_window, screen_capture_exclusions, screen_capture_only_active_windows)
1103
+ filtering = TextFiltering()
1104
+ read_from_readable.append('screen capture')
1105
+ if 'websocket' in (read_from, read_from_secondary):
1106
+ read_from_readable.append('websocket')
1107
+ if 'unixsocket' in (read_from, read_from_secondary):
809
1108
  if sys.platform == 'win32':
810
1109
  raise ValueError('"unixsocket" is not currently supported on Windows')
811
-
812
- global unixsocket_queue
813
- unixsocket_queue = queue.Queue()
814
1110
  socket_path = Path('/tmp/owocr.sock')
815
1111
  if socket_path.exists():
816
1112
  socket_path.unlink()
817
1113
  unix_socket_server = socketserver.ThreadingUnixStreamServer(str(socket_path), RequestHandler)
818
1114
  unix_socket_server_thread = threading.Thread(target=unix_socket_server.serve_forever, daemon=True)
819
1115
  unix_socket_server_thread.start()
820
- read_from_readable = 'unix socket'
821
- elif read_from == 'clipboard':
822
- macos_clipboard_polling = False
823
- windows_clipboard_polling = False
824
- img = None
825
-
826
- if sys.platform == 'darwin':
827
- from AppKit import NSPasteboard, NSPasteboardTypeTIFF, NSPasteboardTypeString
828
- pasteboard = NSPasteboard.generalPasteboard()
829
- count = pasteboard.changeCount()
830
- macos_clipboard_polling = True
831
- elif sys.platform == 'win32':
832
- global clipboard_event
833
- clipboard_event = threading.Event()
834
- windows_clipboard_thread = WindowsClipboardThread()
835
- windows_clipboard_thread.start()
836
- windows_clipboard_polling = True
837
- else:
838
- from PIL import ImageGrab
839
-
840
- read_from_readable = 'clipboard'
841
- elif read_from == 'screencapture':
842
- if screen_capture_combo:
843
- screen_capture_on_combo = True
844
- global screenshot_event
845
- screenshot_event = threading.Event()
846
- key_combos[screen_capture_combo] = on_screenshot_combo
847
- if screen_capture_event_bus:
848
- screen_capture_on_combo = True
849
- screenshot_event = threading.Event()
850
- screen_capture_event_bus = on_screenshot_bus
851
- if type(screen_capture_area) == tuple:
852
- screen_capture_area = ','.join(map(str, screen_capture_area))
853
- global screencapture_window_active
854
- global screencapture_window_visible
855
- screencapture_mode = None
856
- screencapture_window_active = True
857
- screencapture_window_visible = True
858
- last_result = ([], engine_index)
859
- if screen_capture_area == '':
860
- screencapture_mode = 0
861
- elif screen_capture_area.startswith('screen_'):
862
- parts = screen_capture_area.split('_')
863
- if len(parts) != 2 or not parts[1].isdigit():
864
- raise ValueError('Invalid screen_capture_area')
865
- screen_capture_monitor = int(parts[1])
866
- screencapture_mode = 1
867
- elif len(screen_capture_area.split(',')) == 4:
868
- screencapture_mode = 3
869
- else:
870
- screencapture_mode = 2
871
- screen_capture_window = screen_capture_area
872
- if screen_capture_window:
873
- screencapture_mode = 2
874
-
875
- if screencapture_mode != 2:
876
- sct = mss.mss()
877
-
878
- if screencapture_mode == 1:
879
- mon = sct.monitors
880
- if len(mon) <= screen_capture_monitor:
881
- raise ValueError('Invalid monitor number in screen_capture_area')
882
- coord_left = mon[screen_capture_monitor]['left']
883
- coord_top = mon[screen_capture_monitor]['top']
884
- coord_width = mon[screen_capture_monitor]['width']
885
- coord_height = mon[screen_capture_monitor]['height']
886
- elif screencapture_mode == 3:
887
- coord_left, coord_top, coord_width, coord_height = [int(c.strip()) for c in screen_capture_area.split(',')]
888
- else:
889
- logger.opt(ansi=True).info('Launching screen coordinate picker')
890
- screen_selection = get_screen_selection()
891
- if not screen_selection:
892
- raise ValueError('Picker window was closed or an error occurred')
893
- screen_capture_monitor = screen_selection['monitor']
894
- x, y, coord_width, coord_height = screen_selection['coordinates']
895
- if coord_width > 0 and coord_height > 0:
896
- coord_top = screen_capture_monitor['top'] + y
897
- coord_left = screen_capture_monitor['left'] + x
898
- else:
899
- logger.opt(ansi=True).info('Selection is empty, selecting whole screen')
900
- coord_left = screen_capture_monitor['left']
901
- coord_top = screen_capture_monitor['top']
902
- coord_width = screen_capture_monitor['width']
903
- coord_height = screen_capture_monitor['height']
904
-
905
- sct_params = {'top': coord_top, 'left': coord_left, 'width': coord_width, 'height': coord_height}
906
- logger.opt(ansi=True).info(f'Selected coordinates: {coord_left},{coord_top},{coord_width},{coord_height}')
907
- if len(screen_capture_area.split(',')) == 4:
908
- custom_left, custom_top, custom_width, custom_height = [int(c.strip()) for c in screen_capture_area.split(',')]
909
- if screencapture_mode == 2 or screen_capture_window:
910
- area_invalid_error = '"screen_capture_area" must be empty, "screen_N" where N is a screen number starting from 1, a valid set of coordinates, or a valid window name'
911
- if sys.platform == 'darwin':
912
- if int(platform.mac_ver()[0].split('.')[0]) < 14:
913
- old_macos_screenshot_api = True
914
- else:
915
- global screencapturekit_queue
916
- screencapturekit_queue = queue.Queue()
917
- CGMainDisplayID()
918
- old_macos_screenshot_api = False
919
-
920
- window_list = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements, kCGNullWindowID)
921
- window_titles = []
922
- window_ids = []
923
- window_index = None
924
- for i, window in enumerate(window_list):
925
- window_title = window.get(kCGWindowName, '')
926
- if psutil.Process(window['kCGWindowOwnerPID']).name() not in ('Terminal', 'iTerm2'):
927
- window_titles.append(window_title)
928
- window_ids.append(window['kCGWindowNumber'])
929
-
930
- if screen_capture_area in window_titles:
931
- window_index = window_titles.index(screen_capture_window)
932
- else:
933
- for t in window_titles:
934
- if screen_capture_area in t:
935
- window_index = window_titles.index(t)
936
- break
937
-
938
- if not window_index:
939
- raise ValueError(area_invalid_error)
940
-
941
- window_id = window_ids[window_index]
942
- window_title = window_titles[window_index]
943
-
944
- if screen_capture_only_active_windows:
945
- screencapture_window_active = False
946
- macos_window_tracker = MacOSWindowTracker(window_id)
947
- macos_window_tracker.start()
948
- logger.opt(ansi=True).info(f'Selected window: {window_title}')
949
- elif sys.platform == 'win32':
950
- window_handle, window_title = get_windows_window_handle(screen_capture_window)
951
-
952
- if not window_handle:
953
- raise ValueError(area_invalid_error)
954
-
955
- ctypes.windll.shcore.SetProcessDpiAwareness(1)
956
-
957
- if screen_capture_only_active_windows:
958
- screencapture_window_active = False
959
- windows_window_tracker = WindowsWindowTracker(window_handle, screen_capture_only_active_windows)
960
- windows_window_tracker.start()
961
- logger.opt(ansi=True).info(f'Selected window: {window_title}')
962
- else:
963
- raise ValueError('Window capture is only currently supported on Windows and macOS')
964
-
965
- filtering = TextFiltering()
966
- read_from_readable = 'screen capture'
967
- else:
968
- read_from = Path(read_from)
969
- if not read_from.is_dir():
970
- raise ValueError('read_from must be either "websocket", "unixsocket", "clipboard", "screencapture", or a path to a directory')
971
-
972
- allowed_extensions = ('.png', '.jpg', '.jpeg', '.bmp', '.gif', '.webp')
973
- old_paths = set()
974
- for path in read_from.iterdir():
975
- if path.suffix.lower() in allowed_extensions:
976
- old_paths.add(get_path_key(path))
977
-
978
- read_from_readable = f'directory {read_from}'
1116
+ read_from_readable.append('unix socket')
1117
+ if 'clipboard' in (read_from, read_from_secondary):
1118
+ clipboard_thread = ClipboardThread()
1119
+ clipboard_thread.start()
1120
+ read_from_readable.append('clipboard')
1121
+ if any(i and i not in non_path_inputs for i in (read_from, read_from_secondary)):
1122
+ if all(i and i not in non_path_inputs for i in (read_from, read_from_secondary)):
1123
+ raise ValueError("read_from and read_from_secondary can't both be directory paths")
1124
+ delete_images = config.get_general('delete_images')
1125
+ read_from_path = Path(read_from) if read_from not in non_path_inputs else Path(read_from_secondary)
1126
+ if not read_from_path.is_dir():
1127
+ raise ValueError('read_from and read_from_secondary must be either "websocket", "unixsocket", "clipboard", "screencapture", or a path to a directory')
1128
+ directory_watcher_thread = DirectoryWatcher(read_from_path)
1129
+ directory_watcher_thread.start()
1130
+ read_from_readable.append(f'directory {read_from_path}')
979
1131
 
980
1132
  if len(key_combos) > 0:
981
1133
  key_combo_listener = keyboard.GlobalHotKeys(key_combos)
@@ -988,214 +1140,66 @@ def run(read_from=None,
988
1140
  raise ValueError('write_to must be either "websocket", "clipboard" or a path to a text file')
989
1141
  write_to_readable = f'file {write_to}'
990
1142
 
991
- # signal.signal(signal.SIGINT, signal_handler)
1143
+ process_queue = (any(i in ('clipboard', 'websocket', 'unixsocket') for i in (read_from, read_from_secondary)) or read_from_path or screen_capture_on_combo)
1144
+ process_screenshots = 'screencapture' in (read_from, read_from_secondary) and not screen_capture_on_combo
1145
+ if threading.current_thread() == threading.main_thread():
1146
+ signal.signal(signal.SIGINT, signal_handler)
1147
+ if (not process_screenshots) and auto_pause != 0:
1148
+ auto_pause_handler = AutopauseTimer(auto_pause)
992
1149
  user_input_thread = threading.Thread(target=user_input_thread_run, daemon=True)
993
1150
  user_input_thread.start()
994
- logger.opt(ansi=True).info(f"Reading from {read_from_readable}, writing to {write_to_readable} using <{engine_color}>{engine_instances[engine_index].readable_name}</{engine_color}>{' (paused)' if paused else ''}")
1151
+ logger.opt(ansi=True).info(f"Reading from {' and '.join(read_from_readable)}, writing to {write_to_readable} using <{engine_color}>{engine_instances[engine_index].readable_name}</{engine_color}>{' (paused)' if paused else ''}")
995
1152
 
996
- while not terminated and not stop_running_flag:
997
- start_time = datetime.now()
998
- if read_from == 'websocket':
999
- while True:
1000
- try:
1001
- item = websocket_queue.get(timeout=delay_secs)
1002
- except queue.Empty:
1003
- break
1004
- else:
1005
- if not paused:
1006
- img = Image.open(io.BytesIO(item))
1007
- process_and_write_results(img, write_to, notifications, None, None, start_time=start_time)
1008
- elif read_from == 'unixsocket':
1009
- while True:
1010
- try:
1011
- item = unixsocket_queue.get(timeout=delay_secs)
1012
- except queue.Empty:
1013
- break
1014
- else:
1015
- if not paused:
1016
- img = Image.open(io.BytesIO(item))
1017
- process_and_write_results(img, write_to, notifications, None, None, start_time=start_time)
1018
- elif read_from == 'clipboard':
1019
- process_clipboard = False
1020
- if windows_clipboard_polling:
1021
- if clipboard_event.wait(delay_secs):
1022
- clipboard_event.clear()
1023
- while True:
1024
- try:
1025
- win32clipboard.OpenClipboard()
1026
- break
1027
- except pywintypes.error:
1028
- pass
1029
- time.sleep(0.1)
1030
- if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_DIB):
1031
- clipboard_text = ''
1032
- if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_UNICODETEXT):
1033
- clipboard_text = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)
1034
- if ignore_flag or clipboard_text != '*ocr_ignore*':
1035
- img = Image.open(io.BytesIO(win32clipboard.GetClipboardData(win32clipboard.CF_DIB)))
1036
- process_clipboard = True
1037
- win32clipboard.CloseClipboard()
1038
- elif macos_clipboard_polling:
1039
- if not paused:
1040
- with objc.autorelease_pool():
1041
- old_count = count
1042
- count = pasteboard.changeCount()
1043
- if not just_unpaused and count != old_count and NSPasteboardTypeTIFF in pasteboard.types():
1044
- clipboard_text = ''
1045
- if NSPasteboardTypeString in pasteboard.types():
1046
- clipboard_text = pasteboard.stringForType_(NSPasteboardTypeString)
1047
- if ignore_flag or clipboard_text != '*ocr_ignore*':
1048
- img = normalize_macos_clipboard(pasteboard.dataForType_(NSPasteboardTypeTIFF))
1049
- img = Image.open(io.BytesIO(img))
1050
- process_clipboard = True
1051
- else:
1052
- if not paused:
1053
- old_img = img
1054
- try:
1055
- img = ImageGrab.grabclipboard()
1056
- except Exception:
1057
- pass
1058
- else:
1059
- if ((not just_unpaused) and isinstance(img, Image.Image) and \
1060
- (ignore_flag or pyperclipfix.paste() != '*ocr_ignore*') and \
1061
- (not are_images_identical(img, old_img))):
1062
- process_clipboard = True
1063
-
1064
- if process_clipboard:
1065
- process_and_write_results(img, write_to, notifications, None, None, start_time=start_time)
1066
-
1067
- just_unpaused = False
1068
-
1069
- if not windows_clipboard_polling:
1070
- time.sleep(delay_secs)
1071
- elif read_from == 'screencapture':
1072
- if screen_capture_on_combo:
1073
- take_screenshot = screenshot_event.wait(delay_secs)
1074
- if take_screenshot:
1075
- screenshot_event.clear()
1076
- else:
1077
- take_screenshot = screencapture_window_active and not paused
1078
-
1079
- if take_screenshot and screencapture_window_visible:
1080
- if screencapture_mode == 2:
1081
- if sys.platform == 'darwin':
1082
- with objc.autorelease_pool():
1083
- if old_macos_screenshot_api:
1084
- cg_image = CGWindowListCreateImageFromArray(CGRectNull, [window_id], kCGWindowImageBoundsIgnoreFraming)
1085
- else:
1086
- capture_macos_window_screenshot(window_id)
1087
- try:
1088
- cg_image = screencapturekit_queue.get(timeout=0.5)
1089
- except queue.Empty:
1090
- cg_image = None
1091
- if not cg_image:
1092
- on_window_closed(False)
1093
- break
1094
- width = CGImageGetWidth(cg_image)
1095
- height = CGImageGetHeight(cg_image)
1096
- raw_data = CGDataProviderCopyData(CGImageGetDataProvider(cg_image))
1097
- bpr = CGImageGetBytesPerRow(cg_image)
1098
- img = Image.frombuffer('RGBA', (width, height), raw_data, 'raw', 'BGRA', bpr, 1)
1099
- else:
1100
- try:
1101
- coord_left, coord_top, right, bottom = win32gui.GetWindowRect(window_handle)
1102
-
1103
- coord_width = right - coord_left
1104
- coord_height = bottom - coord_top
1105
-
1106
- hwnd_dc = win32gui.GetWindowDC(window_handle)
1107
- mfc_dc = win32ui.CreateDCFromHandle(hwnd_dc)
1108
- save_dc = mfc_dc.CreateCompatibleDC()
1109
-
1110
- save_bitmap = win32ui.CreateBitmap()
1111
- save_bitmap.CreateCompatibleBitmap(mfc_dc, coord_width, coord_height)
1112
- save_dc.SelectObject(save_bitmap)
1113
-
1114
- result = ctypes.windll.user32.PrintWindow(window_handle, save_dc.GetSafeHdc(), 2)
1115
-
1116
- bmpinfo = save_bitmap.GetInfo()
1117
- bmpstr = save_bitmap.GetBitmapBits(True)
1118
- except pywintypes.error:
1119
- on_window_closed(False)
1120
- break
1121
-
1122
- # rand = random.randint(1, 10)
1123
-
1124
- img = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1)
1125
-
1126
- # if rand == 1:
1127
- # img.show()
1153
+ while not terminated:
1154
+ ocr_start_time = datetime.now()
1155
+ start_time = time.time()
1156
+ img = None
1157
+ filter_img = False
1128
1158
 
1129
- if custom_left:
1130
- img = img.crop((custom_left, custom_top, custom_left + custom_width, custom_top + custom_height))
1159
+ if process_queue:
1160
+ try:
1161
+ img, filter_img = image_queue.get(timeout=0.1)
1162
+ notify = True
1163
+ except queue.Empty:
1164
+ pass
1131
1165
 
1132
- # if rand == 1:
1133
- # img.show()
1166
+ if (not img) and process_screenshots:
1167
+ if (not paused) and take_screenshot.screencapture_window_active and take_screenshot.screencapture_window_visible and (time.time() - last_screenshot_time) > screen_capture_delay_secs:
1168
+ img = take_screenshot()
1169
+ filter_img = True
1170
+ notify = False
1171
+ last_screenshot_time = time.time()
1134
1172
 
1135
- win32gui.DeleteObject(save_bitmap.GetHandle())
1136
- save_dc.DeleteDC()
1137
- mfc_dc.DeleteDC()
1138
- win32gui.ReleaseDC(window_handle, hwnd_dc)
1139
- else:
1140
- sct_img = sct.grab(sct_params)
1141
- img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
1142
- # img.save(os.path.join(get_temporary_directory(), 'screencapture_before.png'), 'png')
1143
- if screen_capture_exclusions:
1144
- img = img.convert("RGBA")
1145
- draw = ImageDraw.Draw(img)
1146
- for exclusion in screen_capture_exclusions:
1147
- left, top, width, height = exclusion
1148
- draw.rectangle((left, top, left + width, top + height), fill=(0, 0, 0, 0))
1149
- # draw.rectangle((left, top, right, bottom), fill=(0, 0, 0))
1150
- # img.save(os.path.join(get_temporary_directory(), 'screencapture.png'), 'png')
1151
- res, _ = process_and_write_results(img, write_to, notifications, last_result, filtering, rectangle=rectangle, start_time=start_time)
1173
+ if img == 0:
1174
+ on_window_closed(False)
1175
+ terminated = True
1176
+ break
1177
+ elif img:
1178
+ if filter_img:
1179
+ res, _ = process_and_write_results(img, write_to, last_result, filtering, notify, rectangle=rectangle, ocr_start_time=ocr_start_time)
1152
1180
  if res:
1153
1181
  last_result = (res, engine_index)
1154
- delay = screen_capture_delay_secs
1155
1182
  else:
1156
- delay = delay_secs
1183
+ process_and_write_results(img, None, None, notify)
1184
+ if isinstance(img, Path):
1185
+ if delete_images:
1186
+ Path.unlink(img)
1157
1187
 
1158
- if not screen_capture_on_combo and delay:
1159
- time.sleep(delay)
1160
- else:
1161
- for path in read_from.iterdir():
1162
- if path.suffix.lower() in allowed_extensions:
1163
- path_key = get_path_key(path)
1164
- if path_key not in old_paths:
1165
- old_paths.add(path_key)
1166
-
1167
- if not paused:
1168
- try:
1169
- img = Image.open(path)
1170
- img.load()
1171
- except (UnidentifiedImageError, OSError) as e:
1172
- logger.warning(f'Error while reading file {path}: {e}')
1173
- else:
1174
- process_and_write_results(img, write_to, notifications, None, None, start_time=start_time)
1175
- img.close()
1176
- if delete_images:
1177
- Path.unlink(path)
1178
-
1179
- time.sleep(delay_secs)
1180
-
1181
- if read_from == 'websocket' or write_to == 'websocket':
1188
+ elapsed_time = time.time() - start_time
1189
+ if (not terminated) and elapsed_time < 0.1:
1190
+ time.sleep(0.1 - elapsed_time)
1191
+
1192
+ if websocket_server_thread:
1182
1193
  websocket_server_thread.stop_server()
1183
1194
  websocket_server_thread.join()
1184
- if read_from == 'clipboard' and windows_clipboard_polling:
1185
- win32api.PostThreadMessage(windows_clipboard_thread.thread_id, win32con.WM_QUIT, 0, 0)
1186
- windows_clipboard_thread.join()
1187
- elif read_from == 'screencapture' and screencapture_mode == 2:
1188
- if sys.platform == 'darwin':
1189
- if screen_capture_only_active_windows:
1190
- macos_window_tracker.stop = True
1191
- macos_window_tracker.join()
1192
- else:
1193
- windows_window_tracker.stop = True
1194
- windows_window_tracker.join()
1195
- elif read_from == 'unixsocket':
1195
+ if clipboard_thread:
1196
+ if sys.platform == 'win32':
1197
+ win32api.PostThreadMessage(clipboard_thread.thread_id, win32con.WM_QUIT, 0, 0)
1198
+ clipboard_thread.join()
1199
+ if directory_watcher_thread:
1200
+ directory_watcher_thread.join()
1201
+ if unix_socket_server:
1196
1202
  unix_socket_server.shutdown()
1197
1203
  unix_socket_server_thread.join()
1198
- if len(key_combos) > 0:
1204
+ if key_combo_listener:
1199
1205
  key_combo_listener.stop()
1200
- if auto_pause_handler:
1201
- auto_pause_handler.stop()