GameSentenceMiner 2.13.6__py3-none-any.whl → 2.13.7__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.
- GameSentenceMiner/owocr/owocr/ocr.py +17 -11
- GameSentenceMiner/owocr/owocr/run.py +248 -177
- {gamesentenceminer-2.13.6.dist-info → gamesentenceminer-2.13.7.dist-info}/METADATA +1 -1
- {gamesentenceminer-2.13.6.dist-info → gamesentenceminer-2.13.7.dist-info}/RECORD +8 -8
- {gamesentenceminer-2.13.6.dist-info → gamesentenceminer-2.13.7.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.13.6.dist-info → gamesentenceminer-2.13.7.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.13.6.dist-info → gamesentenceminer-2.13.7.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.13.6.dist-info → gamesentenceminer-2.13.7.dist-info}/top_level.txt +0 -0
@@ -1368,37 +1368,40 @@ class GroqOCR:
|
|
1368
1368
|
return base64.b64encode(pil_image_to_bytes(img, png_compression=1)).decode('utf-8')
|
1369
1369
|
|
1370
1370
|
# class QWENOCR:
|
1371
|
-
# name = '
|
1371
|
+
# name = 'qwenv2'
|
1372
1372
|
# readable_name = 'Qwen2-VL'
|
1373
1373
|
# key = 'q'
|
1374
1374
|
# available = False
|
1375
|
-
|
1375
|
+
|
1376
1376
|
# def __init__(self, config={}, lang='ja'):
|
1377
1377
|
# try:
|
1378
1378
|
# import torch
|
1379
|
-
#
|
1380
|
-
#
|
1379
|
+
# import transformers
|
1380
|
+
# from transformers import AutoModelForImageTextToText, AutoProcessor
|
1381
|
+
# self.model = AutoModelForImageTextToText.from_pretrained(
|
1381
1382
|
# "Qwen/Qwen2-VL-2B-Instruct", torch_dtype="auto", device_map="auto"
|
1382
1383
|
# )
|
1383
1384
|
# self.processor = AutoProcessor.from_pretrained("Qwen/Qwen2-VL-2B-Instruct", use_fast=True)
|
1384
1385
|
# self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
1385
1386
|
# print(self.device)
|
1386
1387
|
# self.available = True
|
1387
|
-
# logger.info('Qwen2-VL ready')
|
1388
|
+
# logger.info('Qwen2.5-VL ready')
|
1388
1389
|
# except Exception as e:
|
1389
1390
|
# logger.warning(f'Qwen2-VL not available: {e}')
|
1390
|
-
|
1391
|
+
|
1391
1392
|
# def __call__(self, img, furigana_filter_sensitivity=0):
|
1392
1393
|
# if not self.available:
|
1393
1394
|
# return (False, 'Qwen2-VL is not available.')
|
1394
1395
|
# try:
|
1395
|
-
# img = input_to_pil_image(img)
|
1396
|
+
# img, is_path = input_to_pil_image(img)
|
1397
|
+
|
1398
|
+
# # img.show()
|
1396
1399
|
# conversation = [
|
1397
1400
|
# {
|
1398
1401
|
# "role": "user",
|
1399
1402
|
# "content": [
|
1400
1403
|
# {"type": "image"},
|
1401
|
-
# {"type": "text", "text": "Analyze the image. Extract text *only* from within dialogue boxes (speech bubbles or panels containing character dialogue). If Text appears to be vertical, read the text from top to bottom, right to left. From the extracted dialogue text, filter out any furigana. Ignore and do not include any text found outside of dialogue boxes, including character names, speaker labels, or sound effects. Return *only* the filtered dialogue text. If no text is found within dialogue boxes after applying filters, return nothing. Do not include any other output, formatting markers, or commentary."},
|
1404
|
+
# {"type": "text", "text": "Analyze the image. Extract text *only* from within dialogue boxes (speech bubbles or panels containing character dialogue). If Text appears to be vertical, read the text from top to bottom, right to left. From the extracted dialogue text, filter out any furigana (Small characters above the kanji). Ignore and do not include any text found outside of dialogue boxes, including character names, speaker labels, or sound effects. Return *only* the filtered dialogue text. If no text is found within dialogue boxes after applying filters, return nothing. Do not include any other output, formatting markers, or commentary."},
|
1402
1405
|
# ],
|
1403
1406
|
# }
|
1404
1407
|
# ]
|
@@ -1418,15 +1421,18 @@ class GroqOCR:
|
|
1418
1421
|
# return (True, output_text[0] if output_text else "")
|
1419
1422
|
# except Exception as e:
|
1420
1423
|
# return (False, f'Qwen2-VL inference failed: {e}')
|
1424
|
+
|
1425
|
+
# def _preprocess(self, img):
|
1426
|
+
# return base64.b64encode(pil_image_to_bytes(img, png_compression=6)).decode('utf-8')
|
1421
1427
|
|
1422
1428
|
|
1423
1429
|
# qwenocr = QWENOCR()
|
1424
|
-
|
1430
|
+
|
1425
1431
|
# for i in range(10):
|
1426
1432
|
# start_time = time.time()
|
1427
|
-
# res, text = qwenocr(Image.open(
|
1433
|
+
# res, text = qwenocr(Image.open(r"C:\Users\Beangate\GSM\GameSentenceMiner\GameSentenceMiner\owocr\owocr\test_furigana.png"), furigana_filter_sensitivity=0) # Example usage
|
1428
1434
|
# end_time = time.time()
|
1429
|
-
|
1435
|
+
|
1430
1436
|
# print(f"Time taken: {end_time - start_time:.2f} seconds")
|
1431
1437
|
# print(text)
|
1432
1438
|
# class LocalOCR:
|
@@ -94,7 +94,8 @@ class ClipboardThread(threading.Thread):
|
|
94
94
|
32 # Bits per pixel (8 bits per sample * 4 samples per pixel)
|
95
95
|
)
|
96
96
|
|
97
|
-
context = NSGraphicsContext.graphicsContextWithBitmapImageRep_(
|
97
|
+
context = NSGraphicsContext.graphicsContextWithBitmapImageRep_(
|
98
|
+
new_image)
|
98
99
|
NSGraphicsContext.setCurrentContext_(context)
|
99
100
|
|
100
101
|
ns_image.drawAtPoint_fromRect_operation_fraction_(
|
@@ -122,9 +123,11 @@ class ClipboardThread(threading.Thread):
|
|
122
123
|
if win32clipboard.IsClipboardFormatAvailable(win32con.CF_BITMAP) and win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_DIB):
|
123
124
|
clipboard_text = ''
|
124
125
|
if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_UNICODETEXT):
|
125
|
-
clipboard_text = win32clipboard.GetClipboardData(
|
126
|
+
clipboard_text = win32clipboard.GetClipboardData(
|
127
|
+
win32clipboard.CF_UNICODETEXT)
|
126
128
|
if self.ignore_flag or clipboard_text != '*ocr_ignore*':
|
127
|
-
img = win32clipboard.GetClipboardData(
|
129
|
+
img = win32clipboard.GetClipboardData(
|
130
|
+
win32clipboard.CF_DIB)
|
128
131
|
image_queue.put((img, False))
|
129
132
|
win32clipboard.CloseClipboard()
|
130
133
|
except pywintypes.error:
|
@@ -173,9 +176,11 @@ class ClipboardThread(threading.Thread):
|
|
173
176
|
if NSPasteboardTypeTIFF in pasteboard.types():
|
174
177
|
clipboard_text = ''
|
175
178
|
if NSPasteboardTypeString in pasteboard.types():
|
176
|
-
clipboard_text = pasteboard.stringForType_(
|
179
|
+
clipboard_text = pasteboard.stringForType_(
|
180
|
+
NSPasteboardTypeString)
|
177
181
|
if self.ignore_flag or clipboard_text != '*ocr_ignore*':
|
178
|
-
img = self.normalize_macos_clipboard(
|
182
|
+
img = self.normalize_macos_clipboard(
|
183
|
+
pasteboard.dataForType_(NSPasteboardTypeTIFF))
|
179
184
|
image_queue.put((img, False))
|
180
185
|
else:
|
181
186
|
old_img = img
|
@@ -184,8 +189,8 @@ class ClipboardThread(threading.Thread):
|
|
184
189
|
except Exception:
|
185
190
|
pass
|
186
191
|
else:
|
187
|
-
if (process_clipboard and isinstance(img, Image.Image) and
|
188
|
-
(self.ignore_flag or pyperclipfix.paste() != '*ocr_ignore*') and
|
192
|
+
if (process_clipboard and isinstance(img, Image.Image) and
|
193
|
+
(self.ignore_flag or pyperclipfix.paste() != '*ocr_ignore*') and
|
189
194
|
(not self.are_images_identical(img, old_img))):
|
190
195
|
image_queue.put((img, False))
|
191
196
|
|
@@ -201,7 +206,8 @@ class DirectoryWatcher(threading.Thread):
|
|
201
206
|
self.path = path
|
202
207
|
self.delay_secs = config.get_general('delay_secs')
|
203
208
|
self.last_update = time.time()
|
204
|
-
self.allowed_extensions = (
|
209
|
+
self.allowed_extensions = (
|
210
|
+
'.png', '.jpg', '.jpeg', '.bmp', '.gif', '.webp')
|
205
211
|
|
206
212
|
def get_path_key(self, path):
|
207
213
|
return path, path.lstat().st_mtime
|
@@ -278,7 +284,8 @@ class WebsocketServerThread(threading.Thread):
|
|
278
284
|
self._loop = asyncio.get_running_loop()
|
279
285
|
self._stop_event = stop_event = asyncio.Event()
|
280
286
|
self._event.set()
|
281
|
-
self.server = start_server = websockets.serve(
|
287
|
+
self.server = start_server = websockets.serve(
|
288
|
+
self.server_handler, '0.0.0.0', config.get_general('websocket_port'), max_size=1000000000)
|
282
289
|
async with start_server:
|
283
290
|
await stop_event.wait()
|
284
291
|
asyncio.run(main())
|
@@ -314,14 +321,17 @@ class TextFiltering:
|
|
314
321
|
from pysbd import Segmenter
|
315
322
|
self.initial_lang = get_ocr_language() or lang
|
316
323
|
self.segmenter = Segmenter(language=get_ocr_language(), clean=True)
|
317
|
-
self.kana_kanji_regex = re.compile(
|
324
|
+
self.kana_kanji_regex = re.compile(
|
325
|
+
r'[\u3041-\u3096\u30A1-\u30FA\u4E00-\u9FFF]')
|
318
326
|
self.chinese_common_regex = re.compile(r'[\u4E00-\u9FFF]')
|
319
327
|
self.english_regex = re.compile(r'[a-zA-Z0-9.,!?;:"\'()\[\]{}]')
|
320
328
|
self.chinese_common_regex = re.compile(r'[\u4E00-\u9FFF]')
|
321
329
|
self.english_regex = re.compile(r'[a-zA-Z0-9.,!?;:"\'()\[\]{}]')
|
322
330
|
self.korean_regex = re.compile(r'[\uAC00-\uD7AF]')
|
323
|
-
self.arabic_regex = re.compile(
|
324
|
-
|
331
|
+
self.arabic_regex = re.compile(
|
332
|
+
r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]')
|
333
|
+
self.russian_regex = re.compile(
|
334
|
+
r'[\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u1C80-\u1C8F]')
|
325
335
|
self.greek_regex = re.compile(r'[\u0370-\u03FF\u1F00-\u1FFF]')
|
326
336
|
self.hebrew_regex = re.compile(r'[\u0590-\u05FF\uFB1D-\uFB4F]')
|
327
337
|
self.thai_regex = re.compile(r'[\u0E00-\u0E7F]')
|
@@ -335,7 +345,7 @@ class TextFiltering:
|
|
335
345
|
model_ckpt = 'papluca/xlm-roberta-base-language-detection'
|
336
346
|
tokenizer = AutoTokenizer.from_pretrained(
|
337
347
|
model_ckpt,
|
338
|
-
use_fast
|
348
|
+
use_fast=False
|
339
349
|
)
|
340
350
|
|
341
351
|
if torch.cuda.is_available():
|
@@ -344,7 +354,8 @@ class TextFiltering:
|
|
344
354
|
device = 'mps'
|
345
355
|
else:
|
346
356
|
device = -1
|
347
|
-
self.pipe = pipeline(
|
357
|
+
self.pipe = pipeline(
|
358
|
+
'text-classification', model=model_ckpt, tokenizer=tokenizer, device=device)
|
348
359
|
self.accurate_filtering = True
|
349
360
|
except:
|
350
361
|
import langid
|
@@ -403,8 +414,8 @@ class TextFiltering:
|
|
403
414
|
new_blocks = []
|
404
415
|
for idx, block in enumerate(orig_text):
|
405
416
|
if orig_text_filtered[idx] and (orig_text_filtered[idx] not in last_text):
|
406
|
-
new_blocks.append(
|
407
|
-
|
417
|
+
new_blocks.append(
|
418
|
+
str(block).strip().replace("BLANK_LINE", "\n"))
|
408
419
|
|
409
420
|
final_blocks = []
|
410
421
|
if self.accurate_filtering:
|
@@ -420,23 +431,22 @@ class TextFiltering:
|
|
420
431
|
if lang not in ["ja", "zh"] or self.classify(block)[0] in ['ja', 'zh'] or block == "\n":
|
421
432
|
final_blocks.append(block)
|
422
433
|
|
423
|
-
|
424
434
|
text = '\n'.join(final_blocks)
|
425
435
|
return text, orig_text_filtered
|
426
436
|
|
427
437
|
|
428
438
|
class ScreenshotThread(threading.Thread):
|
429
|
-
def __init__(self, screen_capture_area, screen_capture_window,
|
439
|
+
def __init__(self, screen_capture_area, screen_capture_window, ocr_config, screen_capture_on_combo):
|
430
440
|
super().__init__(daemon=True)
|
431
441
|
self.macos_window_tracker_instance = None
|
432
442
|
self.windows_window_tracker_instance = None
|
433
443
|
self.screencapture_window_active = True
|
434
444
|
self.screencapture_window_visible = True
|
435
445
|
self.custom_left = None
|
436
|
-
self.screen_capture_exclusions = screen_capture_exclusions
|
437
446
|
self.screen_capture_window = screen_capture_window
|
438
447
|
self.areas = []
|
439
448
|
self.use_periodic_queue = not screen_capture_on_combo
|
449
|
+
self.ocr_config = ocr_config
|
440
450
|
if screen_capture_area == '':
|
441
451
|
self.screencapture_mode = 0
|
442
452
|
elif screen_capture_area.startswith('screen_'):
|
@@ -459,43 +469,46 @@ class ScreenshotThread(threading.Thread):
|
|
459
469
|
if self.screencapture_mode == 1:
|
460
470
|
mon = sct.monitors
|
461
471
|
if len(mon) <= screen_capture_monitor:
|
462
|
-
raise ValueError(
|
472
|
+
raise ValueError(
|
473
|
+
'Invalid monitor number in screen_capture_area')
|
463
474
|
coord_left = mon[screen_capture_monitor]['left']
|
464
475
|
coord_top = mon[screen_capture_monitor]['top']
|
465
476
|
coord_width = mon[screen_capture_monitor]['width']
|
466
477
|
coord_height = mon[screen_capture_monitor]['height']
|
467
478
|
elif self.screencapture_mode == 3:
|
468
|
-
coord_left, coord_top, coord_width, coord_height = [
|
479
|
+
coord_left, coord_top, coord_width, coord_height = [
|
480
|
+
int(c.strip()) for c in screen_capture_area.split(',')]
|
469
481
|
else:
|
470
|
-
logger.opt(ansi=True).info(
|
482
|
+
logger.opt(ansi=True).info(
|
483
|
+
'Launching screen coordinate picker')
|
471
484
|
screen_selection = get_screen_selection()
|
472
485
|
if not screen_selection:
|
473
|
-
raise ValueError(
|
486
|
+
raise ValueError(
|
487
|
+
'Picker window was closed or an error occurred')
|
474
488
|
screen_capture_monitor = screen_selection['monitor']
|
475
489
|
x, y, coord_width, coord_height = screen_selection['coordinates']
|
476
490
|
if coord_width > 0 and coord_height > 0:
|
477
491
|
coord_top = screen_capture_monitor['top'] + y
|
478
492
|
coord_left = screen_capture_monitor['left'] + x
|
479
493
|
else:
|
480
|
-
logger.opt(ansi=True).info(
|
494
|
+
logger.opt(ansi=True).info(
|
495
|
+
'Selection is empty, selecting whole screen')
|
481
496
|
coord_left = screen_capture_monitor['left']
|
482
497
|
coord_top = screen_capture_monitor['top']
|
483
498
|
coord_width = screen_capture_monitor['width']
|
484
499
|
coord_height = screen_capture_monitor['height']
|
485
500
|
|
486
|
-
self.sct_params = {'top': coord_top, 'left': coord_left,
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
if len(area.split(',')) == 4:
|
491
|
-
self.areas.append(([int(c.strip()) for c in area.split(',')]))
|
501
|
+
self.sct_params = {'top': coord_top, 'left': coord_left,
|
502
|
+
'width': coord_width, 'height': coord_height}
|
503
|
+
logger.opt(ansi=True).info(
|
504
|
+
f'Selected coordinates: {coord_left},{coord_top},{coord_width},{coord_height}')
|
492
505
|
else:
|
493
506
|
if len(screen_capture_area.split(',')) == 4:
|
494
|
-
self.areas.append(([int(c.strip())
|
507
|
+
self.areas.append(([int(c.strip())
|
508
|
+
for c in screen_capture_area.split(',')]))
|
495
509
|
|
496
510
|
self.areas.sort(key=lambda rect: (rect[1], rect[0]))
|
497
511
|
|
498
|
-
|
499
512
|
if self.screencapture_mode == 2 or self.screen_capture_window:
|
500
513
|
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'
|
501
514
|
if sys.platform == 'darwin':
|
@@ -505,7 +518,8 @@ class ScreenshotThread(threading.Thread):
|
|
505
518
|
self.old_macos_screenshot_api = False
|
506
519
|
self.screencapturekit_queue = queue.Queue()
|
507
520
|
CGMainDisplayID()
|
508
|
-
window_list = CGWindowListCopyWindowInfo(
|
521
|
+
window_list = CGWindowListCopyWindowInfo(
|
522
|
+
kCGWindowListExcludeDesktopElements, kCGNullWindowID)
|
509
523
|
window_titles = []
|
510
524
|
window_ids = []
|
511
525
|
window_index = None
|
@@ -530,22 +544,26 @@ class ScreenshotThread(threading.Thread):
|
|
530
544
|
window_title = window_titles[window_index]
|
531
545
|
|
532
546
|
if get_ocr_requires_open_window():
|
533
|
-
self.macos_window_tracker_instance = threading.Thread(
|
547
|
+
self.macos_window_tracker_instance = threading.Thread(
|
548
|
+
target=self.macos_window_tracker)
|
534
549
|
self.macos_window_tracker_instance.start()
|
535
550
|
logger.opt(ansi=True).info(f'Selected window: {window_title}')
|
536
551
|
elif sys.platform == 'win32':
|
537
|
-
self.window_handle, window_title = self.get_windows_window_handle(
|
552
|
+
self.window_handle, window_title = self.get_windows_window_handle(
|
553
|
+
screen_capture_window)
|
538
554
|
|
539
555
|
if not self.window_handle:
|
540
556
|
raise ValueError(area_invalid_error)
|
541
557
|
|
542
558
|
set_dpi_awareness()
|
543
559
|
|
544
|
-
self.windows_window_tracker_instance = threading.Thread(
|
560
|
+
self.windows_window_tracker_instance = threading.Thread(
|
561
|
+
target=self.windows_window_tracker)
|
545
562
|
self.windows_window_tracker_instance.start()
|
546
563
|
logger.opt(ansi=True).info(f'Selected window: {window_title}')
|
547
564
|
else:
|
548
|
-
raise ValueError(
|
565
|
+
raise ValueError(
|
566
|
+
'Window capture is only currently supported on Windows and macOS')
|
549
567
|
|
550
568
|
def get_windows_window_handle(self, window_title):
|
551
569
|
def callback(hwnd, window_title_part):
|
@@ -576,7 +594,8 @@ class ScreenshotThread(threading.Thread):
|
|
576
594
|
if get_ocr_requires_open_window():
|
577
595
|
self.screencapture_window_active = self.window_handle == win32gui.GetForegroundWindow()
|
578
596
|
else:
|
579
|
-
self.screencapture_window_visible = not win32gui.IsIconic(
|
597
|
+
self.screencapture_window_visible = not win32gui.IsIconic(
|
598
|
+
self.window_handle)
|
580
599
|
time.sleep(0.2)
|
581
600
|
if not found:
|
582
601
|
on_window_closed(False)
|
@@ -598,14 +617,16 @@ class ScreenshotThread(threading.Thread):
|
|
598
617
|
return
|
599
618
|
|
600
619
|
with objc.autorelease_pool():
|
601
|
-
content_filter = SCContentFilter.alloc(
|
620
|
+
content_filter = SCContentFilter.alloc(
|
621
|
+
).initWithDesktopIndependentWindow_(target_window)
|
602
622
|
|
603
623
|
frame = content_filter.contentRect()
|
604
624
|
scale = content_filter.pointPixelScale()
|
605
625
|
width = frame.size.width * scale
|
606
626
|
height = frame.size.height * scale
|
607
627
|
configuration = SCStreamConfiguration.alloc().init()
|
608
|
-
configuration.setSourceRect_(CGRectMake(
|
628
|
+
configuration.setSourceRect_(CGRectMake(
|
629
|
+
0, 0, frame.size.width, frame.size.height))
|
609
630
|
configuration.setWidth_(width)
|
610
631
|
configuration.setHeight_(height)
|
611
632
|
configuration.setShowsCursor_(False)
|
@@ -633,7 +654,8 @@ class ScreenshotThread(threading.Thread):
|
|
633
654
|
found = False
|
634
655
|
is_active = False
|
635
656
|
with objc.autorelease_pool():
|
636
|
-
window_list = CGWindowListCopyWindowInfo(
|
657
|
+
window_list = CGWindowListCopyWindowInfo(
|
658
|
+
kCGWindowListOptionOnScreenOnly, kCGNullWindowID)
|
637
659
|
for i, window in enumerate(window_list):
|
638
660
|
if found and window.get(kCGWindowName, '') == 'Fullscreen Backdrop':
|
639
661
|
is_active = True
|
@@ -644,7 +666,8 @@ class ScreenshotThread(threading.Thread):
|
|
644
666
|
is_active = True
|
645
667
|
break
|
646
668
|
if not found:
|
647
|
-
window_list = CGWindowListCreateDescriptionFromArray(
|
669
|
+
window_list = CGWindowListCreateDescriptionFromArray(
|
670
|
+
[self.window_id])
|
648
671
|
if len(window_list) > 0:
|
649
672
|
found = True
|
650
673
|
if found:
|
@@ -679,21 +702,26 @@ class ScreenshotThread(threading.Thread):
|
|
679
702
|
cg_image = CGWindowListCreateImageFromArray(CGRectNull, [self.window_id],
|
680
703
|
kCGWindowImageBoundsIgnoreFraming)
|
681
704
|
else:
|
682
|
-
self.capture_macos_window_screenshot(
|
705
|
+
self.capture_macos_window_screenshot(
|
706
|
+
self.window_id)
|
683
707
|
try:
|
684
|
-
cg_image = self.screencapturekit_queue.get(
|
708
|
+
cg_image = self.screencapturekit_queue.get(
|
709
|
+
timeout=0.5)
|
685
710
|
except queue.Empty:
|
686
711
|
cg_image = None
|
687
712
|
if not cg_image:
|
688
713
|
return 0
|
689
714
|
width = CGImageGetWidth(cg_image)
|
690
715
|
height = CGImageGetHeight(cg_image)
|
691
|
-
raw_data = CGDataProviderCopyData(
|
716
|
+
raw_data = CGDataProviderCopyData(
|
717
|
+
CGImageGetDataProvider(cg_image))
|
692
718
|
bpr = CGImageGetBytesPerRow(cg_image)
|
693
|
-
img = Image.frombuffer(
|
719
|
+
img = Image.frombuffer(
|
720
|
+
'RGBA', (width, height), raw_data, 'raw', 'BGRA', bpr, 1)
|
694
721
|
else:
|
695
722
|
try:
|
696
|
-
coord_left, coord_top, right, bottom = win32gui.GetWindowRect(
|
723
|
+
coord_left, coord_top, right, bottom = win32gui.GetWindowRect(
|
724
|
+
self.window_handle)
|
697
725
|
coord_width = right - coord_left
|
698
726
|
coord_height = bottom - coord_top
|
699
727
|
|
@@ -702,10 +730,12 @@ class ScreenshotThread(threading.Thread):
|
|
702
730
|
save_dc = mfc_dc.CreateCompatibleDC()
|
703
731
|
|
704
732
|
save_bitmap = win32ui.CreateBitmap()
|
705
|
-
save_bitmap.CreateCompatibleBitmap(
|
733
|
+
save_bitmap.CreateCompatibleBitmap(
|
734
|
+
mfc_dc, coord_width, coord_height)
|
706
735
|
save_dc.SelectObject(save_bitmap)
|
707
736
|
|
708
|
-
result = ctypes.windll.user32.PrintWindow(
|
737
|
+
result = ctypes.windll.user32.PrintWindow(
|
738
|
+
self.window_handle, save_dc.GetSafeHdc(), 2)
|
709
739
|
|
710
740
|
bmpinfo = save_bitmap.GetInfo()
|
711
741
|
bmpstr = save_bitmap.GetBitmapBits(True)
|
@@ -731,55 +761,32 @@ class ScreenshotThread(threading.Thread):
|
|
731
761
|
pass
|
732
762
|
else:
|
733
763
|
sct_img = sct.grab(self.sct_params)
|
734
|
-
img = Image.frombytes(
|
735
|
-
|
764
|
+
img = Image.frombytes(
|
765
|
+
'RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
|
766
|
+
|
736
767
|
if not img.getbbox():
|
737
|
-
logger.info(
|
768
|
+
logger.info(
|
769
|
+
"Screen Capture Didn't get Capturing anything, sleeping.")
|
738
770
|
time.sleep(1)
|
739
771
|
continue
|
740
772
|
|
741
773
|
import random # Ensure this is imported at the top of the file if not already
|
742
|
-
|
774
|
+
# Executes only once out of 10 times
|
775
|
+
rand_int = random.randint(1, 20)
|
743
776
|
|
744
777
|
if rand_int == 1: # Executes only once out of 10 times
|
745
|
-
img.save(os.path.join(get_temporary_directory(),
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
draw = ImageDraw.Draw(img)
|
750
|
-
for exclusion in self.screen_capture_exclusions:
|
751
|
-
left, top, width, height = exclusion
|
752
|
-
draw.rectangle((left, top, left + width, top + height), fill=(0, 0, 0, 0))
|
753
|
-
|
754
|
-
cropped_sections = []
|
755
|
-
for area in self.areas:
|
756
|
-
# Ensure crop coordinates are within image bounds
|
757
|
-
left = max(0, area[0])
|
758
|
-
top = max(0, area[1])
|
759
|
-
right = min(img.width, area[0] + area[2])
|
760
|
-
bottom = min(img.height, area[1] + area[3])
|
761
|
-
cropped_sections.append(img.crop((left, top, right, bottom)))
|
762
|
-
|
763
|
-
if len(cropped_sections) > 1:
|
764
|
-
combined_width = max(section.width for section in cropped_sections)
|
765
|
-
combined_height = sum(section.height for section in cropped_sections) + (
|
766
|
-
len(cropped_sections) - 1) * 10 # Add space for gaps
|
767
|
-
combined_img = Image.new("RGBA", (combined_width, combined_height))
|
768
|
-
|
769
|
-
y_offset = 0
|
770
|
-
for section in cropped_sections:
|
771
|
-
combined_img.paste(section, (0, y_offset))
|
772
|
-
y_offset += section.height + 50 # Add gap between sections
|
773
|
-
|
774
|
-
img = combined_img
|
775
|
-
elif cropped_sections:
|
776
|
-
img = cropped_sections[0]
|
778
|
+
img.save(os.path.join(get_temporary_directory(),
|
779
|
+
'before_crop.png'), 'PNG')
|
780
|
+
|
781
|
+
img = apply_ocr_config_to_image(img, self.ocr_config)
|
777
782
|
|
778
783
|
if rand_int == 1:
|
779
|
-
img.save(os.path.join(
|
784
|
+
img.save(os.path.join(
|
785
|
+
get_temporary_directory(), 'after_crop.png'), 'PNG')
|
780
786
|
|
781
787
|
if last_image and are_images_identical(img, last_image):
|
782
|
-
logger.debug(
|
788
|
+
logger.debug(
|
789
|
+
"Captured screenshot is identical to the last one, sleeping.")
|
783
790
|
time.sleep(max(.5, get_ocr_scan_rate()))
|
784
791
|
else:
|
785
792
|
self.write_result(img)
|
@@ -789,8 +796,8 @@ class ScreenshotThread(threading.Thread):
|
|
789
796
|
self.macos_window_tracker_instance.join()
|
790
797
|
elif self.windows_window_tracker_instance:
|
791
798
|
self.windows_window_tracker_instance.join()
|
792
|
-
|
793
|
-
|
799
|
+
|
800
|
+
|
794
801
|
def set_last_image(image):
|
795
802
|
global last_image
|
796
803
|
if image == last_image:
|
@@ -801,19 +808,21 @@ def set_last_image(image):
|
|
801
808
|
except Exception:
|
802
809
|
pass
|
803
810
|
last_image = image
|
804
|
-
|
811
|
+
|
812
|
+
|
805
813
|
def are_images_identical(img1, img2):
|
806
814
|
if None in (img1, img2):
|
807
815
|
return img1 == img2
|
808
816
|
|
809
|
-
try:
|
817
|
+
try:
|
810
818
|
img1 = np.array(img1)
|
811
819
|
img2 = np.array(img2)
|
812
820
|
except Exception:
|
813
|
-
logger.warning(
|
821
|
+
logger.warning(
|
822
|
+
"Failed to convert images to numpy arrays for comparison.")
|
814
823
|
# If conversion to numpy array fails, consider them not identical
|
815
824
|
return False
|
816
|
-
|
825
|
+
|
817
826
|
return (img1.shape == img2.shape) and np.array_equal(img1, img2)
|
818
827
|
|
819
828
|
|
@@ -831,7 +840,7 @@ class OBSScreenshotThread(threading.Thread):
|
|
831
840
|
self.width = width
|
832
841
|
self.height = height
|
833
842
|
self.use_periodic_queue = not screen_capture_on_combo
|
834
|
-
|
843
|
+
|
835
844
|
def write_result(self, result):
|
836
845
|
if self.use_periodic_queue:
|
837
846
|
periodic_screenshot_queue.put(result)
|
@@ -841,12 +850,14 @@ class OBSScreenshotThread(threading.Thread):
|
|
841
850
|
def connect_obs(self):
|
842
851
|
import GameSentenceMiner.obs as obs
|
843
852
|
obs.connect_to_obs_sync()
|
844
|
-
|
853
|
+
|
845
854
|
def scale_down_width_height(self, width, height):
|
846
855
|
if width == 0 or height == 0:
|
847
856
|
return self.width, self.height
|
857
|
+
# return width, height
|
848
858
|
aspect_ratio = width / height
|
849
|
-
logger.info(
|
859
|
+
logger.info(
|
860
|
+
f"Scaling down OBS source dimensions: {width}x{height} (Aspect Ratio: {aspect_ratio})")
|
850
861
|
if aspect_ratio > 2.66:
|
851
862
|
# Ultra-wide (32:9) - use 1920x540
|
852
863
|
logger.info("Using ultra-wide aspect ratio scaling (32:9).")
|
@@ -877,7 +888,8 @@ class OBSScreenshotThread(threading.Thread):
|
|
877
888
|
return 1080, 720
|
878
889
|
else:
|
879
890
|
# Default fallback - use original resolution
|
880
|
-
logger.info(
|
891
|
+
logger.info(
|
892
|
+
"Using default aspect ratio scaling (original resolution).")
|
881
893
|
return width, height
|
882
894
|
|
883
895
|
def run(self):
|
@@ -889,12 +901,17 @@ class OBSScreenshotThread(threading.Thread):
|
|
889
901
|
obs.update_current_game()
|
890
902
|
self.current_source = source if source else obs.get_active_source()
|
891
903
|
logger.info(f"Current OBS source: {self.current_source}")
|
892
|
-
self.source_width = self.current_source.get(
|
893
|
-
|
904
|
+
self.source_width = self.current_source.get(
|
905
|
+
"sceneItemTransform").get("sourceWidth") or self.width
|
906
|
+
self.source_height = self.current_source.get(
|
907
|
+
"sceneItemTransform").get("sourceHeight") or self.height
|
894
908
|
if self.source_width and self.source_height:
|
895
|
-
self.width, self.height = self.scale_down_width_height(
|
896
|
-
|
897
|
-
|
909
|
+
self.width, self.height = self.scale_down_width_height(
|
910
|
+
self.source_width, self.source_height)
|
911
|
+
logger.info(
|
912
|
+
f"Using OBS source dimensions: {self.width}x{self.height}")
|
913
|
+
self.current_source_name = self.current_source.get(
|
914
|
+
"sourceName") or None
|
898
915
|
self.current_scene = scene if scene else obs.get_current_game()
|
899
916
|
self.ocr_config = get_scene_ocr_config()
|
900
917
|
if not self.ocr_config:
|
@@ -927,61 +944,39 @@ class OBSScreenshotThread(threading.Thread):
|
|
927
944
|
continue
|
928
945
|
|
929
946
|
if not self.ocr_config:
|
930
|
-
logger.info(
|
947
|
+
logger.info(
|
948
|
+
"No OCR config found for the current scene. Waiting for scene switch.")
|
931
949
|
time.sleep(1)
|
932
950
|
continue
|
933
|
-
|
951
|
+
|
934
952
|
if not self.current_source_name:
|
935
953
|
obs.update_current_game()
|
936
954
|
self.current_source = obs.get_active_source()
|
937
|
-
self.current_source_name = self.current_source.get(
|
955
|
+
self.current_source_name = self.current_source.get(
|
956
|
+
"sourceName") or None
|
938
957
|
|
939
958
|
try:
|
940
959
|
if not self.current_source_name:
|
941
|
-
logger.error(
|
960
|
+
logger.error(
|
961
|
+
"No active source found in the current scene.")
|
942
962
|
time.sleep(1)
|
943
963
|
continue
|
944
964
|
# start_time = time.time()
|
945
|
-
img = obs.get_screenshot_PIL(source_name=self.current_source_name,
|
965
|
+
img = obs.get_screenshot_PIL(source_name=self.current_source_name,
|
966
|
+
width=self.width, height=self.height, img_format='jpg', compression=80)
|
946
967
|
# logger.info(f"OBS screenshot taken in {time.time() - start_time:.2f} seconds.")
|
947
968
|
|
969
|
+
img = apply_ocr_config_to_image(img, self.ocr_config)
|
970
|
+
|
948
971
|
if img is not None:
|
949
972
|
if not img.getbbox():
|
950
973
|
logger.info("OBS Not Capturing anything, sleeping.")
|
951
974
|
time.sleep(1)
|
952
975
|
continue
|
953
976
|
|
954
|
-
for rectangle in self.ocr_config.rectangles:
|
955
|
-
if rectangle.is_excluded:
|
956
|
-
left, top, width, height = rectangle.coordinates
|
957
|
-
draw = ImageDraw.Draw(img)
|
958
|
-
draw.rectangle((left, top, left + width, top + height), fill=(0, 0, 0, 0))
|
959
|
-
|
960
|
-
cropped_sections = []
|
961
|
-
for rectangle in [r for r in self.ocr_config.rectangles if not r.is_excluded]:
|
962
|
-
area = rectangle.coordinates
|
963
|
-
# Ensure crop coordinates are within image bounds
|
964
|
-
left = max(0, area[0])
|
965
|
-
top = max(0, area[1])
|
966
|
-
right = min(img.width, area[0] + area[2])
|
967
|
-
bottom = min(img.height, area[1] + area[3])
|
968
|
-
cropped_sections.append(img.crop((left, top, right, bottom)))
|
969
|
-
|
970
|
-
if len(cropped_sections) > 1:
|
971
|
-
combined_width = max(section.width for section in cropped_sections)
|
972
|
-
combined_height = sum(section.height for section in cropped_sections) + (
|
973
|
-
len(cropped_sections) - 1) * 10
|
974
|
-
combined_img = Image.new("RGBA", (combined_width, combined_height))
|
975
|
-
y_offset = 0
|
976
|
-
for section in cropped_sections:
|
977
|
-
combined_img.paste(section, (0, y_offset))
|
978
|
-
y_offset += section.height + 50
|
979
|
-
img = combined_img
|
980
|
-
elif cropped_sections:
|
981
|
-
img = cropped_sections[0]
|
982
|
-
|
983
977
|
if last_image and are_images_identical(img, last_image):
|
984
|
-
logger.debug(
|
978
|
+
logger.debug(
|
979
|
+
"Captured screenshot is identical to the last one, sleeping.")
|
985
980
|
time.sleep(max(.5, get_ocr_scan_rate()))
|
986
981
|
else:
|
987
982
|
self.write_result(img)
|
@@ -990,9 +985,53 @@ class OBSScreenshotThread(threading.Thread):
|
|
990
985
|
logger.error("Failed to get screenshot data from OBS.")
|
991
986
|
|
992
987
|
except Exception as e:
|
993
|
-
logger.error(
|
988
|
+
logger.error(
|
989
|
+
f"An unexpected error occurred during OBS Capture : {e}", exc_info=True)
|
994
990
|
continue
|
995
991
|
|
992
|
+
|
993
|
+
def apply_ocr_config_to_image(img, ocr_config):
|
994
|
+
for rectangle in ocr_config.rectangles:
|
995
|
+
if rectangle.is_excluded:
|
996
|
+
left, top, width, height = rectangle.coordinates
|
997
|
+
draw = ImageDraw.Draw(img)
|
998
|
+
draw.rectangle((left, top, left + width, top +
|
999
|
+
height), fill=(0, 0, 0, 0))
|
1000
|
+
|
1001
|
+
rectangles = [r for r in ocr_config.rectangles if not r.is_excluded]
|
1002
|
+
|
1003
|
+
# Sort top to bottom
|
1004
|
+
if rectangles:
|
1005
|
+
rectangles.sort(key=lambda r: r.coordinates[1])
|
1006
|
+
|
1007
|
+
cropped_sections = []
|
1008
|
+
for rectangle in rectangles:
|
1009
|
+
area = rectangle.coordinates
|
1010
|
+
# Ensure crop coordinates are within image bounds
|
1011
|
+
left = max(0, area[0])
|
1012
|
+
top = max(0, area[1])
|
1013
|
+
right = min(img.width, area[0] + area[2])
|
1014
|
+
bottom = min(img.height, area[1] + area[3])
|
1015
|
+
crop = img.crop((left, top, right, bottom))
|
1016
|
+
cropped_sections.append(crop)
|
1017
|
+
|
1018
|
+
if len(cropped_sections) > 1:
|
1019
|
+
# Width is the max width of all sections, height is the sum of all sections + gaps
|
1020
|
+
# Gaps are 50 pixels between sections
|
1021
|
+
combined_width = max(section.width for section in cropped_sections)
|
1022
|
+
combined_height = sum(section.height for section in cropped_sections) + (
|
1023
|
+
len(cropped_sections) - 1) * 50
|
1024
|
+
combined_img = Image.new("RGBA", (combined_width, combined_height))
|
1025
|
+
y_offset = 0
|
1026
|
+
for section in cropped_sections:
|
1027
|
+
combined_img.paste(section, (0, y_offset))
|
1028
|
+
y_offset += section.height + 50
|
1029
|
+
img = combined_img
|
1030
|
+
elif cropped_sections:
|
1031
|
+
img = cropped_sections[0]
|
1032
|
+
return img
|
1033
|
+
|
1034
|
+
|
996
1035
|
class AutopauseTimer:
|
997
1036
|
def __init__(self, timeout):
|
998
1037
|
self.stop_event = threading.Event()
|
@@ -1050,9 +1089,11 @@ def engine_change_handler(user_input='s', is_combo=True):
|
|
1050
1089
|
if engine_index != old_engine_index:
|
1051
1090
|
new_engine_name = engine_instances[engine_index].readable_name
|
1052
1091
|
if is_combo:
|
1053
|
-
notifier.send(
|
1092
|
+
notifier.send(
|
1093
|
+
title='owocr', message=f'Switched to {new_engine_name}')
|
1054
1094
|
engine_color = config.get_general('engine_color')
|
1055
|
-
logger.opt(ansi=True).info(
|
1095
|
+
logger.opt(ansi=True).info(
|
1096
|
+
f'Switched to <{engine_color}>{new_engine_name}</{engine_color}>!')
|
1056
1097
|
|
1057
1098
|
|
1058
1099
|
def engine_change_handler_name(engine):
|
@@ -1068,7 +1109,8 @@ def engine_change_handler_name(engine):
|
|
1068
1109
|
new_engine_name = engine_instances[engine_index].readable_name
|
1069
1110
|
notifier.send(title='owocr', message=f'Switched to {new_engine_name}')
|
1070
1111
|
engine_color = config.get_general('engine_color')
|
1071
|
-
logger.opt(ansi=True).info(
|
1112
|
+
logger.opt(ansi=True).info(
|
1113
|
+
f'Switched to <{engine_color}>{new_engine_name}</{engine_color}>!')
|
1072
1114
|
|
1073
1115
|
|
1074
1116
|
def user_input_thread_run():
|
@@ -1099,7 +1141,8 @@ def user_input_thread_run():
|
|
1099
1141
|
else:
|
1100
1142
|
engine_change_handler(user_input, False)
|
1101
1143
|
else:
|
1102
|
-
import tty
|
1144
|
+
import tty
|
1145
|
+
import termios
|
1103
1146
|
fd = sys.stdin.fileno()
|
1104
1147
|
old_settings = termios.tcgetattr(fd)
|
1105
1148
|
try:
|
@@ -1151,16 +1194,15 @@ def process_and_write_results(img_or_path, write_to=None, last_result=None, filt
|
|
1151
1194
|
else:
|
1152
1195
|
engine_instance = engine_instances[engine_index]
|
1153
1196
|
|
1154
|
-
|
1155
1197
|
engine_color = config.get_general('engine_color')
|
1156
1198
|
|
1157
1199
|
start_time = time.time()
|
1158
1200
|
result = engine_instance(img_or_path, furigana_filter_sensitivity)
|
1159
1201
|
res, text, crop_coords = (*result, None)[:3]
|
1160
1202
|
|
1161
|
-
|
1162
1203
|
if not res and ocr_2 == engine:
|
1163
|
-
logger.opt(ansi=True).info(
|
1204
|
+
logger.opt(ansi=True).info(
|
1205
|
+
f"<{engine_color}>{engine_instance.readable_name}</{engine_color}> failed with message: {text}, trying <{engine_color}>{ocr_1}</{engine_color}>")
|
1164
1206
|
for i, instance in enumerate(engine_instances):
|
1165
1207
|
if instance.name.lower() in ocr_1.lower():
|
1166
1208
|
engine_instance = instance
|
@@ -1173,7 +1215,6 @@ def process_and_write_results(img_or_path, write_to=None, last_result=None, filt
|
|
1173
1215
|
|
1174
1216
|
end_time = time.time()
|
1175
1217
|
|
1176
|
-
|
1177
1218
|
orig_text = []
|
1178
1219
|
# print(filtering)
|
1179
1220
|
#
|
@@ -1188,7 +1229,8 @@ def process_and_write_results(img_or_path, write_to=None, last_result=None, filt
|
|
1188
1229
|
text, orig_text = filtering(text, last_result)
|
1189
1230
|
if get_ocr_language() == "ja" or get_ocr_language() == "zh":
|
1190
1231
|
text = post_process(text, keep_blank_lines=get_ocr_keep_newline())
|
1191
|
-
logger.opt(ansi=True).info(
|
1232
|
+
logger.opt(ansi=True).info(
|
1233
|
+
f'Text recognized in {end_time - start_time:0.03f}s using <{engine_color}>{engine_instance.readable_name}</{engine_color}>: {text}')
|
1192
1234
|
if notify and config.get_general('notifications'):
|
1193
1235
|
notifier.send(title='owocr', message='Text recognized: ' + text)
|
1194
1236
|
|
@@ -1197,7 +1239,8 @@ def process_and_write_results(img_or_path, write_to=None, last_result=None, filt
|
|
1197
1239
|
elif write_to == 'clipboard':
|
1198
1240
|
pyperclipfix.copy(text)
|
1199
1241
|
elif write_to == "callback":
|
1200
|
-
txt_callback(text, orig_text, ocr_start_time,
|
1242
|
+
txt_callback(text, orig_text, ocr_start_time,
|
1243
|
+
img_or_path, bool(engine), filtering, crop_coords)
|
1201
1244
|
elif write_to:
|
1202
1245
|
with Path(write_to).open('a', encoding='utf-8') as f:
|
1203
1246
|
f.write(text + '\n')
|
@@ -1205,7 +1248,8 @@ def process_and_write_results(img_or_path, write_to=None, last_result=None, filt
|
|
1205
1248
|
if auto_pause_handler and not paused:
|
1206
1249
|
auto_pause_handler.start()
|
1207
1250
|
else:
|
1208
|
-
logger.opt(ansi=True).info(
|
1251
|
+
logger.opt(ansi=True).info(
|
1252
|
+
f'<{engine_color}>{engine_instance.readable_name}</{engine_color}> reported an error after {end_time - start_time:0.03f}s: {text}')
|
1209
1253
|
|
1210
1254
|
# print(orig_text)
|
1211
1255
|
# print(text)
|
@@ -1286,13 +1330,15 @@ def run(read_from=None,
|
|
1286
1330
|
# screen_capture_only_active_windows = config.get_general('screen_capture_only_active_windows')
|
1287
1331
|
|
1288
1332
|
if screen_capture_exclusions is None:
|
1289
|
-
screen_capture_exclusions = config.get_general(
|
1333
|
+
screen_capture_exclusions = config.get_general(
|
1334
|
+
'screen_capture_exclusions')
|
1290
1335
|
|
1291
1336
|
if screen_capture_window is None:
|
1292
1337
|
screen_capture_window = config.get_general('screen_capture_window')
|
1293
1338
|
|
1294
1339
|
if screen_capture_delay_secs is None:
|
1295
|
-
screen_capture_delay_secs = config.get_general(
|
1340
|
+
screen_capture_delay_secs = config.get_general(
|
1341
|
+
'screen_capture_delay_secs')
|
1296
1342
|
|
1297
1343
|
if screen_capture_combo is None:
|
1298
1344
|
screen_capture_combo = config.get_general('screen_capture_combo')
|
@@ -1301,7 +1347,8 @@ def run(read_from=None,
|
|
1301
1347
|
stop_running_flag = config.get_general('stop_running_flag')
|
1302
1348
|
|
1303
1349
|
if screen_capture_event_bus is None:
|
1304
|
-
screen_capture_event_bus = config.get_general(
|
1350
|
+
screen_capture_event_bus = config.get_general(
|
1351
|
+
'screen_capture_event_bus')
|
1305
1352
|
|
1306
1353
|
if text_callback is None:
|
1307
1354
|
text_callback = config.get_general('text_callback')
|
@@ -1309,14 +1356,16 @@ def run(read_from=None,
|
|
1309
1356
|
if write_to is None:
|
1310
1357
|
write_to = config.get_general('write_to')
|
1311
1358
|
|
1312
|
-
logger.configure(
|
1359
|
+
logger.configure(
|
1360
|
+
handlers=[{'sink': sys.stderr, 'format': config.get_general('logger_format')}])
|
1313
1361
|
|
1314
1362
|
if config.has_config:
|
1315
1363
|
logger.info('Parsed config file')
|
1316
1364
|
else:
|
1317
1365
|
logger.warning('No config file, defaults will be used.')
|
1318
1366
|
if config.downloaded_config:
|
1319
|
-
logger.info(
|
1367
|
+
logger.info(
|
1368
|
+
f'A default config file has been downloaded to {config.config_path}')
|
1320
1369
|
|
1321
1370
|
global engine_instances
|
1322
1371
|
global engine_keys
|
@@ -1331,13 +1380,14 @@ def run(read_from=None,
|
|
1331
1380
|
|
1332
1381
|
for _, engine_class in sorted(inspect.getmembers(sys.modules[__name__],
|
1333
1382
|
lambda x: hasattr(x, '__module__') and x.__module__ and (
|
1334
|
-
|
1383
|
+
__package__ + '.ocr' in x.__module__ or __package__ + '.secret' in x.__module__) and inspect.isclass(
|
1335
1384
|
x))):
|
1336
1385
|
if len(config_engines) == 0 or engine_class.name in config_engines:
|
1337
1386
|
if config.get_engine(engine_class.name) == None:
|
1338
1387
|
engine_instance = engine_class()
|
1339
1388
|
else:
|
1340
|
-
engine_instance = engine_class(config.get_engine(
|
1389
|
+
engine_instance = engine_class(config.get_engine(
|
1390
|
+
engine_class.name), lang=get_ocr_language())
|
1341
1391
|
|
1342
1392
|
if engine_instance.available:
|
1343
1393
|
engine_instances.append(engine_instance)
|
@@ -1369,12 +1419,14 @@ def run(read_from=None,
|
|
1369
1419
|
just_unpaused = True
|
1370
1420
|
first_pressed = None
|
1371
1421
|
auto_pause_handler = None
|
1372
|
-
engine_index = engine_keys.index(
|
1422
|
+
engine_index = engine_keys.index(
|
1423
|
+
default_engine) if default_engine != '' else 0
|
1373
1424
|
engine_color = config.get_general('engine_color')
|
1374
1425
|
prefix_to_use = ""
|
1375
1426
|
delay_secs = config.get_general('delay_secs')
|
1376
1427
|
|
1377
|
-
non_path_inputs = ('screencapture', 'clipboard',
|
1428
|
+
non_path_inputs = ('screencapture', 'clipboard',
|
1429
|
+
'websocket', 'unixsocket', 'obs')
|
1378
1430
|
read_from_path = None
|
1379
1431
|
read_from_readable = []
|
1380
1432
|
terminated = False
|
@@ -1388,7 +1440,8 @@ def run(read_from=None,
|
|
1388
1440
|
key_combo_listener = None
|
1389
1441
|
filtering = None
|
1390
1442
|
auto_pause_handler = None
|
1391
|
-
engine_index = engine_keys.index(
|
1443
|
+
engine_index = engine_keys.index(
|
1444
|
+
default_engine) if default_engine != '' else 0
|
1392
1445
|
engine_color = config.get_general('engine_color')
|
1393
1446
|
combo_pause = config.get_general('combo_pause')
|
1394
1447
|
combo_engine_switch = config.get_general('combo_engine_switch')
|
@@ -1406,7 +1459,8 @@ def run(read_from=None,
|
|
1406
1459
|
raise ValueError('combo_pause must also be specified')
|
1407
1460
|
|
1408
1461
|
if 'websocket' in (read_from, read_from_secondary) or write_to == 'websocket':
|
1409
|
-
websocket_server_thread = WebsocketServerThread(
|
1462
|
+
websocket_server_thread = WebsocketServerThread(
|
1463
|
+
'websocket' in (read_from, read_from_secondary))
|
1410
1464
|
websocket_server_thread.start()
|
1411
1465
|
|
1412
1466
|
if write_to == "callback" and text_callback:
|
@@ -1428,7 +1482,8 @@ def run(read_from=None,
|
|
1428
1482
|
last_result = ([], engine_index)
|
1429
1483
|
|
1430
1484
|
screenshot_event = threading.Event()
|
1431
|
-
screenshot_thread = ScreenshotThread(screen_capture_area, screen_capture_window,
|
1485
|
+
screenshot_thread = ScreenshotThread(screen_capture_area, screen_capture_window,
|
1486
|
+
gsm_ocr_config, screen_capture_on_combo)
|
1432
1487
|
screenshot_thread.start()
|
1433
1488
|
filtering = TextFiltering()
|
1434
1489
|
read_from_readable.append('screen capture')
|
@@ -1436,7 +1491,8 @@ def run(read_from=None,
|
|
1436
1491
|
last_screenshot_time = 0
|
1437
1492
|
last_result = ([], engine_index)
|
1438
1493
|
screenshot_event = threading.Event()
|
1439
|
-
obs_screenshot_thread = OBSScreenshotThread(
|
1494
|
+
obs_screenshot_thread = OBSScreenshotThread(
|
1495
|
+
gsm_ocr_config, screen_capture_on_combo, interval=screen_capture_delay_secs)
|
1440
1496
|
obs_screenshot_thread.start()
|
1441
1497
|
filtering = TextFiltering()
|
1442
1498
|
read_from_readable.append('obs')
|
@@ -1444,12 +1500,15 @@ def run(read_from=None,
|
|
1444
1500
|
read_from_readable.append('websocket')
|
1445
1501
|
if 'unixsocket' in (read_from, read_from_secondary):
|
1446
1502
|
if sys.platform == 'win32':
|
1447
|
-
raise ValueError(
|
1503
|
+
raise ValueError(
|
1504
|
+
'"unixsocket" is not currently supported on Windows')
|
1448
1505
|
socket_path = Path('/tmp/owocr.sock')
|
1449
1506
|
if socket_path.exists():
|
1450
1507
|
socket_path.unlink()
|
1451
|
-
unix_socket_server = socketserver.ThreadingUnixStreamServer(
|
1452
|
-
|
1508
|
+
unix_socket_server = socketserver.ThreadingUnixStreamServer(
|
1509
|
+
str(socket_path), RequestHandler)
|
1510
|
+
unix_socket_server_thread = threading.Thread(
|
1511
|
+
target=unix_socket_server.serve_forever, daemon=True)
|
1453
1512
|
unix_socket_server_thread.start()
|
1454
1513
|
read_from_readable.append('unix socket')
|
1455
1514
|
if 'clipboard' in (read_from, read_from_secondary):
|
@@ -1458,11 +1517,14 @@ def run(read_from=None,
|
|
1458
1517
|
read_from_readable.append('clipboard')
|
1459
1518
|
if any(i and i not in non_path_inputs for i in (read_from, read_from_secondary)):
|
1460
1519
|
if all(i and i not in non_path_inputs for i in (read_from, read_from_secondary)):
|
1461
|
-
raise ValueError(
|
1520
|
+
raise ValueError(
|
1521
|
+
"read_from and read_from_secondary can't both be directory paths")
|
1462
1522
|
delete_images = config.get_general('delete_images')
|
1463
|
-
read_from_path = Path(read_from) if read_from not in non_path_inputs else Path(
|
1523
|
+
read_from_path = Path(read_from) if read_from not in non_path_inputs else Path(
|
1524
|
+
read_from_secondary)
|
1464
1525
|
if not read_from_path.is_dir():
|
1465
|
-
raise ValueError(
|
1526
|
+
raise ValueError(
|
1527
|
+
'read_from and read_from_secondary must be either "websocket", "unixsocket", "clipboard", "screencapture", or a path to a directory')
|
1466
1528
|
directory_watcher_thread = DirectoryWatcher(read_from_path)
|
1467
1529
|
directory_watcher_thread.start()
|
1468
1530
|
read_from_readable.append(f'directory {read_from_path}')
|
@@ -1479,20 +1541,26 @@ def run(read_from=None,
|
|
1479
1541
|
write_to_readable = write_to
|
1480
1542
|
else:
|
1481
1543
|
if Path(write_to).suffix.lower() != '.txt':
|
1482
|
-
raise ValueError(
|
1544
|
+
raise ValueError(
|
1545
|
+
'write_to must be either "websocket", "clipboard" or a path to a text file')
|
1483
1546
|
write_to_readable = f'file {write_to}'
|
1484
1547
|
|
1485
|
-
process_queue = (any(i in ('clipboard', 'websocket', 'unixsocket') for i in (
|
1486
|
-
|
1548
|
+
process_queue = (any(i in ('clipboard', 'websocket', 'unixsocket') for i in (
|
1549
|
+
read_from, read_from_secondary)) or read_from_path or screen_capture_on_combo)
|
1550
|
+
process_screenshots = any(x in ('screencapture', 'obs') for x in (
|
1551
|
+
read_from, read_from_secondary)) and not screen_capture_on_combo
|
1487
1552
|
if threading.current_thread() == threading.main_thread():
|
1488
1553
|
signal.signal(signal.SIGINT, signal_handler)
|
1489
1554
|
if (not process_screenshots) and auto_pause != 0:
|
1490
1555
|
auto_pause_handler = AutopauseTimer(auto_pause)
|
1491
|
-
user_input_thread = threading.Thread(
|
1556
|
+
user_input_thread = threading.Thread(
|
1557
|
+
target=user_input_thread_run, daemon=True)
|
1492
1558
|
user_input_thread.start()
|
1493
|
-
logger.opt(ansi=True).info(
|
1559
|
+
logger.opt(ansi=True).info(
|
1560
|
+
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 ''}")
|
1494
1561
|
if screen_capture_combo:
|
1495
|
-
logger.opt(ansi=True).info(
|
1562
|
+
logger.opt(ansi=True).info(
|
1563
|
+
f'Manual OCR Running... Press <{engine_color}>{screen_capture_combo.replace("<", "").replace(">", "")}</{engine_color}> to run OCR')
|
1496
1564
|
|
1497
1565
|
def handle_config_changes(changes):
|
1498
1566
|
nonlocal last_result
|
@@ -1529,11 +1597,13 @@ def run(read_from=None,
|
|
1529
1597
|
break
|
1530
1598
|
elif img:
|
1531
1599
|
if filter_img:
|
1532
|
-
res, _ = process_and_write_results(img, write_to, last_result, filtering, notify,
|
1600
|
+
res, _ = process_and_write_results(img, write_to, last_result, filtering, notify,
|
1601
|
+
ocr_start_time=ocr_start_time, furigana_filter_sensitivity=get_ocr_furigana_filter_sensitivity())
|
1533
1602
|
if res:
|
1534
1603
|
last_result = (res, engine_index)
|
1535
1604
|
else:
|
1536
|
-
process_and_write_results(
|
1605
|
+
process_and_write_results(
|
1606
|
+
img, write_to, None, notify=notify, ocr_start_time=ocr_start_time, engine=ocr2)
|
1537
1607
|
if isinstance(img, Path):
|
1538
1608
|
if delete_images:
|
1539
1609
|
Path.unlink(img)
|
@@ -1547,7 +1617,8 @@ def run(read_from=None,
|
|
1547
1617
|
websocket_server_thread.join()
|
1548
1618
|
if clipboard_thread:
|
1549
1619
|
if sys.platform == 'win32':
|
1550
|
-
win32api.PostThreadMessage(
|
1620
|
+
win32api.PostThreadMessage(
|
1621
|
+
clipboard_thread.thread_id, win32con.WM_QUIT, 0, 0)
|
1551
1622
|
clipboard_thread.join()
|
1552
1623
|
if directory_watcher_thread:
|
1553
1624
|
directory_watcher_thread.join()
|
@@ -28,8 +28,8 @@ GameSentenceMiner/owocr/owocr/__init__.py,sha256=87hfN5u_PbL_onLfMACbc0F5j4KyIK9
|
|
28
28
|
GameSentenceMiner/owocr/owocr/__main__.py,sha256=XQaqZY99EKoCpU-gWQjNbTs7Kg17HvBVE7JY8LqIE0o,157
|
29
29
|
GameSentenceMiner/owocr/owocr/config.py,sha256=qM7kISHdUhuygGXOxmgU6Ef2nwBShrZtdqu4InDCViE,8103
|
30
30
|
GameSentenceMiner/owocr/owocr/lens_betterproto.py,sha256=oNoISsPilVVRBBPVDtb4-roJtAhp8ZAuFTci3TGXtMc,39141
|
31
|
-
GameSentenceMiner/owocr/owocr/ocr.py,sha256=
|
32
|
-
GameSentenceMiner/owocr/owocr/run.py,sha256
|
31
|
+
GameSentenceMiner/owocr/owocr/ocr.py,sha256=L_Dhy5qoSmVNtzBGVHM8aCJtfFsgqbSdTqXd1Obh1EM,62531
|
32
|
+
GameSentenceMiner/owocr/owocr/run.py,sha256=asd5RsYRlsN7FhnMgbjDcN_m3QtVCWzysb6P9_rplMY,68523
|
33
33
|
GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=Na6XStbQBtpQUSdbN3QhEswtKuU1JjReFk_K8t5ezQE,3395
|
34
34
|
GameSentenceMiner/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
35
35
|
GameSentenceMiner/util/audio_offset_selector.py,sha256=8Stk3BP-XVIuzRv9nl9Eqd2D-1yD3JrgU-CamBywJmY,8542
|
@@ -67,9 +67,9 @@ GameSentenceMiner/web/templates/index.html,sha256=Gv3CJvNnhAzIVV_QxhNq4OD-pXDt1v
|
|
67
67
|
GameSentenceMiner/web/templates/text_replacements.html,sha256=tV5c8mCaWSt_vKuUpbdbLAzXZ3ATZeDvQ9PnnAfqY0M,8598
|
68
68
|
GameSentenceMiner/web/templates/utility.html,sha256=3flZinKNqUJ7pvrZk6xu__v67z44rXnaK7UTZ303R-8,16946
|
69
69
|
GameSentenceMiner/wip/get_overlay_coords.py,sha256=nJRytHJwUBToXeAIkf45HP7Yv42YO-ILbP5h8GVeE2Q,19791
|
70
|
-
gamesentenceminer-2.13.
|
71
|
-
gamesentenceminer-2.13.
|
72
|
-
gamesentenceminer-2.13.
|
73
|
-
gamesentenceminer-2.13.
|
74
|
-
gamesentenceminer-2.13.
|
75
|
-
gamesentenceminer-2.13.
|
70
|
+
gamesentenceminer-2.13.7.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
71
|
+
gamesentenceminer-2.13.7.dist-info/METADATA,sha256=kx4ThmCKdURsRILfpqv07hpD0rqSj85IX-VRPeuRbYA,1463
|
72
|
+
gamesentenceminer-2.13.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
73
|
+
gamesentenceminer-2.13.7.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
74
|
+
gamesentenceminer-2.13.7.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
75
|
+
gamesentenceminer-2.13.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|