GameSentenceMiner 2.13.5.post1__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.
@@ -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 = 'qwenvl'
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
- # from transformers import Qwen2VLForConditionalGeneration, AutoProcessor
1380
- # self.model = Qwen2VLForConditionalGeneration.from_pretrained(
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('test_furigana.png'), furigana_filter_sensitivity=0) # Example usage
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_(new_image)
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(win32clipboard.CF_UNICODETEXT)
126
+ clipboard_text = win32clipboard.GetClipboardData(
127
+ win32clipboard.CF_UNICODETEXT)
126
128
  if self.ignore_flag or clipboard_text != '*ocr_ignore*':
127
- img = win32clipboard.GetClipboardData(win32clipboard.CF_DIB)
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_(NSPasteboardTypeString)
179
+ clipboard_text = pasteboard.stringForType_(
180
+ NSPasteboardTypeString)
177
181
  if self.ignore_flag or clipboard_text != '*ocr_ignore*':
178
- img = self.normalize_macos_clipboard(pasteboard.dataForType_(NSPasteboardTypeTIFF))
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 = ('.png', '.jpg', '.jpeg', '.bmp', '.gif', '.webp')
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(self.server_handler, '0.0.0.0', config.get_general('websocket_port'), max_size=1000000000)
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(r'[\u3041-\u3096\u30A1-\u30FA\u4E00-\u9FFF]')
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(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]')
324
- self.russian_regex = re.compile(r'[\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u1C80-\u1C8F]')
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 = False
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('text-classification', model=model_ckpt, tokenizer=tokenizer, device=device)
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(str(block).strip().replace("BLANK_LINE", "\n"))
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, screen_capture_exclusions, screen_capture_areas, screen_capture_on_combo):
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('Invalid monitor number in screen_capture_area')
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 = [int(c.strip()) for c in screen_capture_area.split(',')]
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('Launching screen coordinate picker')
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('Picker window was closed or an error occurred')
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('Selection is empty, selecting whole screen')
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, 'width': coord_width, 'height': coord_height}
487
- logger.opt(ansi=True).info(f'Selected coordinates: {coord_left},{coord_top},{coord_width},{coord_height}')
488
- if screen_capture_areas:
489
- for area in screen_capture_areas:
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()) for c in screen_capture_area.split(',')]))
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(kCGWindowListExcludeDesktopElements, kCGNullWindowID)
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(target=self.macos_window_tracker)
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(screen_capture_window)
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(target=self.windows_window_tracker)
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('Window capture is only currently supported on Windows and macOS')
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(self.window_handle)
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().initWithDesktopIndependentWindow_(target_window)
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(0, 0, frame.size.width, frame.size.height))
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(kCGWindowListOptionOnScreenOnly, kCGNullWindowID)
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([self.window_id])
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(self.window_id)
705
+ self.capture_macos_window_screenshot(
706
+ self.window_id)
683
707
  try:
684
- cg_image = self.screencapturekit_queue.get(timeout=0.5)
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(CGImageGetDataProvider(cg_image))
716
+ raw_data = CGDataProviderCopyData(
717
+ CGImageGetDataProvider(cg_image))
692
718
  bpr = CGImageGetBytesPerRow(cg_image)
693
- img = Image.frombuffer('RGBA', (width, height), raw_data, 'raw', 'BGRA', bpr, 1)
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(self.window_handle)
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(mfc_dc, coord_width, coord_height)
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(self.window_handle, save_dc.GetSafeHdc(), 2)
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('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
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("Screen Capture Didn't get Capturing anything, sleeping.")
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
- rand_int = random.randint(1, 20) # Executes only once out of 10 times
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(), 'before_crop.png'), 'PNG')
746
-
747
- if self.screen_capture_exclusions:
748
- img = img.convert("RGBA")
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(get_temporary_directory(), 'after_crop.png'), 'PNG')
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("Captured screenshot is identical to the last one, sleeping.")
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("Failed to convert images to numpy arrays for comparison.")
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(f"Scaling down OBS source dimensions: {width}x{height} (Aspect Ratio: {aspect_ratio})")
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("Using default aspect ratio scaling (original resolution).")
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("sceneItemTransform").get("sourceWidth") or self.width
893
- self.source_height = self.current_source.get("sceneItemTransform").get("sourceHeight") or self.height
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(self.source_width, self.source_height)
896
- logger.info(f"Using OBS source dimensions: {self.width}x{self.height}")
897
- self.current_source_name = self.current_source.get("sourceName") or None
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,21 +944,29 @@ class OBSScreenshotThread(threading.Thread):
927
944
  continue
928
945
 
929
946
  if not self.ocr_config:
930
- logger.info("No OCR config found for the current scene. Waiting for scene switch.")
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("sourceName") or None
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("No active source found in the current scene.")
960
+ logger.error(
961
+ "No active source found in the current scene.")
942
962
  time.sleep(1)
943
963
  continue
944
- img = obs.get_screenshot_PIL(source_name=self.current_source_name, width=self.width, height=self.height, img_format='png', compression=80)
964
+ # start_time = time.time()
965
+ img = obs.get_screenshot_PIL(source_name=self.current_source_name,
966
+ width=self.width, height=self.height, img_format='jpg', compression=80)
967
+ # logger.info(f"OBS screenshot taken in {time.time() - start_time:.2f} seconds.")
968
+
969
+ img = apply_ocr_config_to_image(img, self.ocr_config)
945
970
 
946
971
  if img is not None:
947
972
  if not img.getbbox():
@@ -949,37 +974,9 @@ class OBSScreenshotThread(threading.Thread):
949
974
  time.sleep(1)
950
975
  continue
951
976
 
952
- for rectangle in self.ocr_config.rectangles:
953
- if rectangle.is_excluded:
954
- left, top, width, height = rectangle.coordinates
955
- draw = ImageDraw.Draw(img)
956
- draw.rectangle((left, top, left + width, top + height), fill=(0, 0, 0, 0))
957
-
958
- cropped_sections = []
959
- for rectangle in [r for r in self.ocr_config.rectangles if not r.is_excluded]:
960
- area = rectangle.coordinates
961
- # Ensure crop coordinates are within image bounds
962
- left = max(0, area[0])
963
- top = max(0, area[1])
964
- right = min(img.width, area[0] + area[2])
965
- bottom = min(img.height, area[1] + area[3])
966
- cropped_sections.append(img.crop((left, top, right, bottom)))
967
-
968
- if len(cropped_sections) > 1:
969
- combined_width = max(section.width for section in cropped_sections)
970
- combined_height = sum(section.height for section in cropped_sections) + (
971
- len(cropped_sections) - 1) * 10
972
- combined_img = Image.new("RGBA", (combined_width, combined_height))
973
- y_offset = 0
974
- for section in cropped_sections:
975
- combined_img.paste(section, (0, y_offset))
976
- y_offset += section.height + 50
977
- img = combined_img
978
- elif cropped_sections:
979
- img = cropped_sections[0]
980
-
981
977
  if last_image and are_images_identical(img, last_image):
982
- logger.debug("Captured screenshot is identical to the last one, sleeping.")
978
+ logger.debug(
979
+ "Captured screenshot is identical to the last one, sleeping.")
983
980
  time.sleep(max(.5, get_ocr_scan_rate()))
984
981
  else:
985
982
  self.write_result(img)
@@ -988,9 +985,53 @@ class OBSScreenshotThread(threading.Thread):
988
985
  logger.error("Failed to get screenshot data from OBS.")
989
986
 
990
987
  except Exception as e:
991
- logger.error(f"An unexpected error occurred during OBS Capture : {e}", exc_info=True)
988
+ logger.error(
989
+ f"An unexpected error occurred during OBS Capture : {e}", exc_info=True)
992
990
  continue
993
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
+
994
1035
  class AutopauseTimer:
995
1036
  def __init__(self, timeout):
996
1037
  self.stop_event = threading.Event()
@@ -1048,9 +1089,11 @@ def engine_change_handler(user_input='s', is_combo=True):
1048
1089
  if engine_index != old_engine_index:
1049
1090
  new_engine_name = engine_instances[engine_index].readable_name
1050
1091
  if is_combo:
1051
- notifier.send(title='owocr', message=f'Switched to {new_engine_name}')
1092
+ notifier.send(
1093
+ title='owocr', message=f'Switched to {new_engine_name}')
1052
1094
  engine_color = config.get_general('engine_color')
1053
- logger.opt(ansi=True).info(f'Switched to <{engine_color}>{new_engine_name}</{engine_color}>!')
1095
+ logger.opt(ansi=True).info(
1096
+ f'Switched to <{engine_color}>{new_engine_name}</{engine_color}>!')
1054
1097
 
1055
1098
 
1056
1099
  def engine_change_handler_name(engine):
@@ -1066,7 +1109,8 @@ def engine_change_handler_name(engine):
1066
1109
  new_engine_name = engine_instances[engine_index].readable_name
1067
1110
  notifier.send(title='owocr', message=f'Switched to {new_engine_name}')
1068
1111
  engine_color = config.get_general('engine_color')
1069
- logger.opt(ansi=True).info(f'Switched to <{engine_color}>{new_engine_name}</{engine_color}>!')
1112
+ logger.opt(ansi=True).info(
1113
+ f'Switched to <{engine_color}>{new_engine_name}</{engine_color}>!')
1070
1114
 
1071
1115
 
1072
1116
  def user_input_thread_run():
@@ -1097,7 +1141,8 @@ def user_input_thread_run():
1097
1141
  else:
1098
1142
  engine_change_handler(user_input, False)
1099
1143
  else:
1100
- import tty, termios
1144
+ import tty
1145
+ import termios
1101
1146
  fd = sys.stdin.fileno()
1102
1147
  old_settings = termios.tcgetattr(fd)
1103
1148
  try:
@@ -1149,16 +1194,15 @@ def process_and_write_results(img_or_path, write_to=None, last_result=None, filt
1149
1194
  else:
1150
1195
  engine_instance = engine_instances[engine_index]
1151
1196
 
1152
-
1153
1197
  engine_color = config.get_general('engine_color')
1154
1198
 
1155
1199
  start_time = time.time()
1156
1200
  result = engine_instance(img_or_path, furigana_filter_sensitivity)
1157
1201
  res, text, crop_coords = (*result, None)[:3]
1158
1202
 
1159
-
1160
1203
  if not res and ocr_2 == engine:
1161
- logger.opt(ansi=True).info(f"<{engine_color}>{engine_instance.readable_name}</{engine_color}> failed with message: {text}, trying <{engine_color}>{ocr_1}</{engine_color}>")
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}>")
1162
1206
  for i, instance in enumerate(engine_instances):
1163
1207
  if instance.name.lower() in ocr_1.lower():
1164
1208
  engine_instance = instance
@@ -1171,7 +1215,6 @@ def process_and_write_results(img_or_path, write_to=None, last_result=None, filt
1171
1215
 
1172
1216
  end_time = time.time()
1173
1217
 
1174
-
1175
1218
  orig_text = []
1176
1219
  # print(filtering)
1177
1220
  #
@@ -1186,7 +1229,8 @@ def process_and_write_results(img_or_path, write_to=None, last_result=None, filt
1186
1229
  text, orig_text = filtering(text, last_result)
1187
1230
  if get_ocr_language() == "ja" or get_ocr_language() == "zh":
1188
1231
  text = post_process(text, keep_blank_lines=get_ocr_keep_newline())
1189
- 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}')
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}')
1190
1234
  if notify and config.get_general('notifications'):
1191
1235
  notifier.send(title='owocr', message='Text recognized: ' + text)
1192
1236
 
@@ -1195,7 +1239,8 @@ def process_and_write_results(img_or_path, write_to=None, last_result=None, filt
1195
1239
  elif write_to == 'clipboard':
1196
1240
  pyperclipfix.copy(text)
1197
1241
  elif write_to == "callback":
1198
- txt_callback(text, orig_text, ocr_start_time, img_or_path, bool(engine), filtering, crop_coords)
1242
+ txt_callback(text, orig_text, ocr_start_time,
1243
+ img_or_path, bool(engine), filtering, crop_coords)
1199
1244
  elif write_to:
1200
1245
  with Path(write_to).open('a', encoding='utf-8') as f:
1201
1246
  f.write(text + '\n')
@@ -1203,7 +1248,8 @@ def process_and_write_results(img_or_path, write_to=None, last_result=None, filt
1203
1248
  if auto_pause_handler and not paused:
1204
1249
  auto_pause_handler.start()
1205
1250
  else:
1206
- 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}')
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}')
1207
1253
 
1208
1254
  # print(orig_text)
1209
1255
  # print(text)
@@ -1284,13 +1330,15 @@ def run(read_from=None,
1284
1330
  # screen_capture_only_active_windows = config.get_general('screen_capture_only_active_windows')
1285
1331
 
1286
1332
  if screen_capture_exclusions is None:
1287
- screen_capture_exclusions = config.get_general('screen_capture_exclusions')
1333
+ screen_capture_exclusions = config.get_general(
1334
+ 'screen_capture_exclusions')
1288
1335
 
1289
1336
  if screen_capture_window is None:
1290
1337
  screen_capture_window = config.get_general('screen_capture_window')
1291
1338
 
1292
1339
  if screen_capture_delay_secs is None:
1293
- screen_capture_delay_secs = config.get_general('screen_capture_delay_secs')
1340
+ screen_capture_delay_secs = config.get_general(
1341
+ 'screen_capture_delay_secs')
1294
1342
 
1295
1343
  if screen_capture_combo is None:
1296
1344
  screen_capture_combo = config.get_general('screen_capture_combo')
@@ -1299,7 +1347,8 @@ def run(read_from=None,
1299
1347
  stop_running_flag = config.get_general('stop_running_flag')
1300
1348
 
1301
1349
  if screen_capture_event_bus is None:
1302
- screen_capture_event_bus = config.get_general('screen_capture_event_bus')
1350
+ screen_capture_event_bus = config.get_general(
1351
+ 'screen_capture_event_bus')
1303
1352
 
1304
1353
  if text_callback is None:
1305
1354
  text_callback = config.get_general('text_callback')
@@ -1307,14 +1356,16 @@ def run(read_from=None,
1307
1356
  if write_to is None:
1308
1357
  write_to = config.get_general('write_to')
1309
1358
 
1310
- logger.configure(handlers=[{'sink': sys.stderr, 'format': config.get_general('logger_format')}])
1359
+ logger.configure(
1360
+ handlers=[{'sink': sys.stderr, 'format': config.get_general('logger_format')}])
1311
1361
 
1312
1362
  if config.has_config:
1313
1363
  logger.info('Parsed config file')
1314
1364
  else:
1315
1365
  logger.warning('No config file, defaults will be used.')
1316
1366
  if config.downloaded_config:
1317
- logger.info(f'A default config file has been downloaded to {config.config_path}')
1367
+ logger.info(
1368
+ f'A default config file has been downloaded to {config.config_path}')
1318
1369
 
1319
1370
  global engine_instances
1320
1371
  global engine_keys
@@ -1329,13 +1380,14 @@ def run(read_from=None,
1329
1380
 
1330
1381
  for _, engine_class in sorted(inspect.getmembers(sys.modules[__name__],
1331
1382
  lambda x: hasattr(x, '__module__') and x.__module__ and (
1332
- __package__ + '.ocr' in x.__module__ or __package__ + '.secret' in x.__module__) and inspect.isclass(
1383
+ __package__ + '.ocr' in x.__module__ or __package__ + '.secret' in x.__module__) and inspect.isclass(
1333
1384
  x))):
1334
1385
  if len(config_engines) == 0 or engine_class.name in config_engines:
1335
1386
  if config.get_engine(engine_class.name) == None:
1336
1387
  engine_instance = engine_class()
1337
1388
  else:
1338
- engine_instance = engine_class(config.get_engine(engine_class.name), lang=get_ocr_language())
1389
+ engine_instance = engine_class(config.get_engine(
1390
+ engine_class.name), lang=get_ocr_language())
1339
1391
 
1340
1392
  if engine_instance.available:
1341
1393
  engine_instances.append(engine_instance)
@@ -1367,12 +1419,14 @@ def run(read_from=None,
1367
1419
  just_unpaused = True
1368
1420
  first_pressed = None
1369
1421
  auto_pause_handler = None
1370
- engine_index = engine_keys.index(default_engine) if default_engine != '' else 0
1422
+ engine_index = engine_keys.index(
1423
+ default_engine) if default_engine != '' else 0
1371
1424
  engine_color = config.get_general('engine_color')
1372
1425
  prefix_to_use = ""
1373
1426
  delay_secs = config.get_general('delay_secs')
1374
1427
 
1375
- non_path_inputs = ('screencapture', 'clipboard', 'websocket', 'unixsocket', 'obs')
1428
+ non_path_inputs = ('screencapture', 'clipboard',
1429
+ 'websocket', 'unixsocket', 'obs')
1376
1430
  read_from_path = None
1377
1431
  read_from_readable = []
1378
1432
  terminated = False
@@ -1386,7 +1440,8 @@ def run(read_from=None,
1386
1440
  key_combo_listener = None
1387
1441
  filtering = None
1388
1442
  auto_pause_handler = None
1389
- engine_index = engine_keys.index(default_engine) if default_engine != '' else 0
1443
+ engine_index = engine_keys.index(
1444
+ default_engine) if default_engine != '' else 0
1390
1445
  engine_color = config.get_general('engine_color')
1391
1446
  combo_pause = config.get_general('combo_pause')
1392
1447
  combo_engine_switch = config.get_general('combo_engine_switch')
@@ -1404,7 +1459,8 @@ def run(read_from=None,
1404
1459
  raise ValueError('combo_pause must also be specified')
1405
1460
 
1406
1461
  if 'websocket' in (read_from, read_from_secondary) or write_to == 'websocket':
1407
- websocket_server_thread = WebsocketServerThread('websocket' in (read_from, read_from_secondary))
1462
+ websocket_server_thread = WebsocketServerThread(
1463
+ 'websocket' in (read_from, read_from_secondary))
1408
1464
  websocket_server_thread.start()
1409
1465
 
1410
1466
  if write_to == "callback" and text_callback:
@@ -1426,7 +1482,8 @@ def run(read_from=None,
1426
1482
  last_result = ([], engine_index)
1427
1483
 
1428
1484
  screenshot_event = threading.Event()
1429
- screenshot_thread = ScreenshotThread(screen_capture_area, screen_capture_window, screen_capture_exclusions, screen_capture_areas, screen_capture_on_combo)
1485
+ screenshot_thread = ScreenshotThread(screen_capture_area, screen_capture_window,
1486
+ gsm_ocr_config, screen_capture_on_combo)
1430
1487
  screenshot_thread.start()
1431
1488
  filtering = TextFiltering()
1432
1489
  read_from_readable.append('screen capture')
@@ -1434,7 +1491,8 @@ def run(read_from=None,
1434
1491
  last_screenshot_time = 0
1435
1492
  last_result = ([], engine_index)
1436
1493
  screenshot_event = threading.Event()
1437
- obs_screenshot_thread = OBSScreenshotThread(gsm_ocr_config, screen_capture_on_combo, interval=screen_capture_delay_secs)
1494
+ obs_screenshot_thread = OBSScreenshotThread(
1495
+ gsm_ocr_config, screen_capture_on_combo, interval=screen_capture_delay_secs)
1438
1496
  obs_screenshot_thread.start()
1439
1497
  filtering = TextFiltering()
1440
1498
  read_from_readable.append('obs')
@@ -1442,12 +1500,15 @@ def run(read_from=None,
1442
1500
  read_from_readable.append('websocket')
1443
1501
  if 'unixsocket' in (read_from, read_from_secondary):
1444
1502
  if sys.platform == 'win32':
1445
- raise ValueError('"unixsocket" is not currently supported on Windows')
1503
+ raise ValueError(
1504
+ '"unixsocket" is not currently supported on Windows')
1446
1505
  socket_path = Path('/tmp/owocr.sock')
1447
1506
  if socket_path.exists():
1448
1507
  socket_path.unlink()
1449
- unix_socket_server = socketserver.ThreadingUnixStreamServer(str(socket_path), RequestHandler)
1450
- unix_socket_server_thread = threading.Thread(target=unix_socket_server.serve_forever, daemon=True)
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)
1451
1512
  unix_socket_server_thread.start()
1452
1513
  read_from_readable.append('unix socket')
1453
1514
  if 'clipboard' in (read_from, read_from_secondary):
@@ -1456,11 +1517,14 @@ def run(read_from=None,
1456
1517
  read_from_readable.append('clipboard')
1457
1518
  if any(i and i not in non_path_inputs for i in (read_from, read_from_secondary)):
1458
1519
  if all(i and i not in non_path_inputs for i in (read_from, read_from_secondary)):
1459
- raise ValueError("read_from and read_from_secondary can't both be directory paths")
1520
+ raise ValueError(
1521
+ "read_from and read_from_secondary can't both be directory paths")
1460
1522
  delete_images = config.get_general('delete_images')
1461
- read_from_path = Path(read_from) if read_from not in non_path_inputs else Path(read_from_secondary)
1523
+ read_from_path = Path(read_from) if read_from not in non_path_inputs else Path(
1524
+ read_from_secondary)
1462
1525
  if not read_from_path.is_dir():
1463
- raise ValueError('read_from and read_from_secondary must be either "websocket", "unixsocket", "clipboard", "screencapture", or a path to a directory')
1526
+ raise ValueError(
1527
+ 'read_from and read_from_secondary must be either "websocket", "unixsocket", "clipboard", "screencapture", or a path to a directory')
1464
1528
  directory_watcher_thread = DirectoryWatcher(read_from_path)
1465
1529
  directory_watcher_thread.start()
1466
1530
  read_from_readable.append(f'directory {read_from_path}')
@@ -1477,20 +1541,26 @@ def run(read_from=None,
1477
1541
  write_to_readable = write_to
1478
1542
  else:
1479
1543
  if Path(write_to).suffix.lower() != '.txt':
1480
- raise ValueError('write_to must be either "websocket", "clipboard" or a path to a text file')
1544
+ raise ValueError(
1545
+ 'write_to must be either "websocket", "clipboard" or a path to a text file')
1481
1546
  write_to_readable = f'file {write_to}'
1482
1547
 
1483
- 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)
1484
- process_screenshots = any(x in ('screencapture', 'obs') for x in (read_from, read_from_secondary)) and not screen_capture_on_combo
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
1485
1552
  if threading.current_thread() == threading.main_thread():
1486
1553
  signal.signal(signal.SIGINT, signal_handler)
1487
1554
  if (not process_screenshots) and auto_pause != 0:
1488
1555
  auto_pause_handler = AutopauseTimer(auto_pause)
1489
- user_input_thread = threading.Thread(target=user_input_thread_run, daemon=True)
1556
+ user_input_thread = threading.Thread(
1557
+ target=user_input_thread_run, daemon=True)
1490
1558
  user_input_thread.start()
1491
- 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 ''}")
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 ''}")
1492
1561
  if screen_capture_combo:
1493
- logger.opt(ansi=True).info(f'Manual OCR Running... Press <{engine_color}>{screen_capture_combo.replace("<", "").replace(">", "")}</{engine_color}> to run OCR')
1562
+ logger.opt(ansi=True).info(
1563
+ f'Manual OCR Running... Press <{engine_color}>{screen_capture_combo.replace("<", "").replace(">", "")}</{engine_color}> to run OCR')
1494
1564
 
1495
1565
  def handle_config_changes(changes):
1496
1566
  nonlocal last_result
@@ -1527,11 +1597,13 @@ def run(read_from=None,
1527
1597
  break
1528
1598
  elif img:
1529
1599
  if filter_img:
1530
- res, _ = process_and_write_results(img, write_to, last_result, filtering, notify, ocr_start_time=ocr_start_time, furigana_filter_sensitivity=get_ocr_furigana_filter_sensitivity())
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())
1531
1602
  if res:
1532
1603
  last_result = (res, engine_index)
1533
1604
  else:
1534
- process_and_write_results(img, write_to, None, notify=notify, ocr_start_time=ocr_start_time, engine=ocr2)
1605
+ process_and_write_results(
1606
+ img, write_to, None, notify=notify, ocr_start_time=ocr_start_time, engine=ocr2)
1535
1607
  if isinstance(img, Path):
1536
1608
  if delete_images:
1537
1609
  Path.unlink(img)
@@ -1545,7 +1617,8 @@ def run(read_from=None,
1545
1617
  websocket_server_thread.join()
1546
1618
  if clipboard_thread:
1547
1619
  if sys.platform == 'win32':
1548
- win32api.PostThreadMessage(clipboard_thread.thread_id, win32con.WM_QUIT, 0, 0)
1620
+ win32api.PostThreadMessage(
1621
+ clipboard_thread.thread_id, win32con.WM_QUIT, 0, 0)
1549
1622
  clipboard_thread.join()
1550
1623
  if directory_watcher_thread:
1551
1624
  directory_watcher_thread.join()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.13.5.post1
3
+ Version: 2.13.7
4
4
  Summary: A tool for mining sentences from games. Update: Overlay?
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
@@ -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=NHo-sjE_ZGUjPzzJqMDmFs29xbIvvQQyCBRQ61PTyfo,62221
32
- GameSentenceMiner/owocr/owocr/run.py,sha256=ex0TZp3WTN2qTjrZ5D3Q-Gma4iZ0NPXGaHddTPIqRYs,68229
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.5.post1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
71
- gamesentenceminer-2.13.5.post1.dist-info/METADATA,sha256=RZAfAc3aTumTUQeL_1T6iGHryRRDKn4q7erBdbqUvG8,1469
72
- gamesentenceminer-2.13.5.post1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
73
- gamesentenceminer-2.13.5.post1.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
74
- gamesentenceminer-2.13.5.post1.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
75
- gamesentenceminer-2.13.5.post1.dist-info/RECORD,,
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,,