GameSentenceMiner 2.14.21__py3-none-any.whl → 2.15.0__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 +3 -0
- GameSentenceMiner/obs.py +14 -0
- GameSentenceMiner/ocr/gsm_ocr_config.py +0 -1
- GameSentenceMiner/ocr/owocr_helper.py +0 -1
- GameSentenceMiner/owocr/owocr/ocr.py +8 -7
- GameSentenceMiner/owocr/owocr/run.py +101 -21
- GameSentenceMiner/util/get_overlay_coords.py +81 -48
- {gamesentenceminer-2.14.21.dist-info → gamesentenceminer-2.15.0.dist-info}/METADATA +2 -2
- {gamesentenceminer-2.14.21.dist-info → gamesentenceminer-2.15.0.dist-info}/RECORD +13 -13
- {gamesentenceminer-2.14.21.dist-info → gamesentenceminer-2.15.0.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.14.21.dist-info → gamesentenceminer-2.15.0.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.14.21.dist-info → gamesentenceminer-2.15.0.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.14.21.dist-info → gamesentenceminer-2.15.0.dist-info}/top_level.txt +0 -0
GameSentenceMiner/config_gui.py
CHANGED
@@ -2192,6 +2192,9 @@ class ConfigApp:
|
|
2192
2192
|
row=self.current_row, column=0)
|
2193
2193
|
self.overlay_monitor = ttk.Combobox(overlay_frame, values=self.monitors, state="readonly")
|
2194
2194
|
self.overlay_monitor.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
2195
|
+
# disable selection for now, default to value 1
|
2196
|
+
self.overlay_monitor.current(1)
|
2197
|
+
self.overlay_monitor.config(state="disabled")
|
2195
2198
|
self.current_row += 1
|
2196
2199
|
|
2197
2200
|
if self.monitors:
|
GameSentenceMiner/obs.py
CHANGED
@@ -483,6 +483,9 @@ def set_fit_to_screen_for_scene_items(scene_name: str):
|
|
483
483
|
try:
|
484
484
|
# 1. Get the canvas (base) resolution from OBS video settings
|
485
485
|
video_settings = client.get_video_settings()
|
486
|
+
if not hasattr(video_settings, 'base_width') or not hasattr(video_settings, 'base_height'):
|
487
|
+
logger.debug("Video settings do not have base_width or base_height attributes, probably weird websocket error issue? Idk what causes it..")
|
488
|
+
return
|
486
489
|
canvas_width = video_settings.base_width
|
487
490
|
canvas_height = video_settings.base_height
|
488
491
|
|
@@ -499,12 +502,23 @@ def set_fit_to_screen_for_scene_items(scene_name: str):
|
|
499
502
|
item_id = item['sceneItemId']
|
500
503
|
source_name = item['sourceName']
|
501
504
|
|
505
|
+
scene_item_transform = item.get('sceneItemTransform', {})
|
506
|
+
|
507
|
+
source_width = scene_item_transform.get('sourceWidth', None)
|
508
|
+
source_height = scene_item_transform.get('sourceHeight', None)
|
509
|
+
|
502
510
|
# This transform object is the equivalent of "Fit to Screen"
|
503
511
|
fit_to_screen_transform = {
|
504
512
|
'boundsType': 'OBS_BOUNDS_SCALE_INNER',
|
505
513
|
'alignment': 5, # 5 = Center alignment (horizontal and vertical)
|
506
514
|
'boundsWidth': canvas_width,
|
507
515
|
'boundsHeight': canvas_height,
|
516
|
+
'positionX': 0,
|
517
|
+
'positionY': 0,
|
518
|
+
'cropLeft': 0 if canvas_width >= source_width else (source_width - canvas_width) // 2,
|
519
|
+
'cropRight': 0 if canvas_width >= source_width else (source_width - canvas_width) // 2,
|
520
|
+
'cropTop': 0 if canvas_height >= source_height else (source_height - canvas_height) // 2,
|
521
|
+
'cropBottom': 0 if canvas_height >= source_height else (source_height - canvas_height) // 2,
|
508
522
|
}
|
509
523
|
|
510
524
|
try:
|
@@ -83,7 +83,6 @@ class OCRConfig:
|
|
83
83
|
]
|
84
84
|
|
85
85
|
def scale_to_custom_size(self, width, height):
|
86
|
-
print(self.pre_scale_rectangles)
|
87
86
|
self.rectangles = self.pre_scale_rectangles.copy()
|
88
87
|
if self.coordinate_system and self.coordinate_system == "percentage":
|
89
88
|
for rectangle in self.rectangles:
|
@@ -106,6 +106,7 @@ def empty_post_process(text):
|
|
106
106
|
|
107
107
|
def post_process(text, keep_blank_lines=False):
|
108
108
|
import jaconv
|
109
|
+
text = text.replace("\"", "")
|
109
110
|
if keep_blank_lines:
|
110
111
|
text = '\n'.join([''.join(i.split()) for i in text.splitlines()])
|
111
112
|
else:
|
@@ -354,8 +355,8 @@ class GoogleLens:
|
|
354
355
|
response_proto = LensOverlayServerResponse().FromString(res.content)
|
355
356
|
response_dict = response_proto.to_dict(betterproto.Casing.SNAKE)
|
356
357
|
|
357
|
-
if os.path.exists(r"C:\Users\Beangate\GSM\
|
358
|
-
with open(os.path.join(r"C:\Users\Beangate\GSM\
|
358
|
+
if os.path.exists(r"C:\Users\Beangate\GSM\test"):
|
359
|
+
with open(os.path.join(r"C:\Users\Beangate\GSM\test", 'glens_response.json'), 'w', encoding='utf-8') as f:
|
359
360
|
json.dump(response_dict, f, indent=4, ensure_ascii=False)
|
360
361
|
res = ''
|
361
362
|
text = response_dict['objects_response']['text']
|
@@ -1433,10 +1434,10 @@ class localLLMOCR:
|
|
1433
1434
|
self.keep_warm = config.get('keep_warm', True)
|
1434
1435
|
self.custom_prompt = config.get('prompt', None)
|
1435
1436
|
self.available = True
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1439
|
-
|
1437
|
+
if any(x in self.api_url for x in ['localhost', '127.0.0.1']):
|
1438
|
+
if not self.check_connection(self.api_url):
|
1439
|
+
logger.warning('Local LLM OCR API is not reachable')
|
1440
|
+
return
|
1440
1441
|
self.client = openai.OpenAI(
|
1441
1442
|
base_url=self.api_url.replace('/v1/chat/completions', '/v1'),
|
1442
1443
|
api_key=self.api_key
|
@@ -1493,7 +1494,7 @@ class localLLMOCR:
|
|
1493
1494
|
prompt = self.custom_prompt.strip()
|
1494
1495
|
else:
|
1495
1496
|
prompt = f"""
|
1496
|
-
Extract all {CommonLanguages.from_code(get_ocr_language()).name} Text from Image. Ignore all Furigana. Do not return any commentary, just the text in the image. If there is no text in the image, return "" (Empty String).
|
1497
|
+
Extract all {CommonLanguages.from_code(get_ocr_language()).name} Text from Image. Ignore all Furigana. Do not return any commentary, just the text in the image. Do not Translate. If there is no text in the image, return "" (Empty String).
|
1497
1498
|
"""
|
1498
1499
|
|
1499
1500
|
response = self.client.chat.completions.create(
|
@@ -1,5 +1,6 @@
|
|
1
|
-
from
|
2
|
-
from
|
1
|
+
from GameSentenceMiner.ocr.gsm_ocr_config import set_dpi_awareness, get_scene_ocr_config
|
2
|
+
from GameSentenceMiner.util.electron_config import * # noqa: F403
|
3
|
+
from GameSentenceMiner.util.gsm_utils import do_text_replacements, OCR_REPLACEMENTS_FILE
|
3
4
|
|
4
5
|
try:
|
5
6
|
import win32gui
|
@@ -28,7 +29,6 @@ import signal
|
|
28
29
|
import threading
|
29
30
|
from pathlib import Path
|
30
31
|
import queue
|
31
|
-
import io
|
32
32
|
import re
|
33
33
|
import logging
|
34
34
|
import inspect
|
@@ -39,23 +39,22 @@ import mss
|
|
39
39
|
import asyncio
|
40
40
|
import websockets
|
41
41
|
import socketserver
|
42
|
-
import
|
42
|
+
import cv2
|
43
|
+
import numpy as np
|
43
44
|
|
44
45
|
from datetime import datetime, timedelta
|
45
|
-
from PIL import Image, ImageDraw
|
46
|
+
from PIL import Image, ImageDraw
|
46
47
|
from loguru import logger
|
47
48
|
from desktop_notifier import DesktopNotifierSync
|
48
49
|
import psutil
|
49
50
|
|
50
|
-
import
|
51
|
-
from .ocr import *
|
52
|
-
try:
|
53
|
-
from .secret import *
|
54
|
-
except ImportError:
|
55
|
-
pass
|
51
|
+
from .ocr import * # noqa: F403
|
56
52
|
from .config import Config
|
57
53
|
from .screen_coordinate_picker import get_screen_selection
|
58
|
-
from GameSentenceMiner.util.configuration import get_temporary_directory
|
54
|
+
from GameSentenceMiner.util.configuration import get_temporary_directory
|
55
|
+
|
56
|
+
from skimage.metrics import structural_similarity as ssim
|
57
|
+
from typing import Union
|
59
58
|
|
60
59
|
config = None
|
61
60
|
last_image = None
|
@@ -799,8 +798,6 @@ class ScreenshotThread(threading.Thread):
|
|
799
798
|
self.windows_window_tracker_instance.join()
|
800
799
|
|
801
800
|
|
802
|
-
import cv2
|
803
|
-
import numpy as np
|
804
801
|
|
805
802
|
def apply_adaptive_threshold_filter(img):
|
806
803
|
img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
|
@@ -865,11 +862,6 @@ def are_images_identical(img1, img2, img2_np=None):
|
|
865
862
|
return (img1_np.shape == img2_np.shape) and np.array_equal(img1_np, img2_np)
|
866
863
|
|
867
864
|
|
868
|
-
import cv2
|
869
|
-
import numpy as np
|
870
|
-
from skimage.metrics import structural_similarity as ssim
|
871
|
-
from typing import Union
|
872
|
-
|
873
865
|
ImageType = Union[np.ndarray, Image.Image]
|
874
866
|
|
875
867
|
def _prepare_image(image: ImageType) -> np.ndarray:
|
@@ -1319,6 +1311,10 @@ def on_screenshot_combo():
|
|
1319
1311
|
def on_window_minimized(minimized):
|
1320
1312
|
global screencapture_window_visible
|
1321
1313
|
screencapture_window_visible = not minimized
|
1314
|
+
|
1315
|
+
|
1316
|
+
def do_configured_ocr_replacements(text: str) -> str:
|
1317
|
+
return do_text_replacements(text, OCR_REPLACEMENTS_FILE)
|
1322
1318
|
|
1323
1319
|
|
1324
1320
|
def process_and_write_results(img_or_path, write_to=None, last_result=None, filtering=None, notify=None, engine=None, ocr_start_time=None, furigana_filter_sensitivity=0):
|
@@ -1364,14 +1360,21 @@ def process_and_write_results(img_or_path, write_to=None, last_result=None, filt
|
|
1364
1360
|
# print(engine_index)
|
1365
1361
|
|
1366
1362
|
if res:
|
1363
|
+
text = do_configured_ocr_replacements(text)
|
1367
1364
|
if filtering:
|
1368
1365
|
text, orig_text = filtering(text, last_result)
|
1369
1366
|
if get_ocr_language() == "ja" or get_ocr_language() == "zh":
|
1370
1367
|
text = post_process(text, keep_blank_lines=get_ocr_keep_newline())
|
1371
|
-
logger.opt(ansi=True).info(
|
1372
|
-
f'Text recognized in {end_time - start_time:0.03f}s using <{engine_color}>{engine_instance.readable_name}</{engine_color}>: {text}')
|
1373
1368
|
if notify and config.get_general('notifications'):
|
1374
1369
|
notifier.send(title='owocr', message='Text recognized: ' + text)
|
1370
|
+
|
1371
|
+
if text and write_to is not None:
|
1372
|
+
if check_text_is_all_menu(text, crop_coords):
|
1373
|
+
logger.opt(ansi=True).info('Text is identified as all menu items, skipping further processing.')
|
1374
|
+
return orig_text, ''
|
1375
|
+
|
1376
|
+
logger.opt(ansi=True).info(
|
1377
|
+
f'Text recognized in {end_time - start_time:0.03f}s using <{engine_color}>{engine_instance.readable_name}</{engine_color}>: {text}')
|
1375
1378
|
|
1376
1379
|
if write_to == 'websocket':
|
1377
1380
|
websocket_server_thread.send_text(text)
|
@@ -1395,6 +1398,83 @@ def process_and_write_results(img_or_path, write_to=None, last_result=None, filt
|
|
1395
1398
|
|
1396
1399
|
return orig_text, text
|
1397
1400
|
|
1401
|
+
def check_text_is_all_menu(text: str, crop_coords: tuple) -> bool:
|
1402
|
+
"""
|
1403
|
+
Checks if the recognized text consists entirely of menu items.
|
1404
|
+
This function checks if the detected text area falls entirely within secondary rectangles (menu areas).
|
1405
|
+
|
1406
|
+
:param text: The recognized text from OCR.
|
1407
|
+
:param crop_coords: Tuple containing (x, y, width, height) of the detected text area relative to the cropped image.
|
1408
|
+
:return: True if the text is all menu items (within secondary rectangles), False otherwise.
|
1409
|
+
"""
|
1410
|
+
if not text or not crop_coords:
|
1411
|
+
return False
|
1412
|
+
|
1413
|
+
original_width = obs_screenshot_thread.width
|
1414
|
+
original_height = obs_screenshot_thread.height
|
1415
|
+
crop_x, crop_y, crop_w, crop_h = crop_coords
|
1416
|
+
|
1417
|
+
ocr_config = get_scene_ocr_config()
|
1418
|
+
|
1419
|
+
if not any(rect.is_secondary for rect in ocr_config.rectangles):
|
1420
|
+
return False
|
1421
|
+
|
1422
|
+
ocr_config.scale_to_custom_size(original_width, original_height)
|
1423
|
+
if not ocr_config or not ocr_config.rectangles:
|
1424
|
+
return False
|
1425
|
+
|
1426
|
+
primary_rectangles = [rect for rect in ocr_config.rectangles if not rect.is_excluded and not rect.is_secondary]
|
1427
|
+
menu_rectangles = [rect for rect in ocr_config.rectangles if rect.is_secondary and not rect.is_excluded]
|
1428
|
+
|
1429
|
+
if not menu_rectangles:
|
1430
|
+
return False
|
1431
|
+
|
1432
|
+
if not primary_rectangles:
|
1433
|
+
if crop_x < 0 or crop_y < 0 or crop_x + crop_w > original_width or crop_y + crop_h > original_height:
|
1434
|
+
return False
|
1435
|
+
for menu_rect in menu_rectangles:
|
1436
|
+
rect_left, rect_top, rect_width, rect_height = menu_rect.coordinates
|
1437
|
+
rect_right = rect_left + rect_width
|
1438
|
+
rect_bottom = rect_top + rect_height
|
1439
|
+
if (crop_x >= rect_left and crop_y >= rect_top and
|
1440
|
+
crop_x + crop_w <= rect_right and crop_y + crop_h <= rect_bottom):
|
1441
|
+
return True
|
1442
|
+
return False
|
1443
|
+
|
1444
|
+
primary_rectangles.sort(key=lambda r: r.coordinates[1])
|
1445
|
+
|
1446
|
+
if len(primary_rectangles) == 1:
|
1447
|
+
primary_rect = primary_rectangles[0]
|
1448
|
+
primary_left, primary_top = primary_rect.coordinates[0], primary_rect.coordinates[1]
|
1449
|
+
original_x = crop_x + primary_left
|
1450
|
+
original_y = crop_y + primary_top
|
1451
|
+
else:
|
1452
|
+
current_y_offset = 0
|
1453
|
+
original_x = None
|
1454
|
+
original_y = None
|
1455
|
+
for i, primary_rect in enumerate(primary_rectangles):
|
1456
|
+
primary_left, primary_top, primary_width, primary_height = primary_rect.coordinates
|
1457
|
+
section_height = primary_height
|
1458
|
+
if crop_y >= current_y_offset and crop_y < current_y_offset + section_height:
|
1459
|
+
original_x = crop_x + primary_left
|
1460
|
+
original_y = (crop_y - current_y_offset) + primary_top
|
1461
|
+
break
|
1462
|
+
current_y_offset += section_height + 50
|
1463
|
+
if original_x is None or original_y is None:
|
1464
|
+
return False
|
1465
|
+
|
1466
|
+
if original_x < 0 or original_y < 0 or original_x > original_width or original_y > original_height:
|
1467
|
+
return False
|
1468
|
+
|
1469
|
+
for menu_rect in menu_rectangles:
|
1470
|
+
rect_left, rect_top, rect_width, rect_height = menu_rect.coordinates
|
1471
|
+
rect_right = rect_left + rect_width
|
1472
|
+
rect_bottom = rect_top + rect_height
|
1473
|
+
if (original_x >= rect_left and original_y >= rect_top and
|
1474
|
+
original_x <= rect_right and original_y <= rect_bottom):
|
1475
|
+
return True
|
1476
|
+
|
1477
|
+
return False
|
1398
1478
|
|
1399
1479
|
def get_path_key(path):
|
1400
1480
|
return path, path.lstat().st_mtime
|
@@ -94,26 +94,59 @@ class OverlayProcessor:
|
|
94
94
|
except Exception as e:
|
95
95
|
logger.error(f"Error during OCR processing: {e}", exc_info=True)
|
96
96
|
return []
|
97
|
+
|
98
|
+
@staticmethod
|
99
|
+
def get_monitor_workarea(monitor_index=0):
|
100
|
+
"""
|
101
|
+
Return MSS-style dict for monitor area.
|
102
|
+
For primary monitor, excludes taskbar. For others, returns full monitor area.
|
103
|
+
monitor_index: 0 = primary monitor, 1+ = others (as in mss.monitors).
|
104
|
+
"""
|
105
|
+
with mss.mss() as sct:
|
106
|
+
monitors = sct.monitors[1:]
|
107
|
+
print(monitors)
|
108
|
+
if is_windows() and monitor_index == 0:
|
109
|
+
from ctypes import wintypes
|
110
|
+
import ctypes
|
111
|
+
# Get work area for primary monitor (ignores taskbar)
|
112
|
+
SPI_GETWORKAREA = 0x0030
|
113
|
+
rect = wintypes.RECT()
|
114
|
+
res = ctypes.windll.user32.SystemParametersInfoW(
|
115
|
+
SPI_GETWORKAREA, 0, ctypes.byref(rect), 0
|
116
|
+
)
|
117
|
+
if not res:
|
118
|
+
raise ctypes.WinError()
|
119
|
+
|
120
|
+
return {
|
121
|
+
"left": rect.left,
|
122
|
+
"top": rect.top,
|
123
|
+
"width": rect.right - rect.left,
|
124
|
+
"height": rect.bottom - rect.top,
|
125
|
+
}
|
126
|
+
elif is_windows() and monitor_index > 0:
|
127
|
+
# Secondary monitors: just return with a guess of how tall the taskbar is
|
128
|
+
taskbar_height_guess = 48 # A common taskbar height, may vary
|
129
|
+
mon = monitors[monitor_index]
|
130
|
+
return {
|
131
|
+
"left": mon["left"],
|
132
|
+
"top": mon["top"],
|
133
|
+
"width": mon["width"],
|
134
|
+
"height": mon["height"] - taskbar_height_guess
|
135
|
+
}
|
136
|
+
else:
|
137
|
+
# For non-Windows systems or unspecified monitors, return the monitor area as-is
|
138
|
+
return monitors[monitor_index] if 0 <= monitor_index < len(monitors) else monitors[0]
|
139
|
+
|
97
140
|
|
98
141
|
def _get_full_screenshot(self) -> Tuple[Image.Image | None, int, int]:
|
99
142
|
"""Captures a screenshot of the configured monitor."""
|
100
143
|
if not mss:
|
101
144
|
raise RuntimeError("MSS screenshot library is not installed.")
|
102
|
-
|
103
145
|
with mss.mss() as sct:
|
104
|
-
|
105
|
-
# Index 0 is the 'all monitors' virtual screen, so we skip it.
|
106
|
-
monitor_list = monitors[1:] if len(monitors) > 1 else [monitors[0]]
|
107
|
-
|
108
|
-
monitor_index = self.config.overlay.monitor_to_capture
|
109
|
-
if monitor_index >= len(monitor_list):
|
110
|
-
logger.error(f"Monitor index {monitor_index} is out of bounds. Found {len(monitor_list)} monitors.")
|
111
|
-
return None, 0, 0
|
112
|
-
|
113
|
-
monitor = monitor_list[monitor_index]
|
146
|
+
monitor = self.get_monitor_workarea(0) # Get primary monitor work area
|
114
147
|
sct_img = sct.grab(monitor)
|
115
148
|
img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
|
116
|
-
|
149
|
+
|
117
150
|
return img, monitor['width'], monitor['height']
|
118
151
|
|
119
152
|
def _create_composite_image(
|
@@ -210,7 +243,8 @@ class OverlayProcessor:
|
|
210
243
|
crop_x=0,
|
211
244
|
crop_y=0,
|
212
245
|
crop_width=composite_image.width,
|
213
|
-
crop_height=composite_image.height
|
246
|
+
crop_height=composite_image.height,
|
247
|
+
use_percentages=True
|
214
248
|
)
|
215
249
|
|
216
250
|
return extracted_data
|
@@ -223,7 +257,8 @@ class OverlayProcessor:
|
|
223
257
|
crop_x: int,
|
224
258
|
crop_y: int,
|
225
259
|
crop_width: int,
|
226
|
-
crop_height: int
|
260
|
+
crop_height: int,
|
261
|
+
use_percentages: bool
|
227
262
|
) -> List[Dict[str, Any]]:
|
228
263
|
"""
|
229
264
|
Parses Google Lens API response and converts normalized coordinates
|
@@ -244,9 +279,10 @@ class OverlayProcessor:
|
|
244
279
|
word_text = word.get("plain_text", "")
|
245
280
|
line_text_parts.append(word_text)
|
246
281
|
|
247
|
-
word_box = self.
|
282
|
+
word_box = self._convert_box_to_overlay_coords(
|
248
283
|
word["geometry"]["bounding_box"],
|
249
|
-
crop_x, crop_y, crop_width, crop_height
|
284
|
+
crop_x, crop_y, crop_width, crop_height,
|
285
|
+
use_percentage=use_percentages
|
250
286
|
)
|
251
287
|
|
252
288
|
word_list.append({
|
@@ -258,9 +294,9 @@ class OverlayProcessor:
|
|
258
294
|
continue
|
259
295
|
|
260
296
|
full_line_text = "".join(line_text_parts)
|
261
|
-
line_box = self.
|
297
|
+
line_box = self._convert_box_to_overlay_coords(
|
262
298
|
line["geometry"]["bounding_box"],
|
263
|
-
crop_x, crop_y, crop_width, crop_height
|
299
|
+
crop_x, crop_y, crop_width, crop_height, use_percentage=use_percentages
|
264
300
|
)
|
265
301
|
|
266
302
|
results.append({
|
@@ -270,36 +306,45 @@ class OverlayProcessor:
|
|
270
306
|
})
|
271
307
|
return results
|
272
308
|
|
273
|
-
def
|
309
|
+
def _convert_box_to_overlay_coords(
|
274
310
|
self,
|
275
311
|
bbox_data: Dict[str, float],
|
276
312
|
crop_x: int,
|
277
313
|
crop_y: int,
|
278
314
|
crop_width: int,
|
279
|
-
crop_height: int
|
315
|
+
crop_height: int,
|
316
|
+
use_percentage: bool
|
280
317
|
) -> Dict[str, float]:
|
281
318
|
"""
|
282
319
|
Simplified conversion: scales normalized bbox to pixel coordinates within
|
283
320
|
the cropped region, then offsets by the crop position. Ignores rotation.
|
321
|
+
If use_percentage is True, returns coordinates as percentages of the crop dimensions.
|
284
322
|
"""
|
285
323
|
cx, cy = bbox_data['center_x'], bbox_data['center_y']
|
286
324
|
w, h = bbox_data['width'], bbox_data['height']
|
287
325
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
326
|
+
if use_percentage:
|
327
|
+
# Return coordinates as percentages of the crop dimensions
|
328
|
+
box_width = w
|
329
|
+
box_height = h
|
330
|
+
center_x = cx
|
331
|
+
center_y = cy
|
332
|
+
else:
|
333
|
+
# Scale normalized coordinates to pixel coordinates relative to the crop area
|
334
|
+
box_width = w * crop_width
|
335
|
+
box_height = h * crop_height
|
336
|
+
|
337
|
+
# Calculate center within the cropped area and then add the crop offset
|
338
|
+
center_x = (cx * crop_width) + crop_x
|
339
|
+
center_y = (cy * crop_height) + crop_y
|
295
340
|
|
296
341
|
# Calculate corners (unrotated)
|
297
|
-
|
342
|
+
half_w, half_h = box_width / 2, box_height / 2
|
298
343
|
return {
|
299
|
-
"x1":
|
300
|
-
"x2":
|
301
|
-
"x3":
|
302
|
-
"x4":
|
344
|
+
"x1": center_x - half_w, "y1": center_y - half_h,
|
345
|
+
"x2": center_x + half_w, "y2": center_y - half_h,
|
346
|
+
"x3": center_x + half_w, "y3": center_y + half_h,
|
347
|
+
"x4": center_x - half_w, "y4": center_y + half_h,
|
303
348
|
}
|
304
349
|
|
305
350
|
async def main_test_screenshot():
|
@@ -334,22 +379,10 @@ async def main_run_ocr():
|
|
334
379
|
"""
|
335
380
|
Main function to demonstrate running the full OCR process.
|
336
381
|
"""
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
print("OCR process completed successfully.")
|
342
|
-
# print(json.dumps(results, indent=2, ensure_ascii=False))
|
343
|
-
# Find first result with some text
|
344
|
-
for result in results:
|
345
|
-
if result.get("text"):
|
346
|
-
print(f"Found line: '{result['text']}'")
|
347
|
-
print(f" - Line BBox: {result['bounding_rect']}")
|
348
|
-
if result.get("words"):
|
349
|
-
print(f" - First word: '{result['words'][0]['text']}' BBox: {result['words'][0]['bounding_rect']}")
|
350
|
-
break
|
351
|
-
else:
|
352
|
-
print("OCR process did not find any text.")
|
382
|
+
overlay_processor = OverlayProcessor()
|
383
|
+
while True:
|
384
|
+
await overlay_processor.find_box_and_send_to_overlay('')
|
385
|
+
await asyncio.sleep(10)
|
353
386
|
|
354
387
|
|
355
388
|
if __name__ == '__main__':
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: GameSentenceMiner
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.15.0
|
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
|
@@ -57,7 +57,7 @@ An application designed to assist with language learning through games.
|
|
57
57
|
|
58
58
|
Short Demo (Watch this first): https://www.youtube.com/watch?v=FeFBL7py6HY
|
59
59
|
|
60
|
-
Installation: https://
|
60
|
+
Installation: https://www.youtube.com/watch?v=sVL9omRbGc4
|
61
61
|
|
62
62
|
Discord: https://discord.gg/yP8Qse6bb8
|
63
63
|
|
@@ -1,9 +1,9 @@
|
|
1
1
|
GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
GameSentenceMiner/anki.py,sha256=4Tq6OGjfN-5tYorYRWiih7FZjSKMG6amrLv6DFKkFQc,25344
|
3
|
-
GameSentenceMiner/config_gui.py,sha256=
|
3
|
+
GameSentenceMiner/config_gui.py,sha256=Ynr7P0xkhqeOiO-7aiM9OzB3aTLs5JSlEsia5cHyyd4,139669
|
4
4
|
GameSentenceMiner/gametext.py,sha256=fgBgLchezpauWELE9Y5G3kVCLfAneD0X4lJFoI3FYbs,10351
|
5
5
|
GameSentenceMiner/gsm.py,sha256=4mJn5v4WKqKAJEtph5e0v4YPVDOpvFN1ylV2vQvf_Dg,31913
|
6
|
-
GameSentenceMiner/obs.py,sha256=
|
6
|
+
GameSentenceMiner/obs.py,sha256=U7ooVhf3k0vx-T8noSLVQirgCbS27fNnuZmpfM6alnc,24580
|
7
7
|
GameSentenceMiner/vad.py,sha256=YCn4ZIc6_Q3IGOr5QNMiheVT3Ma5nisn8-V8xD53Mw4,19236
|
8
8
|
GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
9
|
GameSentenceMiner/ai/ai_prompting.py,sha256=41xdBzE88Jlt12A0D-T_cMfLO5j6MSxfniOptpwNZm0,24068
|
@@ -19,17 +19,17 @@ GameSentenceMiner/locales/en_us.json,sha256=FAnvsCfsFzWyxYKZKh8HKHsAahi3Oa4wGVek
|
|
19
19
|
GameSentenceMiner/locales/ja_jp.json,sha256=-v0ng0psD88-C4XjYazJL0Rn0gwQU7b2VYspvdatDO4,28326
|
20
20
|
GameSentenceMiner/locales/zh_cn.json,sha256=X5nw6tsu7ACaZIuSUDSUUjG8qPUwmqyG3TKcPbWSIYw,24654
|
21
21
|
GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
|
-
GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=
|
22
|
+
GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=DfcR3bHTu26JJerLzqfW_KpdgUBSrRV4hqSy_LYclps,5967
|
23
23
|
GameSentenceMiner/ocr/ocrconfig.py,sha256=_tY8mjnzHMJrLS8E5pHqYXZjMuLoGKYgJwdhYgN-ny4,6466
|
24
24
|
GameSentenceMiner/ocr/owocr_area_selector.py,sha256=Rm1_nuZotJhfOfoJ_3mesh9udtOBjYqKhnAvSief6fo,29181
|
25
|
-
GameSentenceMiner/ocr/owocr_helper.py,sha256=
|
25
|
+
GameSentenceMiner/ocr/owocr_helper.py,sha256=8aIpAHSPPByox5qcU4SX-_ECDgQZVEwrXdj4A8AQZ6U,28437
|
26
26
|
GameSentenceMiner/ocr/ss_picker.py,sha256=0IhxUdaKruFpZyBL-8SpxWg7bPrlGpy3lhTcMMZ5rwo,5224
|
27
27
|
GameSentenceMiner/owocr/owocr/__init__.py,sha256=87hfN5u_PbL_onLfMACbc0F5j4KyIK9lKnRCj6oZgR0,49
|
28
28
|
GameSentenceMiner/owocr/owocr/__main__.py,sha256=XQaqZY99EKoCpU-gWQjNbTs7Kg17HvBVE7JY8LqIE0o,157
|
29
29
|
GameSentenceMiner/owocr/owocr/config.py,sha256=qM7kISHdUhuygGXOxmgU6Ef2nwBShrZtdqu4InDCViE,8103
|
30
30
|
GameSentenceMiner/owocr/owocr/lens_betterproto.py,sha256=oNoISsPilVVRBBPVDtb4-roJtAhp8ZAuFTci3TGXtMc,39141
|
31
|
-
GameSentenceMiner/owocr/owocr/ocr.py,sha256=
|
32
|
-
GameSentenceMiner/owocr/owocr/run.py,sha256=
|
31
|
+
GameSentenceMiner/owocr/owocr/ocr.py,sha256=rJKmB8xtSVrayq8XcZ4JKBULyPKDqfJDGKgLjICbUX8,71021
|
32
|
+
GameSentenceMiner/owocr/owocr/run.py,sha256=xbBpyFCVfITZDztsRLT8_sX6BGf1o5LxOPxE9zUWfQc,79975
|
33
33
|
GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=Na6XStbQBtpQUSdbN3QhEswtKuU1JjReFk_K8t5ezQE,3395
|
34
34
|
GameSentenceMiner/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
35
35
|
GameSentenceMiner/tools/audio_offset_selector.py,sha256=8Stk3BP-XVIuzRv9nl9Eqd2D-1yD3JrgU-CamBywJmY,8542
|
@@ -41,7 +41,7 @@ GameSentenceMiner/util/configuration.py,sha256=JVwaqvfrUrOUiA0kZcznDsCo9hJkJqBpV
|
|
41
41
|
GameSentenceMiner/util/db.py,sha256=2bO0rD4i8A1hhsRBER-wgZy9IK17ibRbI8DHxdKvYsI,16598
|
42
42
|
GameSentenceMiner/util/electron_config.py,sha256=KfeJToeFFVw0IR5MKa-gBzpzaGrU-lyJbR9z-sDEHYU,8767
|
43
43
|
GameSentenceMiner/util/ffmpeg.py,sha256=jA-cFtCmdCWrUSPpdtFSLr-GSoqs4qNUzW20v4HPHf0,28715
|
44
|
-
GameSentenceMiner/util/get_overlay_coords.py,sha256=
|
44
|
+
GameSentenceMiner/util/get_overlay_coords.py,sha256=bSvBSvLFmABogPp-quXcQrN-meWvl5NRB6gFEluoNNg,15142
|
45
45
|
GameSentenceMiner/util/gsm_utils.py,sha256=Piwv88Q9av2LBeN7M6QDi0Mp0_R2lNbkcI6ekK5hd2o,11851
|
46
46
|
GameSentenceMiner/util/model.py,sha256=R-_RYTYLSDNgBoVTPuPBcIHeOznIqi_vBzQ7VQ20WYk,6727
|
47
47
|
GameSentenceMiner/util/notification.py,sha256=YBhf_mSo_i3cjBz-pmeTPx3wchKiG9BK2VBdZSa2prQ,4597
|
@@ -72,9 +72,9 @@ GameSentenceMiner/web/templates/index.html,sha256=LqXZx7-NE42pXSpHNZ3To680rD-vt9
|
|
72
72
|
GameSentenceMiner/web/templates/text_replacements.html,sha256=tV5c8mCaWSt_vKuUpbdbLAzXZ3ATZeDvQ9PnnAfqY0M,8598
|
73
73
|
GameSentenceMiner/web/templates/utility.html,sha256=3flZinKNqUJ7pvrZk6xu__v67z44rXnaK7UTZ303R-8,16946
|
74
74
|
GameSentenceMiner/wip/__init___.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
75
|
-
gamesentenceminer-2.
|
76
|
-
gamesentenceminer-2.
|
77
|
-
gamesentenceminer-2.
|
78
|
-
gamesentenceminer-2.
|
79
|
-
gamesentenceminer-2.
|
80
|
-
gamesentenceminer-2.
|
75
|
+
gamesentenceminer-2.15.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
76
|
+
gamesentenceminer-2.15.0.dist-info/METADATA,sha256=Fd7VWnRI-2fkihajfxLaGnHXWOF2h7couT11IqrsJoc,7317
|
77
|
+
gamesentenceminer-2.15.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
78
|
+
gamesentenceminer-2.15.0.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
79
|
+
gamesentenceminer-2.15.0.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
80
|
+
gamesentenceminer-2.15.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|