GameSentenceMiner 2.12.2__py3-none-any.whl → 2.12.4__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/config_gui.py +25 -1
- GameSentenceMiner/gametext.py +1 -1
- GameSentenceMiner/obs.py +71 -1
- GameSentenceMiner/owocr/owocr/ocr.py +28 -31
- GameSentenceMiner/owocr/owocr/run.py +25 -64
- GameSentenceMiner/util/configuration.py +1 -0
- GameSentenceMiner/util/downloader/oneocr_dl.py +9 -8
- GameSentenceMiner/wip/get_overlay_coords.py +373 -98
- {gamesentenceminer-2.12.2.dist-info → gamesentenceminer-2.12.4.dist-info}/METADATA +3 -1
- {gamesentenceminer-2.12.2.dist-info → gamesentenceminer-2.12.4.dist-info}/RECORD +14 -14
- {gamesentenceminer-2.12.2.dist-info → gamesentenceminer-2.12.4.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.12.2.dist-info → gamesentenceminer-2.12.4.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.12.2.dist-info → gamesentenceminer-2.12.4.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.12.2.dist-info → gamesentenceminer-2.12.4.dist-info}/top_level.txt +0 -0
GameSentenceMiner/config_gui.py
CHANGED
@@ -139,6 +139,16 @@ class ConfigApp:
|
|
139
139
|
self.ai_tab = None
|
140
140
|
self.advanced_tab = None
|
141
141
|
self.wip_tab = None
|
142
|
+
self.monitors = []
|
143
|
+
|
144
|
+
try:
|
145
|
+
import mss as mss
|
146
|
+
self.monitors = [f"Monitor {i}: width: {monitor['width']}, height: {monitor['height']}" for i, monitor in enumerate(mss.mss().monitors[1:], start=1)]
|
147
|
+
print(self.monitors)
|
148
|
+
if len(self.monitors) == 0:
|
149
|
+
self.monitors = [1]
|
150
|
+
except ImportError:
|
151
|
+
self.monitors = []
|
142
152
|
|
143
153
|
self.create_tabs()
|
144
154
|
|
@@ -375,7 +385,8 @@ class ConfigApp:
|
|
375
385
|
),
|
376
386
|
wip=WIP(
|
377
387
|
overlay_websocket_port=int(self.overlay_websocket_port.get()),
|
378
|
-
overlay_websocket_send=self.overlay_websocket_send.get()
|
388
|
+
overlay_websocket_send=self.overlay_websocket_send.get(),
|
389
|
+
monitor_to_capture=self.monitor_to_capture.current()
|
379
390
|
)
|
380
391
|
)
|
381
392
|
|
@@ -1743,6 +1754,19 @@ class ConfigApp:
|
|
1743
1754
|
row=self.current_row, column=1, sticky='W', pady=2)
|
1744
1755
|
self.current_row += 1
|
1745
1756
|
|
1757
|
+
HoverInfoLabelWidget(wip_frame, text="Monitor to Capture:",
|
1758
|
+
tooltip="Select the monitor to capture (1-based index).",
|
1759
|
+
row=self.current_row, column=0)
|
1760
|
+
self.monitor_to_capture = ttk.Combobox(wip_frame, values=self.monitors, state="readonly")
|
1761
|
+
|
1762
|
+
# set index of monitor to capture, not the string
|
1763
|
+
if self.monitors:
|
1764
|
+
self.monitor_to_capture.current(self.settings.wip.monitor_to_capture)
|
1765
|
+
else:
|
1766
|
+
self.monitor_to_capture.set("OwOCR Not Detected")
|
1767
|
+
self.monitor_to_capture.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1768
|
+
self.current_row += 1
|
1769
|
+
|
1746
1770
|
self.add_reset_button(wip_frame, "wip", self.current_row, 0, self.create_wip_tab)
|
1747
1771
|
|
1748
1772
|
for col in range(2):
|
GameSentenceMiner/gametext.py
CHANGED
@@ -137,7 +137,7 @@ async def find_box_for_sentence(sentence):
|
|
137
137
|
boxes = []
|
138
138
|
logger.info(f"Finding Box for Sentence: {sentence}")
|
139
139
|
boxes, font_size = await get_overlay_coords.find_box_for_sentence(sentence)
|
140
|
-
logger.info(f"Found Boxes: {boxes}, Font Size: {font_size}")
|
140
|
+
# logger.info(f"Found Boxes: {boxes}, Font Size: {font_size}")
|
141
141
|
# if boxes:
|
142
142
|
# x1, y1, x2, y2 = box
|
143
143
|
# boxes.append({'sentence': sentence, 'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2, 'fontSize': font_size})
|
GameSentenceMiner/obs.py
CHANGED
@@ -383,7 +383,38 @@ def get_screenshot_base64(compression=75, width=None, height=None):
|
|
383
383
|
except Exception as e:
|
384
384
|
logger.error(f"Error getting screenshot: {e}")
|
385
385
|
return None
|
386
|
+
|
387
|
+
|
388
|
+
def get_screenshot_PIL(source_name=None, compression=75, img_format='png', width=None, height=None, retry=3):
|
389
|
+
import io
|
390
|
+
import base64
|
391
|
+
from PIL import Image
|
392
|
+
if not source_name:
|
393
|
+
source_name = get_active_source().get('sourceName', None)
|
394
|
+
if not source_name:
|
395
|
+
logger.error("No active source found in the current scene.")
|
396
|
+
return None
|
397
|
+
while True:
|
398
|
+
response = client.get_source_screenshot(name=source_name, img_format=img_format, quality=compression, width=width, height=height)
|
399
|
+
try:
|
400
|
+
response.image_data = response.image_data.split(',', 1)[-1] # Remove data:image/png;base64, prefix if present
|
401
|
+
except AttributeError:
|
402
|
+
retry -= 1
|
403
|
+
if retry <= 0:
|
404
|
+
logger.error(f"Error getting screenshot: {response}")
|
405
|
+
return None
|
406
|
+
continue
|
407
|
+
if response and response.image_data:
|
408
|
+
image_data = response.image_data.split(',', 1)[-1] # Remove data:image/png;base64, prefix if present
|
409
|
+
image_data = base64.b64decode(image_data)
|
410
|
+
img = Image.open(io.BytesIO(image_data)).convert("RGBA")
|
411
|
+
# if width and height:
|
412
|
+
# img = img.resize((width, height), Image.Resampling.LANCZOS)
|
413
|
+
return img
|
414
|
+
return None
|
386
415
|
|
416
|
+
|
417
|
+
|
387
418
|
|
388
419
|
def update_current_game():
|
389
420
|
gsm_state.current_game = get_current_scene()
|
@@ -433,8 +464,47 @@ def main():
|
|
433
464
|
disconnect_from_obs()
|
434
465
|
|
435
466
|
if __name__ == '__main__':
|
467
|
+
from mss import mss
|
436
468
|
logging.basicConfig(level=logging.INFO)
|
437
469
|
# main()
|
438
470
|
connect_to_obs_sync()
|
439
|
-
|
471
|
+
# i = 100
|
472
|
+
# for i in range(1, 100):
|
473
|
+
# print(f"Getting screenshot {i}")
|
474
|
+
# start = time.time()
|
475
|
+
# # get_screenshot(compression=95)
|
476
|
+
# # get_screenshot_base64(compression=95, width=1280, height=720)
|
477
|
+
|
478
|
+
# img = get_screenshot_PIL(compression=i, img_format='jpg', width=1280, height=720)
|
479
|
+
# end = time.time()
|
480
|
+
# print(f"Time taken to get screenshot with compression {i}: {end - start} seconds")
|
481
|
+
|
482
|
+
# for i in range(1, 100):
|
483
|
+
# print(f"Getting screenshot {i}")
|
484
|
+
# start = time.time()
|
485
|
+
# # get_screenshot(compression=95)
|
486
|
+
# # get_screenshot_base64(compression=95, width=1280, height=720)
|
487
|
+
|
488
|
+
# img = get_screenshot_PIL(compression=i, img_format='jpg', width=2560, height=1440)
|
489
|
+
# end = time.time()
|
490
|
+
# print(f"Time taken to get screenshot full sized jpg with compression {i}: {end - start} seconds")
|
491
|
+
|
492
|
+
# png_img = get_screenshot_PIL(compression=75, img_format='png', width=1280, height=720)
|
493
|
+
|
494
|
+
# jpg_img = get_screenshot_PIL(compression=100, img_format='jpg', width=2560, height=1440)
|
495
|
+
|
496
|
+
# png_img.show()
|
497
|
+
# jpg_img.show()
|
498
|
+
|
499
|
+
# start = time.time()
|
500
|
+
# with mss() as sct:
|
501
|
+
# monitor = sct.monitors[1]
|
502
|
+
# sct_img = sct.grab(monitor)
|
503
|
+
# img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
|
504
|
+
# img.show()
|
505
|
+
# end = time.time()
|
506
|
+
# print(f"Time taken to get screenshot with mss: {end - start} seconds")
|
507
|
+
|
508
|
+
|
509
|
+
# print(get_screenshot_base64(compression=75, width=1280, height=720))
|
440
510
|
|
@@ -436,7 +436,7 @@ class GoogleLens:
|
|
436
436
|
# res += '\n'
|
437
437
|
|
438
438
|
if return_coords:
|
439
|
-
x = (True, res,
|
439
|
+
x = (True, res, response_dict)
|
440
440
|
else:
|
441
441
|
x = (True, res)
|
442
442
|
|
@@ -887,7 +887,28 @@ class OneOCR:
|
|
887
887
|
except:
|
888
888
|
logger.warning('Error reading URL from config, OneOCR will not work!')
|
889
889
|
|
890
|
-
def
|
890
|
+
def get_regex(self, lang):
|
891
|
+
if lang == "ja":
|
892
|
+
self.regex = re.compile(r'[\u3041-\u3096\u30A1-\u30FA\u4E00-\u9FFF]')
|
893
|
+
elif lang == "zh":
|
894
|
+
self.regex = re.compile(r'[\u4E00-\u9FFF]')
|
895
|
+
elif lang == "ko":
|
896
|
+
self.regex = re.compile(r'[\uAC00-\uD7AF]')
|
897
|
+
elif lang == "ar":
|
898
|
+
self.regex = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]')
|
899
|
+
elif lang == "ru":
|
900
|
+
self.regex = re.compile(r'[\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u1C80-\u1C8F]')
|
901
|
+
elif lang == "el":
|
902
|
+
self.regex = re.compile(r'[\u0370-\u03FF\u1F00-\u1FFF]')
|
903
|
+
elif lang == "he":
|
904
|
+
self.regex = re.compile(r'[\u0590-\u05FF\uFB1D-\uFB4F]')
|
905
|
+
elif lang == "th":
|
906
|
+
self.regex = re.compile(r'[\u0E00-\u0E7F]')
|
907
|
+
else:
|
908
|
+
self.regex = re.compile(
|
909
|
+
r'[a-zA-Z\u00C0-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u1D00-\u1D7F\u1D80-\u1DBF\u1E00-\u1EFF\u2C60-\u2C7F\uA720-\uA7FF\uAB30-\uAB6F]')
|
910
|
+
|
911
|
+
def __call__(self, img, furigana_filter_sensitivity=0, return_coords=False):
|
891
912
|
lang = get_ocr_language()
|
892
913
|
if lang != self.initial_lang:
|
893
914
|
self.initial_lang = lang
|
@@ -911,6 +932,10 @@ class OneOCR:
|
|
911
932
|
json.dump(ocr_resp, f, indent=4, ensure_ascii=False)
|
912
933
|
# print(json.dumps(ocr_resp))
|
913
934
|
filtered_lines = [line for line in ocr_resp['lines'] if self.regex.search(line['text'])]
|
935
|
+
x_coords = [line['bounding_rect'][f'x{i}'] for line in filtered_lines for i in range(1, 5)]
|
936
|
+
y_coords = [line['bounding_rect'][f'y{i}'] for line in filtered_lines for i in range(1, 5)]
|
937
|
+
if x_coords and y_coords:
|
938
|
+
crop_coords = (min(x_coords) - 5, min(y_coords) - 5, max(x_coords) + 5, max(y_coords) + 5)
|
914
939
|
# logger.info(filtered_lines)
|
915
940
|
res = ''
|
916
941
|
skipped = []
|
@@ -964,30 +989,6 @@ class OneOCR:
|
|
964
989
|
# else:
|
965
990
|
# continue
|
966
991
|
# res += '\n'
|
967
|
-
elif sentence_to_check:
|
968
|
-
lines_to_build_area = []
|
969
|
-
widths = []
|
970
|
-
heights = []
|
971
|
-
for line in ocr_resp['lines']:
|
972
|
-
print(line['text'])
|
973
|
-
if sentence_to_check in line['text'] or line['text'] in sentence_to_check or rapidfuzz.fuzz.partial_ratio(sentence_to_check, line['text']) > 50:
|
974
|
-
lines_to_build_area.append(line)
|
975
|
-
res += line['text']
|
976
|
-
for word in line['words']:
|
977
|
-
widths.append(word['bounding_rect']['x2'] - word['bounding_rect']['x1'])
|
978
|
-
heights.append(word['bounding_rect']['y3'] - word['bounding_rect']['y1'])
|
979
|
-
|
980
|
-
x_coords = [line['bounding_rect'][f'x{i}'] for line in lines_to_build_area for i in
|
981
|
-
range(1, 5)]
|
982
|
-
y_coords = [line['bounding_rect'][f'y{i}'] for line in lines_to_build_area for i in
|
983
|
-
range(1, 5)]
|
984
|
-
if widths:
|
985
|
-
avg_width = sum(widths) / len(widths)
|
986
|
-
if heights:
|
987
|
-
avg_height = sum(heights) / len(heights)
|
988
|
-
if x_coords and y_coords:
|
989
|
-
crop_coords = (
|
990
|
-
min(x_coords) - 5, min(y_coords) - 5, max(x_coords) + 5, max(y_coords) + 5)
|
991
992
|
elif return_coords:
|
992
993
|
for line in filtered_lines:
|
993
994
|
for word in line['words']:
|
@@ -998,10 +999,6 @@ class OneOCR:
|
|
998
999
|
boxes.append(box)
|
999
1000
|
res = ocr_resp['text']
|
1000
1001
|
else:
|
1001
|
-
x_coords = [line['bounding_rect'][f'x{i}'] for line in filtered_lines for i in range(1, 5)]
|
1002
|
-
y_coords = [line['bounding_rect'][f'y{i}'] for line in filtered_lines for i in range(1, 5)]
|
1003
|
-
if x_coords and y_coords:
|
1004
|
-
crop_coords = (min(x_coords) - 5, min(y_coords) - 5, max(x_coords) + 5, max(y_coords) + 5)
|
1005
1002
|
res = ocr_resp['text']
|
1006
1003
|
|
1007
1004
|
except RuntimeError as e:
|
@@ -1019,7 +1016,7 @@ class OneOCR:
|
|
1019
1016
|
|
1020
1017
|
res = res.json()['text']
|
1021
1018
|
if return_coords:
|
1022
|
-
x = (True, res,
|
1019
|
+
x = (True, res, filtered_lines)
|
1023
1020
|
else:
|
1024
1021
|
x = (True, res, crop_coords)
|
1025
1022
|
if is_path:
|
@@ -44,7 +44,6 @@ import queue
|
|
44
44
|
from datetime import datetime
|
45
45
|
from PIL import Image, ImageDraw, UnidentifiedImageError
|
46
46
|
from loguru import logger
|
47
|
-
from pynput import keyboard
|
48
47
|
from desktop_notifier import DesktopNotifierSync
|
49
48
|
import psutil
|
50
49
|
|
@@ -384,6 +383,7 @@ class TextFiltering:
|
|
384
383
|
block_filtered = self.latin_extended_regex.findall(block)
|
385
384
|
else:
|
386
385
|
block_filtered = self.latin_extended_regex.findall(block)
|
386
|
+
|
387
387
|
if block_filtered:
|
388
388
|
orig_text_filtered.append(''.join(block_filtered))
|
389
389
|
else:
|
@@ -547,39 +547,6 @@ class ScreenshotThread(threading.Thread):
|
|
547
547
|
else:
|
548
548
|
raise ValueError('Window capture is only currently supported on Windows and macOS')
|
549
549
|
|
550
|
-
def __del__(self):
|
551
|
-
if self.macos_window_tracker_instance:
|
552
|
-
self.macos_window_tracker_instance.join()
|
553
|
-
elif self.windows_window_tracker_instance:
|
554
|
-
self.windows_window_tracker_instance.join()
|
555
|
-
|
556
|
-
def setup_persistent_windows_window_tracker(self):
|
557
|
-
global window_open
|
558
|
-
window_open = False
|
559
|
-
def setup_tracker():
|
560
|
-
global window_open
|
561
|
-
self.window_handle, window_title = self.get_windows_window_handle(self.screen_capture_window)
|
562
|
-
|
563
|
-
if not self.window_handle:
|
564
|
-
# print(f"Window '{screen_capture_window}' not found.")
|
565
|
-
return
|
566
|
-
|
567
|
-
set_dpi_awareness()
|
568
|
-
window_open = True
|
569
|
-
self.windows_window_tracker_instance = threading.Thread(target=self.windows_window_tracker)
|
570
|
-
self.windows_window_tracker_instance.start()
|
571
|
-
logger.opt(ansi=True).info(f'Selected window: {window_title}')
|
572
|
-
|
573
|
-
while not terminated:
|
574
|
-
if not window_open:
|
575
|
-
try:
|
576
|
-
setup_tracker()
|
577
|
-
except ValueError as e:
|
578
|
-
logger.error(f"Error setting up persistent windows window tracker: {e}")
|
579
|
-
break
|
580
|
-
time.sleep(5)
|
581
|
-
|
582
|
-
|
583
550
|
def get_windows_window_handle(self, window_title):
|
584
551
|
def callback(hwnd, window_title_part):
|
585
552
|
window_title = win32gui.GetWindowText(hwnd)
|
@@ -602,7 +569,7 @@ class ScreenshotThread(threading.Thread):
|
|
602
569
|
|
603
570
|
def windows_window_tracker(self):
|
604
571
|
found = True
|
605
|
-
while not terminated
|
572
|
+
while not terminated:
|
606
573
|
found = win32gui.IsWindow(self.window_handle)
|
607
574
|
if not found:
|
608
575
|
break
|
@@ -872,18 +839,9 @@ class OBSScreenshotThread(threading.Thread):
|
|
872
839
|
image_queue.put((result, True))
|
873
840
|
|
874
841
|
def connect_obs(self):
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
host=get_config().obs.host,
|
879
|
-
port=get_config().obs.port,
|
880
|
-
password=get_config().obs.password,
|
881
|
-
timeout=10
|
882
|
-
)
|
883
|
-
logger.info("Connected to OBS WebSocket.")
|
884
|
-
except Exception as e:
|
885
|
-
logger.error(f"Failed to connect to OBS: {e}")
|
886
|
-
self.obs_client = None
|
842
|
+
import GameSentenceMiner.obs as obs
|
843
|
+
obs.connect_to_obs_sync()
|
844
|
+
|
887
845
|
|
888
846
|
def run(self):
|
889
847
|
global last_image
|
@@ -895,7 +853,7 @@ class OBSScreenshotThread(threading.Thread):
|
|
895
853
|
def init_config(source=None, scene=None):
|
896
854
|
obs.update_current_game()
|
897
855
|
self.current_source = source if source else obs.get_active_source()
|
898
|
-
self.current_source_name = self.current_source.get(
|
856
|
+
self.current_source_name = self.current_source.get("sourceName") or None
|
899
857
|
self.current_scene = scene if scene else obs.get_current_game()
|
900
858
|
self.ocr_config = get_scene_ocr_config()
|
901
859
|
self.ocr_config.scale_to_custom_size(self.width, self.height)
|
@@ -927,20 +885,20 @@ class OBSScreenshotThread(threading.Thread):
|
|
927
885
|
if not self.ocr_config:
|
928
886
|
time.sleep(1)
|
929
887
|
continue
|
888
|
+
|
889
|
+
if not self.current_source_name:
|
890
|
+
obs.update_current_game()
|
891
|
+
self.current_source = obs.get_active_source()
|
892
|
+
self.current_source_name = self.current_source.get("sourceName") or None
|
930
893
|
|
931
894
|
try:
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
height=self.height,
|
938
|
-
)
|
895
|
+
if not self.current_source_name:
|
896
|
+
logger.error("No active source found in the current scene.")
|
897
|
+
time.sleep(1)
|
898
|
+
continue
|
899
|
+
img = obs.get_screenshot_PIL(source_name=self.current_source_name, width=self.width, height=self.height, img_format='jpg', compression=90)
|
939
900
|
|
940
|
-
if
|
941
|
-
image_data = base64.b64decode(response.image_data.split(",")[1])
|
942
|
-
img = Image.open(io.BytesIO(image_data)).convert("RGBA")
|
943
|
-
|
901
|
+
if img is not None:
|
944
902
|
if not img.getbbox():
|
945
903
|
logger.info("OBS Not Capturing anything, sleeping.")
|
946
904
|
time.sleep(1)
|
@@ -1118,11 +1076,10 @@ def signal_handler(sig, frame):
|
|
1118
1076
|
|
1119
1077
|
|
1120
1078
|
def on_window_closed(alive):
|
1121
|
-
global terminated
|
1079
|
+
global terminated
|
1122
1080
|
if not (alive or terminated):
|
1123
1081
|
logger.info('Window closed or error occurred, terminated!')
|
1124
|
-
|
1125
|
-
# terminated = True
|
1082
|
+
terminated = True
|
1126
1083
|
|
1127
1084
|
|
1128
1085
|
def on_screenshot_combo():
|
@@ -1464,8 +1421,12 @@ def run(read_from=None,
|
|
1464
1421
|
read_from_readable.append(f'directory {read_from_path}')
|
1465
1422
|
|
1466
1423
|
if len(key_combos) > 0:
|
1467
|
-
|
1468
|
-
|
1424
|
+
try:
|
1425
|
+
from pynput import keyboard
|
1426
|
+
key_combo_listener = keyboard.GlobalHotKeys(key_combos)
|
1427
|
+
key_combo_listener.start()
|
1428
|
+
except ImportError:
|
1429
|
+
pass
|
1469
1430
|
|
1470
1431
|
if write_to in ('clipboard', 'websocket', 'callback'):
|
1471
1432
|
write_to_readable = write_to
|
@@ -239,11 +239,12 @@ class Downloader:
|
|
239
239
|
# Example usage:
|
240
240
|
if __name__ == "__main__":
|
241
241
|
downloader = Downloader()
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
242
|
+
downloader.download_and_extract()
|
243
|
+
# if downloader.download_and_extract():
|
244
|
+
# print("SnippingTool files are ready.")
|
245
|
+
# print("Press Ctrl+C or X on window to exit.")
|
246
|
+
# # input()
|
247
|
+
# else:
|
248
|
+
# # print("Failed to download and extract SnippingTool files. You may need to follow instructions at https://github.com/AuroraWright/oneocr")
|
249
|
+
# print("Press Ctrl+C or X on window to exit.")
|
250
|
+
# input()
|
@@ -1,88 +1,285 @@
|
|
1
1
|
import asyncio
|
2
2
|
import io
|
3
3
|
import base64
|
4
|
+
import math
|
4
5
|
from PIL import Image
|
5
6
|
from GameSentenceMiner.util.configuration import get_config
|
7
|
+
from typing import Dict, Any, List, Tuple
|
8
|
+
|
9
|
+
from GameSentenceMiner.util.electron_config import get_ocr_language
|
6
10
|
|
7
11
|
if get_config().wip.overlay_websocket_send:
|
8
|
-
from GameSentenceMiner.owocr.owocr.ocr import GoogleLens, OneOCR
|
12
|
+
from GameSentenceMiner.owocr.owocr.ocr import GoogleLens, OneOCR, get_regex
|
9
13
|
from GameSentenceMiner.obs import *
|
10
14
|
|
11
|
-
# OBS WebSocket settings
|
12
|
-
OBS_HOST = 'localhost'
|
13
|
-
OBS_PORT = 7274
|
14
|
-
OBS_PASSWORD = 'your_obs_websocket_password' # Set your OBS WebSocket password here, if any
|
15
|
-
|
16
|
-
WINDOW_NAME = "Nier:Automata"
|
17
|
-
WIDTH = 2560
|
18
|
-
HEIGHT = 1440
|
19
15
|
if get_config().wip.overlay_websocket_send:
|
20
16
|
oneocr = OneOCR()
|
21
17
|
lens = GoogleLens()
|
22
18
|
|
23
|
-
|
19
|
+
|
20
|
+
def _convert_box_to_pixels_v2(
|
21
|
+
bbox_data: Dict[str, float],
|
22
|
+
original_width: int,
|
23
|
+
original_height: int,
|
24
|
+
crop_x: int,
|
25
|
+
crop_y: int,
|
26
|
+
crop_width: int,
|
27
|
+
crop_height: int
|
28
|
+
) -> Dict[str, float]:
|
24
29
|
"""
|
25
|
-
|
26
|
-
|
30
|
+
Simplified conversion: scales normalized bbox to pixel coordinates, ignores rotation.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
bbox_data: A dictionary with normalized 'center_x', 'center_y', 'width', 'height'.
|
34
|
+
original_width: The width of the original, full-size image in pixels.
|
35
|
+
original_height: The height of the original, full-size image in pixels.
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
A dictionary of the four corner points with absolute pixel coordinates.
|
27
39
|
"""
|
28
|
-
|
29
|
-
|
40
|
+
cx, cy = bbox_data['center_x'], bbox_data['center_y']
|
41
|
+
w, h = bbox_data['width'], bbox_data['height']
|
42
|
+
|
43
|
+
# Scale normalized coordinates to pixel coordinates
|
44
|
+
box_width_px = w * crop_width
|
45
|
+
box_height_px = h * crop_height
|
46
|
+
center_x_px = cx * crop_width + crop_x
|
47
|
+
center_y_px = cy * crop_height + crop_y
|
48
|
+
|
49
|
+
# Calculate corners (no rotation)
|
50
|
+
x1 = center_x_px - box_width_px / 2
|
51
|
+
y1 = center_y_px - box_height_px / 2
|
52
|
+
x2 = center_x_px + box_width_px / 2
|
53
|
+
y2 = center_y_px - box_height_px / 2
|
54
|
+
x3 = center_x_px + box_width_px / 2
|
55
|
+
y3 = center_y_px + box_height_px / 2
|
56
|
+
x4 = center_x_px - box_width_px / 2
|
57
|
+
y4 = center_y_px + box_height_px / 2
|
58
|
+
|
59
|
+
return {
|
60
|
+
"x1": x1,
|
61
|
+
"y1": y1,
|
62
|
+
"x2": x2,
|
63
|
+
"y2": y2,
|
64
|
+
"x3": x3,
|
65
|
+
"y3": y3,
|
66
|
+
"x4": x4,
|
67
|
+
"y4": y4,
|
68
|
+
}
|
30
69
|
|
31
|
-
|
32
|
-
|
70
|
+
def _convert_box_to_pixels(
|
71
|
+
bbox_data: Dict[str, float],
|
72
|
+
original_width: int,
|
73
|
+
original_height: int,
|
74
|
+
crop_x: int,
|
75
|
+
crop_y: int,
|
76
|
+
crop_width: int,
|
77
|
+
crop_height: int
|
78
|
+
) -> Dict[str, Dict[str, float]]:
|
79
|
+
"""
|
80
|
+
Converts a normalized bounding box to an absolute pixel-based quad.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
bbox_data: A dictionary with normalized 'center_x', 'center_y', etc.
|
84
|
+
original_width: The width of the original, full-size image in pixels.
|
85
|
+
original_height: The height of the original, full-size image in pixels.
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
A dictionary of the four corner points with absolute pixel coordinates.
|
89
|
+
"""
|
90
|
+
# Normalized coordinates from the input
|
91
|
+
cx, cy = bbox_data['center_x'], bbox_data['center_y']
|
92
|
+
w, h = bbox_data['width'], bbox_data['height']
|
93
|
+
angle_rad = bbox_data.get('rotation_z', 0.0)
|
94
|
+
|
95
|
+
# Calculate un-rotated corner points (still normalized) relative to the center
|
96
|
+
half_w, half_h = w / 2, h / 2
|
97
|
+
corners = [
|
98
|
+
(-half_w, -half_h), # Top-left
|
99
|
+
( half_w, -half_h), # Top-right
|
100
|
+
( half_w, half_h), # Bottom-right
|
101
|
+
(-half_w, half_h), # Bottom-left
|
102
|
+
]
|
103
|
+
|
104
|
+
# Rotate each corner and translate it to its absolute normalized position
|
105
|
+
cos_a, sin_a = math.cos(angle_rad), math.sin(angle_rad)
|
106
|
+
pixel_corners = []
|
107
|
+
for x_norm, y_norm in corners:
|
108
|
+
# 2D rotation
|
109
|
+
x_rot_norm = x_norm * cos_a - y_norm * sin_a
|
110
|
+
y_rot_norm = x_norm * sin_a + y_norm * cos_a
|
111
|
+
|
112
|
+
# Translate to absolute normalized position
|
113
|
+
abs_x_norm = cx + x_rot_norm
|
114
|
+
abs_y_norm = cy + y_rot_norm
|
115
|
+
|
116
|
+
# Scale up to pixel coordinates
|
117
|
+
pixel_corners.append((
|
118
|
+
abs_x_norm * crop_width + crop_x,
|
119
|
+
abs_y_norm * crop_height + crop_y
|
120
|
+
))
|
121
|
+
|
122
|
+
# Return as x1, y1, x2, y2, x3, y3, x4, y4
|
123
|
+
return {
|
124
|
+
"x1": pixel_corners[0][0],
|
125
|
+
"y1": pixel_corners[0][1],
|
126
|
+
"x2": pixel_corners[1][0],
|
127
|
+
"y2": pixel_corners[1][1],
|
128
|
+
"x3": pixel_corners[2][0],
|
129
|
+
"y3": pixel_corners[2][1],
|
130
|
+
"x4": pixel_corners[3][0],
|
131
|
+
"y4": pixel_corners[3][1],
|
132
|
+
}
|
133
|
+
|
134
|
+
def extract_text_with_pixel_boxes(
|
135
|
+
api_response: Dict[str, Any],
|
136
|
+
original_width: int,
|
137
|
+
original_height: int,
|
138
|
+
crop_x: int,
|
139
|
+
crop_y: int,
|
140
|
+
crop_width: int,
|
141
|
+
crop_height: int
|
142
|
+
) -> List[Dict[str, Any]]:
|
143
|
+
"""
|
144
|
+
Extracts sentences and words and converts their normalized bounding boxes
|
145
|
+
to absolute pixel coordinates based on original image dimensions.
|
146
|
+
|
147
|
+
Args:
|
148
|
+
api_response: The dictionary parsed from the source JSON.
|
149
|
+
original_width: The width of the original, full-size image.
|
150
|
+
original_height: The height of the original, full-size image.
|
151
|
+
|
152
|
+
Returns:
|
153
|
+
A list of sentence objects with text and bounding boxes in pixel coordinates.
|
154
|
+
"""
|
155
|
+
results = []
|
156
|
+
regex = get_regex(get_ocr_language())
|
33
157
|
|
34
|
-
|
35
|
-
|
158
|
+
try:
|
159
|
+
paragraphs = api_response["objects_response"]["text"]["text_layout"]["paragraphs"]
|
160
|
+
except KeyError:
|
161
|
+
return [] # Return empty list if the structure is not found
|
162
|
+
|
163
|
+
for para in paragraphs:
|
164
|
+
for line in para.get("lines", []):
|
165
|
+
line_text_parts = []
|
166
|
+
word_list = []
|
167
|
+
|
168
|
+
|
169
|
+
for word in line.get("words", []):
|
170
|
+
if not regex.search(word.get("plain_text", "")):
|
171
|
+
continue
|
172
|
+
word_text = word.get("plain_text", "")
|
173
|
+
line_text_parts.append(word_text)
|
174
|
+
|
175
|
+
# Convert word's bounding box to pixel coordinates
|
176
|
+
word_box = _convert_box_to_pixels_v2(
|
177
|
+
word["geometry"]["bounding_box"],
|
178
|
+
original_width,
|
179
|
+
original_height,
|
180
|
+
crop_x=crop_x,
|
181
|
+
crop_y=crop_y,
|
182
|
+
crop_width=crop_width,
|
183
|
+
crop_height=crop_height
|
184
|
+
)
|
185
|
+
|
186
|
+
word_list.append({
|
187
|
+
"text": word_text,
|
188
|
+
"bounding_rect": word_box
|
189
|
+
})
|
190
|
+
|
191
|
+
if not line_text_parts:
|
192
|
+
continue
|
193
|
+
|
194
|
+
# Assemble the sentence object
|
195
|
+
full_sentence_text = "".join(line_text_parts)
|
196
|
+
# Convert the full line's bounding box to pixel coordinates
|
197
|
+
line_box = _convert_box_to_pixels_v2(
|
198
|
+
line["geometry"]["bounding_box"],
|
199
|
+
original_width,
|
200
|
+
original_height,
|
201
|
+
crop_x=crop_x,
|
202
|
+
crop_y=crop_y,
|
203
|
+
crop_width=crop_width,
|
204
|
+
crop_height=crop_height
|
205
|
+
)
|
206
|
+
|
207
|
+
results.append({
|
208
|
+
"text": full_sentence_text,
|
209
|
+
"bounding_rect": line_box,
|
210
|
+
"words": word_list
|
211
|
+
})
|
212
|
+
|
213
|
+
return results
|
214
|
+
|
215
|
+
# def correct_ocr_text(detected_text: str, reference_text: str) -> str:
|
216
|
+
# """
|
217
|
+
# Correct OCR text by comparing character-by-character with reference text.
|
218
|
+
# When mismatches are found, look for subsequent matches and correct previous mismatches.
|
219
|
+
# """
|
220
|
+
# if not detected_text or not reference_text:
|
221
|
+
# return detected_text
|
36
222
|
|
37
|
-
|
223
|
+
# detected_chars = list(detected_text)
|
224
|
+
# reference_chars = list(reference_text)
|
38
225
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
old_char = detected_chars[last_mismatch_pos]
|
49
|
-
detected_chars[last_mismatch_pos] = reference_chars[last_mismatch_pos]
|
50
|
-
logger.info(f"Corrected position {last_mismatch_pos}: '{old_char}' -> '{reference_chars[last_mismatch_pos]}'")
|
226
|
+
# # Track positions where mismatches occurred
|
227
|
+
# mismatched_positions = []
|
228
|
+
|
229
|
+
# min_length = min(len(detected_chars), len(reference_chars))
|
230
|
+
|
231
|
+
# start_of_reference = 0
|
232
|
+
# for char in detected_chars:
|
233
|
+
# if char == reference_chars[start_of_reference]:
|
234
|
+
# start_of_reference += 1
|
51
235
|
|
52
|
-
|
53
|
-
|
236
|
+
# for i in range(min_length):
|
237
|
+
# if detected_chars[i] != reference_chars[i]:
|
238
|
+
# mismatched_positions.append(i)
|
239
|
+
# logger.info(f"Mismatch at position {i}: detected '{detected_chars[i]}' vs reference '{reference_chars[i]}'")
|
240
|
+
# else:
|
241
|
+
# # We found a match - if we have previous mismatches, correct the most recent one
|
242
|
+
# if mismatched_positions:
|
243
|
+
# # Correct the most recent mismatch (simple 1-for-1 strategy)
|
244
|
+
# last_mismatch_pos = mismatched_positions.pop()
|
245
|
+
# old_char = detected_chars[last_mismatch_pos]
|
246
|
+
# detected_chars[last_mismatch_pos] = reference_chars[last_mismatch_pos]
|
247
|
+
# logger.info(f"Corrected position {last_mismatch_pos}: '{old_char}' -> '{reference_chars[last_mismatch_pos]}'")
|
248
|
+
|
249
|
+
# corrected_text = ''.join(detected_chars)
|
250
|
+
# return corrected_text
|
54
251
|
|
55
|
-
def redistribute_corrected_text(original_boxes: list, original_text: str, corrected_text: str) -> list:
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
252
|
+
# def redistribute_corrected_text(original_boxes: list, original_text: str, corrected_text: str) -> list:
|
253
|
+
# """
|
254
|
+
# Redistribute corrected text back to the original text boxes while maintaining their positions.
|
255
|
+
# """
|
256
|
+
# if original_text == corrected_text:
|
257
|
+
# return original_boxes
|
61
258
|
|
62
|
-
|
63
|
-
|
259
|
+
# corrected_boxes = []
|
260
|
+
# text_position = 0
|
64
261
|
|
65
|
-
|
66
|
-
|
67
|
-
|
262
|
+
# for box in original_boxes:
|
263
|
+
# original_word = box['text']
|
264
|
+
# word_length = len(original_word)
|
68
265
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
266
|
+
# # Extract the corrected portion for this box
|
267
|
+
# if text_position + word_length <= len(corrected_text):
|
268
|
+
# corrected_word = corrected_text[text_position:text_position + word_length]
|
269
|
+
# else:
|
270
|
+
# # Handle case where corrected text is shorter
|
271
|
+
# corrected_word = corrected_text[text_position:] if text_position < len(corrected_text) else ""
|
75
272
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
273
|
+
# # Create a new box with corrected text but same coordinates
|
274
|
+
# corrected_box = box.copy()
|
275
|
+
# corrected_box['text'] = corrected_word
|
276
|
+
# corrected_boxes.append(corrected_box)
|
80
277
|
|
81
|
-
|
278
|
+
# text_position += word_length
|
82
279
|
|
83
|
-
|
280
|
+
# logger.info(f"Redistributed: '{original_word}' -> '{corrected_word}'")
|
84
281
|
|
85
|
-
|
282
|
+
# return corrected_boxes
|
86
283
|
|
87
284
|
async def get_full_screenshot() -> Image.Image | None:
|
88
285
|
# logger.info(f"Attempting to connect to OBS WebSocket at ws://{OBS_HOST}:{OBS_PORT}")
|
@@ -108,11 +305,46 @@ async def get_full_screenshot() -> Image.Image | None:
|
|
108
305
|
|
109
306
|
logger.info("Getting Screenshot from OBS")
|
110
307
|
try:
|
111
|
-
|
308
|
+
import mss as mss
|
112
309
|
start_time = time.time()
|
113
|
-
|
114
|
-
|
115
|
-
|
310
|
+
with mss.mss() as sct:
|
311
|
+
monitors = sct.monitors
|
312
|
+
if len(monitors) > 1:
|
313
|
+
monitors = monitors[1:]
|
314
|
+
else:
|
315
|
+
monitors = [monitors[0]]
|
316
|
+
monitor = monitors[get_config().wip.monitor_to_capture]
|
317
|
+
img = get_screenshot_PIL(compression=100, img_format='jpg')
|
318
|
+
# Put the image over a transparent background without stretching
|
319
|
+
new_img = Image.new("RGBA", (monitor['width'], monitor['height']), (0, 0, 0, 0))
|
320
|
+
# Calculate coordinates to center img horizontally and vertically
|
321
|
+
left = 0
|
322
|
+
top = 0
|
323
|
+
if img.width < monitor['width'] and img.height < monitor['height']:
|
324
|
+
# scale image to fit monitor
|
325
|
+
img = img.resize((monitor['width'], monitor['height']), Image.Resampling.BILINEAR)
|
326
|
+
if img.width < monitor['width']:
|
327
|
+
left = (monitor['width'] - img.width) // 2
|
328
|
+
if img.height < monitor['height']:
|
329
|
+
top = (monitor['height'] - img.height) // 2
|
330
|
+
|
331
|
+
print(f"Image size: {img.size}, Monitor size: {monitor['width']}x{monitor['height']}")
|
332
|
+
new_img.paste(img, (left, top))
|
333
|
+
|
334
|
+
# new_img.show()
|
335
|
+
|
336
|
+
return new_img, monitor['width'], monitor['height']
|
337
|
+
# sct_img = sct.grab(monitor)
|
338
|
+
# img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
|
339
|
+
|
340
|
+
# # img.show()
|
341
|
+
# return img
|
342
|
+
# update_current_game()
|
343
|
+
|
344
|
+
# image_data = get_screenshot_base64(compression=75, width=1280, height=720)
|
345
|
+
# image_data = base64.b64decode(image_data)
|
346
|
+
img = get_screenshot_PIL(img_format='jpg')
|
347
|
+
# img = Image.open(io.BytesIO(image_data)).convert("RGBA").resize((WIDTH, HEIGHT), Image.Resampling.LANCZOS)
|
116
348
|
# img.show()
|
117
349
|
logger.info(f"Screenshot captured in {time.time() - start_time:.2f} seconds.")
|
118
350
|
|
@@ -127,56 +359,77 @@ async def do_work(sentence_to_check=None):
|
|
127
359
|
logger.info("in find_box")
|
128
360
|
# await asyncio.sleep(.5)
|
129
361
|
logger.info("after_initial_sleep")
|
130
|
-
full_screenshot_image = await get_full_screenshot()
|
362
|
+
full_screenshot_image, monitor_width, monitor_height = await get_full_screenshot()
|
363
|
+
|
364
|
+
oneocr_results = oneocr(full_screenshot_image)
|
365
|
+
crop_coords = oneocr_results[2]
|
366
|
+
logger.info("Cropping full screenshot with coordinates: %s", crop_coords)
|
367
|
+
cropped_image = full_screenshot_image.crop(crop_coords)
|
368
|
+
# Convert 1/4
|
131
369
|
if os.path.exists("C:\\Users\\Beangate\\GSM\\temp"):
|
132
|
-
|
370
|
+
cropped_image.save("C:\\Users\\Beangate\\GSM\\temp\\full_screenshot.png")
|
133
371
|
# full_screenshot_image.show()
|
134
|
-
if
|
372
|
+
if cropped_image:
|
135
373
|
logger.info("Full screenshot captured successfully. Now performing local OCR...")
|
136
|
-
ocr_results = oneocr(full_screenshot_image, return_coords=True)
|
374
|
+
# ocr_results = oneocr(full_screenshot_image, return_coords=True)
|
375
|
+
google_ocr_results = lens(cropped_image, return_coords=True)[2]
|
137
376
|
|
138
|
-
|
377
|
+
ret = extract_text_with_pixel_boxes(
|
378
|
+
api_response=google_ocr_results,
|
379
|
+
original_width=monitor_width,
|
380
|
+
original_height=monitor_height,
|
381
|
+
crop_x=crop_coords[0],
|
382
|
+
crop_y=crop_coords[1],
|
383
|
+
crop_width=crop_coords[2] - crop_coords[0],
|
384
|
+
crop_height=crop_coords[3] - crop_coords[1]
|
385
|
+
)
|
386
|
+
|
387
|
+
# boxes_of_text = google_ocr_results[2]
|
139
388
|
# logger.info(f"Boxes of text found: {boxes_of_text}")
|
140
389
|
|
141
390
|
words = []
|
142
391
|
|
392
|
+
# logger.info(json.dumps(ret, indent=4, ensure_ascii=False))
|
393
|
+
|
394
|
+
return ret, 48
|
395
|
+
|
143
396
|
# If we have a reference sentence, perform character-by-character correction
|
144
|
-
if sentence_to_check:
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
397
|
+
# if sentence_to_check:
|
398
|
+
# # Concatenate all OCR text to form the detected sentence
|
399
|
+
# detected_sentence = ''.join([box['text'] for box in boxes_of_text])
|
400
|
+
# logger.info(f"Original detected sentence: '{detected_sentence}'")
|
401
|
+
# logger.info(f"Reference sentence: '{sentence_to_check}'")
|
149
402
|
|
150
|
-
|
151
|
-
|
152
|
-
|
403
|
+
# # Perform character-by-character comparison and correction
|
404
|
+
# corrected_sentence = correct_ocr_text(detected_sentence, sentence_to_check)
|
405
|
+
# logger.info(f"Corrected sentence: '{corrected_sentence}'")
|
153
406
|
|
154
|
-
|
155
|
-
|
156
|
-
else:
|
157
|
-
|
407
|
+
# # Redistribute corrected text back to boxes while maintaining positions
|
408
|
+
# corrected_boxes = redistribute_corrected_text(boxes_of_text, detected_sentence, corrected_sentence)
|
409
|
+
# else:
|
410
|
+
# corrected_boxes = boxes_of_text
|
158
411
|
|
159
|
-
sentence_position = 0
|
160
|
-
for box in corrected_boxes:
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
412
|
+
# sentence_position = 0
|
413
|
+
# for box in corrected_boxes:
|
414
|
+
# word = box['text']
|
415
|
+
# # logger.info(f"Box: {box}")
|
416
|
+
# x1, y1 = box['bounding_rect']['x1'], box['bounding_rect']['y1']
|
417
|
+
# x2, y2 = box['bounding_rect']['x3'], box['bounding_rect']['y3']
|
418
|
+
# words.append({
|
419
|
+
# "x1": x1,
|
420
|
+
# "y1": y1,
|
421
|
+
# "x2": x2,
|
422
|
+
# "y2": y2,
|
423
|
+
# "word": box['text']
|
424
|
+
# })
|
172
425
|
|
173
|
-
# logger.info(f"Returning words: {words}")
|
426
|
+
# # logger.info(f"Returning words: {words}")
|
174
427
|
|
175
|
-
ret = [
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
]
|
428
|
+
# ret = [
|
429
|
+
# {
|
430
|
+
# "words": words,
|
431
|
+
# }
|
432
|
+
# ]
|
180
433
|
# cropped_sections = []
|
181
434
|
# for box in boxes_of_text:
|
182
435
|
# # Ensure crop coordinates are within image bounds
|
@@ -234,8 +487,30 @@ async def find_box_for_sentence(sentence_to_check):
|
|
234
487
|
return [], 48
|
235
488
|
|
236
489
|
async def main():
|
490
|
+
import mss as mss
|
237
491
|
connect_to_obs_sync(5)
|
238
|
-
|
492
|
+
start_time = time.time()
|
493
|
+
with mss.mss() as sct:
|
494
|
+
monitors = sct.monitors
|
495
|
+
if len(monitors) > 1:
|
496
|
+
monitors = monitors[1:]
|
497
|
+
else:
|
498
|
+
monitors = [monitors[0]]
|
499
|
+
monitor = monitors[get_config().wip.monitor_to_capture]
|
500
|
+
img = get_screenshot_PIL(img_format='jpg')
|
501
|
+
img.show()
|
502
|
+
# Put the image over a transparent background without stretching
|
503
|
+
# Create a transparent image with the same size as the monitor
|
504
|
+
new_img = Image.new("RGBA", (monitor['width'], monitor['height']), (0, 0, 0, 0))
|
505
|
+
# Calculate coordinates to center img horizontally and vertically
|
506
|
+
left = (monitor['width'] - img.width) // 2
|
507
|
+
top = (monitor['height'] - img.height) // 2
|
508
|
+
print(f"Image size: {img.size}, Monitor size: {monitor['width']}x{monitor['height']}")
|
509
|
+
print(f"Left: {left}, Top: {top}, Width: {monitor['width']}, Height: {monitor['height']}")
|
510
|
+
new_img.paste(img, (left, top))
|
511
|
+
new_img.show()
|
512
|
+
|
513
|
+
return new_img
|
239
514
|
|
240
515
|
if __name__ == '__main__':
|
241
516
|
try:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: GameSentenceMiner
|
3
|
-
Version: 2.12.
|
3
|
+
Version: 2.12.4
|
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
|
@@ -150,6 +150,8 @@ If you encounter issues, please ask for help in my [Discord](https://discord.gg/
|
|
150
150
|
|
151
151
|
* [Renji's Texthooker](https://github.com/Renji-XD/texthooker-ui)
|
152
152
|
|
153
|
+
* https://github.com/Saplling/transparent-texthooker-overlay
|
154
|
+
|
153
155
|
## Donations
|
154
156
|
|
155
157
|
If you've found this or any of my other projects helpful, please consider supporting my work through [GitHub Sponsors](https://github.com/sponsors/bpwhelan), [Ko-fi](https://ko-fi.com/beangate), or [Patreon](https://www.patreon.com/GameSentenceMiner).
|
@@ -1,9 +1,9 @@
|
|
1
1
|
GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
GameSentenceMiner/anki.py,sha256=FUwcWO0-arzfQjejQmDKP7pNNakhboo8InQ4s_jv6AY,19099
|
3
|
-
GameSentenceMiner/config_gui.py,sha256=
|
4
|
-
GameSentenceMiner/gametext.py,sha256=
|
3
|
+
GameSentenceMiner/config_gui.py,sha256=GBcPWWoki8dMigWqORcG9memBwKp-BNFbhXhjfFLV0c,104414
|
4
|
+
GameSentenceMiner/gametext.py,sha256=fIm28ZvRzKvnVHj86TmSYR2QQifo_Lk6cx4UptIltLs,7844
|
5
5
|
GameSentenceMiner/gsm.py,sha256=GGF0owRrrYJgdfXx-INwfuKbaoY-G5gLllE-sNrwYnI,25341
|
6
|
-
GameSentenceMiner/obs.py,sha256
|
6
|
+
GameSentenceMiner/obs.py,sha256=bMVWAPQ6QLf4celLiOsL9BUO8pTdMn9lpT9fQCNfm7Q,18718
|
7
7
|
GameSentenceMiner/vad.py,sha256=zo9JpuEOCXczPXM-dq8lbr-zM-MPpfJ8aajggR3mKk4,18710
|
8
8
|
GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
9
|
GameSentenceMiner/ai/ai_prompting.py,sha256=iHkEx2pQJ-tEyejOgYy4G0DcZc8qvBugVL6-CQpPSME,26089
|
@@ -25,12 +25,12 @@ GameSentenceMiner/owocr/owocr/__init__.py,sha256=87hfN5u_PbL_onLfMACbc0F5j4KyIK9
|
|
25
25
|
GameSentenceMiner/owocr/owocr/__main__.py,sha256=XQaqZY99EKoCpU-gWQjNbTs7Kg17HvBVE7JY8LqIE0o,157
|
26
26
|
GameSentenceMiner/owocr/owocr/config.py,sha256=qM7kISHdUhuygGXOxmgU6Ef2nwBShrZtdqu4InDCViE,8103
|
27
27
|
GameSentenceMiner/owocr/owocr/lens_betterproto.py,sha256=oNoISsPilVVRBBPVDtb4-roJtAhp8ZAuFTci3TGXtMc,39141
|
28
|
-
GameSentenceMiner/owocr/owocr/ocr.py,sha256=
|
29
|
-
GameSentenceMiner/owocr/owocr/run.py,sha256=
|
28
|
+
GameSentenceMiner/owocr/owocr/ocr.py,sha256=Zii5r15ZlHFJWSbmXpva6QJVGkU3j2wT5Q0izazLyCQ,63021
|
29
|
+
GameSentenceMiner/owocr/owocr/run.py,sha256=GJAAqifaERxDnxcqPBTsEnxn-rJsUBgDC1s2F26N6KM,65724
|
30
30
|
GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=Na6XStbQBtpQUSdbN3QhEswtKuU1JjReFk_K8t5ezQE,3395
|
31
31
|
GameSentenceMiner/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
32
32
|
GameSentenceMiner/util/audio_offset_selector.py,sha256=8Stk3BP-XVIuzRv9nl9Eqd2D-1yD3JrgU-CamBywJmY,8542
|
33
|
-
GameSentenceMiner/util/configuration.py,sha256=
|
33
|
+
GameSentenceMiner/util/configuration.py,sha256=QIjSN0NfrucaK0ddhGcXqIYemBzPNtpNA3PN2SSs_PM,35991
|
34
34
|
GameSentenceMiner/util/electron_config.py,sha256=8LZwl-T_uF5z_ig-IZcm9QI-VKaD7zaHX9u6MaLYuo4,8648
|
35
35
|
GameSentenceMiner/util/ffmpeg.py,sha256=t0tflxq170n8PZKkdw8fTZIUQfXD0p_qARa9JTdhBTc,21530
|
36
36
|
GameSentenceMiner/util/gsm_utils.py,sha256=iRyLVcodMptRhkCzLf3hyqc6_RCktXnwApi6mLju6oQ,11565
|
@@ -46,7 +46,7 @@ GameSentenceMiner/util/communication/websocket.py,sha256=TbphRGmxVrgEupS7tNdifsm
|
|
46
46
|
GameSentenceMiner/util/downloader/Untitled_json.py,sha256=RUUl2bbbCpUDUUS0fP0tdvf5FngZ7ILdA_J5TFYAXUQ,15272
|
47
47
|
GameSentenceMiner/util/downloader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
48
48
|
GameSentenceMiner/util/downloader/download_tools.py,sha256=zR-aEHiFVkyo-9oPoSx6nQ2K-_J8WBHLZyLoOhypsW4,8458
|
49
|
-
GameSentenceMiner/util/downloader/oneocr_dl.py,sha256=
|
49
|
+
GameSentenceMiner/util/downloader/oneocr_dl.py,sha256=l3s9Z-x1b57GX048o5h-MVv0UTZo4H-Q-zb-JREkMLI,10439
|
50
50
|
GameSentenceMiner/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
51
51
|
GameSentenceMiner/web/service.py,sha256=S7bYf2kSk08u-8R9Qpv7piM-pxfFjYZUvU825xupmuI,5279
|
52
52
|
GameSentenceMiner/web/texthooking_page.py,sha256=2ZS89CAI17xVkx64rGmHHbF96eKR8gPWiR_WAoDJ0Mw,17399
|
@@ -63,10 +63,10 @@ GameSentenceMiner/web/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
|
|
63
63
|
GameSentenceMiner/web/templates/index.html,sha256=Gv3CJvNnhAzIVV_QxhNq4OD-pXDt1vKCu9k6WdHSXuA,215343
|
64
64
|
GameSentenceMiner/web/templates/text_replacements.html,sha256=tV5c8mCaWSt_vKuUpbdbLAzXZ3ATZeDvQ9PnnAfqY0M,8598
|
65
65
|
GameSentenceMiner/web/templates/utility.html,sha256=3flZinKNqUJ7pvrZk6xu__v67z44rXnaK7UTZ303R-8,16946
|
66
|
-
GameSentenceMiner/wip/get_overlay_coords.py,sha256=
|
67
|
-
gamesentenceminer-2.12.
|
68
|
-
gamesentenceminer-2.12.
|
69
|
-
gamesentenceminer-2.12.
|
70
|
-
gamesentenceminer-2.12.
|
71
|
-
gamesentenceminer-2.12.
|
72
|
-
gamesentenceminer-2.12.
|
66
|
+
GameSentenceMiner/wip/get_overlay_coords.py,sha256=yivn8C26BBRK4cjE7yPv1XfvbyqWC0itLL9Vay8aY-c,19780
|
67
|
+
gamesentenceminer-2.12.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
68
|
+
gamesentenceminer-2.12.4.dist-info/METADATA,sha256=xidAx_PVQT2GCaZoAkfYeMAJHqAppcpiPhmZ5Lhz1X4,7061
|
69
|
+
gamesentenceminer-2.12.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
70
|
+
gamesentenceminer-2.12.4.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
71
|
+
gamesentenceminer-2.12.4.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
72
|
+
gamesentenceminer-2.12.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|