GameSentenceMiner 2.14.8__py3-none-any.whl → 2.14.9__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 +19 -10
- GameSentenceMiner/gsm.py +68 -8
- GameSentenceMiner/locales/en_us.json +4 -0
- GameSentenceMiner/locales/ja_jp.json +4 -0
- GameSentenceMiner/locales/zh_cn.json +4 -0
- GameSentenceMiner/obs.py +12 -8
- {gamesentenceminer-2.14.8.dist-info → gamesentenceminer-2.14.9.dist-info}/METADATA +1 -2
- gamesentenceminer-2.14.9.dist-info/RECORD +24 -0
- GameSentenceMiner/ai/__init__.py +0 -0
- GameSentenceMiner/ai/ai_prompting.py +0 -473
- GameSentenceMiner/ocr/__init__.py +0 -0
- GameSentenceMiner/ocr/gsm_ocr_config.py +0 -174
- GameSentenceMiner/ocr/ocrconfig.py +0 -129
- GameSentenceMiner/ocr/owocr_area_selector.py +0 -629
- GameSentenceMiner/ocr/owocr_helper.py +0 -638
- GameSentenceMiner/ocr/ss_picker.py +0 -140
- GameSentenceMiner/owocr/owocr/__init__.py +0 -1
- GameSentenceMiner/owocr/owocr/__main__.py +0 -9
- GameSentenceMiner/owocr/owocr/config.py +0 -148
- GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -1238
- GameSentenceMiner/owocr/owocr/ocr.py +0 -1690
- GameSentenceMiner/owocr/owocr/run.py +0 -1817
- GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -109
- GameSentenceMiner/tools/__init__.py +0 -0
- GameSentenceMiner/tools/audio_offset_selector.py +0 -215
- GameSentenceMiner/tools/ss_selector.py +0 -135
- GameSentenceMiner/tools/window_transparency.py +0 -214
- GameSentenceMiner/util/__init__.py +0 -0
- GameSentenceMiner/util/communication/__init__.py +0 -22
- GameSentenceMiner/util/communication/send.py +0 -7
- GameSentenceMiner/util/communication/websocket.py +0 -94
- GameSentenceMiner/util/configuration.py +0 -1198
- GameSentenceMiner/util/db.py +0 -408
- GameSentenceMiner/util/downloader/Untitled_json.py +0 -472
- GameSentenceMiner/util/downloader/__init__.py +0 -0
- GameSentenceMiner/util/downloader/download_tools.py +0 -194
- GameSentenceMiner/util/downloader/oneocr_dl.py +0 -250
- GameSentenceMiner/util/electron_config.py +0 -259
- GameSentenceMiner/util/ffmpeg.py +0 -571
- GameSentenceMiner/util/get_overlay_coords.py +0 -366
- GameSentenceMiner/util/gsm_utils.py +0 -323
- GameSentenceMiner/util/model.py +0 -206
- GameSentenceMiner/util/notification.py +0 -147
- GameSentenceMiner/util/text_log.py +0 -214
- GameSentenceMiner/web/__init__.py +0 -0
- GameSentenceMiner/web/service.py +0 -132
- GameSentenceMiner/web/static/__init__.py +0 -0
- GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
- GameSentenceMiner/web/static/favicon-96x96.png +0 -0
- GameSentenceMiner/web/static/favicon.ico +0 -0
- GameSentenceMiner/web/static/favicon.svg +0 -3
- GameSentenceMiner/web/static/site.webmanifest +0 -21
- GameSentenceMiner/web/static/style.css +0 -292
- GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
- GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
- GameSentenceMiner/web/templates/__init__.py +0 -0
- GameSentenceMiner/web/templates/index.html +0 -50
- GameSentenceMiner/web/templates/text_replacements.html +0 -238
- GameSentenceMiner/web/templates/utility.html +0 -483
- GameSentenceMiner/web/texthooking_page.py +0 -584
- GameSentenceMiner/wip/__init___.py +0 -0
- gamesentenceminer-2.14.8.dist-info/RECORD +0 -77
- {gamesentenceminer-2.14.8.dist-info → gamesentenceminer-2.14.9.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.14.8.dist-info → gamesentenceminer-2.14.9.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.14.8.dist-info → gamesentenceminer-2.14.9.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.14.8.dist-info → gamesentenceminer-2.14.9.dist-info}/top_level.txt +0 -0
@@ -1,638 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
import io
|
3
|
-
import json
|
4
|
-
import logging
|
5
|
-
import os
|
6
|
-
import queue
|
7
|
-
import threading
|
8
|
-
import time
|
9
|
-
from datetime import datetime
|
10
|
-
from logging.handlers import RotatingFileHandler
|
11
|
-
from pathlib import Path
|
12
|
-
from tkinter import messagebox
|
13
|
-
|
14
|
-
import mss
|
15
|
-
import mss.tools
|
16
|
-
import websockets
|
17
|
-
from PIL import Image
|
18
|
-
from rapidfuzz import fuzz
|
19
|
-
|
20
|
-
from GameSentenceMiner import obs
|
21
|
-
from GameSentenceMiner.util.electron_config import *
|
22
|
-
from GameSentenceMiner.ocr.ss_picker import ScreenCropper
|
23
|
-
from GameSentenceMiner.owocr.owocr.run import TextFiltering
|
24
|
-
from GameSentenceMiner.util.configuration import get_config, get_app_directory, get_temporary_directory
|
25
|
-
from GameSentenceMiner.ocr.gsm_ocr_config import OCRConfig, has_config_changed, set_dpi_awareness, get_window, get_ocr_config_path
|
26
|
-
from GameSentenceMiner.owocr.owocr import screen_coordinate_picker, run
|
27
|
-
from GameSentenceMiner.util.gsm_utils import sanitize_filename, do_text_replacements, OCR_REPLACEMENTS_FILE
|
28
|
-
import threading
|
29
|
-
import time
|
30
|
-
|
31
|
-
CONFIG_FILE = Path("ocr_config.json")
|
32
|
-
DEFAULT_IMAGE_PATH = r"C:\Users\Beangate\Pictures\msedge_acbl8GL7Ax.jpg" # CHANGE THIS
|
33
|
-
logger = logging.getLogger("GSM_OCR")
|
34
|
-
logger.setLevel(logging.DEBUG)
|
35
|
-
# Create a file handler for logging
|
36
|
-
log_file = os.path.join(get_app_directory(), "logs", "ocr_log.txt")
|
37
|
-
os.makedirs(os.path.join(get_app_directory(), "logs"), exist_ok=True)
|
38
|
-
file_handler = RotatingFileHandler(log_file, maxBytes=1024 * 1024, backupCount=5, encoding='utf-8')
|
39
|
-
file_handler.setLevel(logging.DEBUG)
|
40
|
-
# Create a formatter and set it for the handler
|
41
|
-
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
42
|
-
file_handler.setFormatter(formatter)
|
43
|
-
# Add the handler to the logger
|
44
|
-
logger.addHandler(file_handler)
|
45
|
-
|
46
|
-
console_handler = logging.StreamHandler()
|
47
|
-
console_handler.setLevel(logging.INFO)
|
48
|
-
console_handler.setFormatter(formatter)
|
49
|
-
logger.addHandler(console_handler)
|
50
|
-
|
51
|
-
|
52
|
-
def get_ocr_config(window=None, use_window_for_config=False) -> OCRConfig:
|
53
|
-
"""Loads and updates screen capture areas from the corresponding JSON file."""
|
54
|
-
ocr_config_dir = get_ocr_config_path()
|
55
|
-
obs.update_current_game()
|
56
|
-
if use_window_for_config and window:
|
57
|
-
scene = sanitize_filename(window)
|
58
|
-
else:
|
59
|
-
scene = sanitize_filename(obs.get_current_scene())
|
60
|
-
config_path = Path(ocr_config_dir) / f"{scene}.json"
|
61
|
-
if not config_path.exists():
|
62
|
-
ocr_config = OCRConfig(scene=scene, window=window, rectangles=[], coordinate_system="percentage")
|
63
|
-
with open(config_path, 'w', encoding="utf-8") as f:
|
64
|
-
json.dump(ocr_config.to_dict(), f, indent=4)
|
65
|
-
return ocr_config
|
66
|
-
try:
|
67
|
-
with open(config_path, 'r', encoding="utf-8") as f:
|
68
|
-
config_data = json.load(f)
|
69
|
-
if "rectangles" in config_data and isinstance(config_data["rectangles"], list) and all(
|
70
|
-
isinstance(item, list) and len(item) == 4 for item in config_data["rectangles"]):
|
71
|
-
# Old config format, convert to new
|
72
|
-
new_rectangles = []
|
73
|
-
with mss.mss() as sct:
|
74
|
-
monitors = sct.monitors
|
75
|
-
default_monitor = monitors[1] if len(monitors) > 1 else monitors[0]
|
76
|
-
for rect in config_data["rectangles"]:
|
77
|
-
new_rectangles.append({
|
78
|
-
"monitor": {
|
79
|
-
"left": default_monitor["left"],
|
80
|
-
"top": default_monitor["top"],
|
81
|
-
"width": default_monitor["width"],
|
82
|
-
"height": default_monitor["height"],
|
83
|
-
"index": 0 # Assuming single monitor for old config
|
84
|
-
},
|
85
|
-
"coordinates": rect,
|
86
|
-
"is_excluded": False
|
87
|
-
})
|
88
|
-
if 'excluded_rectangles' in config_data:
|
89
|
-
for rect in config_data['excluded_rectangles']:
|
90
|
-
new_rectangles.append({
|
91
|
-
"monitor": {
|
92
|
-
"left": default_monitor["left"],
|
93
|
-
"top": default_monitor["top"],
|
94
|
-
"width": default_monitor["width"],
|
95
|
-
"height": default_monitor["height"],
|
96
|
-
"index": 0 # Assuming single monitor for old config
|
97
|
-
},
|
98
|
-
"coordinates": rect,
|
99
|
-
"is_excluded": True
|
100
|
-
})
|
101
|
-
new_config_data = {"scene": config_data.get("scene", scene), "window": config_data.get("window", None),
|
102
|
-
"rectangles": new_rectangles, "coordinate_system": "absolute"}
|
103
|
-
with open(config_path, 'w', encoding="utf-8") as f:
|
104
|
-
json.dump(new_config_data, f, indent=4)
|
105
|
-
return OCRConfig.from_dict(new_config_data)
|
106
|
-
elif "rectangles" in config_data and isinstance(config_data["rectangles"], list) and all(
|
107
|
-
isinstance(item, dict) and "coordinates" in item for item in config_data["rectangles"]):
|
108
|
-
return OCRConfig.from_dict(config_data)
|
109
|
-
else:
|
110
|
-
raise Exception(f"Invalid config format in {config_path}.")
|
111
|
-
except json.JSONDecodeError:
|
112
|
-
print("Error decoding JSON. Please check your config file.")
|
113
|
-
return None
|
114
|
-
except Exception as e:
|
115
|
-
print(f"Error loading config: {e}")
|
116
|
-
return None
|
117
|
-
|
118
|
-
|
119
|
-
websocket_server_thread = None
|
120
|
-
websocket_queue = queue.Queue()
|
121
|
-
paused = False
|
122
|
-
|
123
|
-
|
124
|
-
class WebsocketServerThread(threading.Thread):
|
125
|
-
def __init__(self, read):
|
126
|
-
super().__init__(daemon=True)
|
127
|
-
self._loop = None
|
128
|
-
self.read = read
|
129
|
-
self.clients = set()
|
130
|
-
self._event = threading.Event()
|
131
|
-
|
132
|
-
@property
|
133
|
-
def loop(self):
|
134
|
-
self._event.wait()
|
135
|
-
return self._loop
|
136
|
-
|
137
|
-
async def send_text_coroutine(self, message):
|
138
|
-
for client in self.clients:
|
139
|
-
await client.send(message)
|
140
|
-
|
141
|
-
async def server_handler(self, websocket):
|
142
|
-
self.clients.add(websocket)
|
143
|
-
try:
|
144
|
-
async for message in websocket:
|
145
|
-
if self.read and not paused:
|
146
|
-
websocket_queue.put(message)
|
147
|
-
try:
|
148
|
-
await websocket.send('True')
|
149
|
-
except websockets.exceptions.ConnectionClosedOK:
|
150
|
-
pass
|
151
|
-
else:
|
152
|
-
try:
|
153
|
-
await websocket.send('False')
|
154
|
-
except websockets.exceptions.ConnectionClosedOK:
|
155
|
-
pass
|
156
|
-
except websockets.exceptions.ConnectionClosedError:
|
157
|
-
pass
|
158
|
-
finally:
|
159
|
-
self.clients.remove(websocket)
|
160
|
-
|
161
|
-
async def send_text(self, text, line_time: datetime):
|
162
|
-
if text:
|
163
|
-
return asyncio.run_coroutine_threadsafe(
|
164
|
-
self.send_text_coroutine(json.dumps({"sentence": text, "time": line_time.isoformat(), "process_path": obs.get_current_game()})), self.loop)
|
165
|
-
|
166
|
-
def stop_server(self):
|
167
|
-
self.loop.call_soon_threadsafe(self._stop_event.set)
|
168
|
-
|
169
|
-
def run(self):
|
170
|
-
async def main():
|
171
|
-
self._loop = asyncio.get_running_loop()
|
172
|
-
self._stop_event = stop_event = asyncio.Event()
|
173
|
-
self._event.set()
|
174
|
-
self.server = start_server = websockets.serve(self.server_handler,
|
175
|
-
"0.0.0.0",
|
176
|
-
get_config().advanced.ocr_websocket_port,
|
177
|
-
max_size=1000000000)
|
178
|
-
async with start_server:
|
179
|
-
await stop_event.wait()
|
180
|
-
|
181
|
-
asyncio.run(main())
|
182
|
-
|
183
|
-
|
184
|
-
def compare_ocr_results(prev_text, new_text, threshold=90):
|
185
|
-
if not prev_text or not new_text:
|
186
|
-
return False
|
187
|
-
if isinstance(prev_text, list):
|
188
|
-
prev_text = ''.join([item for item in prev_text if item is not None]) if prev_text else ""
|
189
|
-
if isinstance(new_text, list):
|
190
|
-
new_text = ''.join([item for item in new_text if item is not None]) if new_text else ""
|
191
|
-
similarity = fuzz.ratio(prev_text, new_text)
|
192
|
-
return similarity >= threshold
|
193
|
-
|
194
|
-
all_cords = None
|
195
|
-
rectangles = None
|
196
|
-
last_ocr2_result = []
|
197
|
-
last_sent_result = ""
|
198
|
-
|
199
|
-
def do_second_ocr(ocr1_text, time, img, filtering, pre_crop_image=None, ignore_furigana_filter=False, ignore_previous_result=False):
|
200
|
-
global twopassocr, ocr2, last_ocr2_result, last_sent_result
|
201
|
-
try:
|
202
|
-
orig_text, text = run.process_and_write_results(img, None, last_ocr2_result if not ignore_previous_result else None, filtering, None,
|
203
|
-
engine=get_ocr_ocr2(), furigana_filter_sensitivity=furigana_filter_sensitivity if not ignore_furigana_filter else 0)
|
204
|
-
|
205
|
-
if compare_ocr_results(last_sent_result, text, threshold=80):
|
206
|
-
if text:
|
207
|
-
logger.info("Seems like Text we already sent, not doing anything.")
|
208
|
-
return
|
209
|
-
save_result_image(img, pre_crop_image=pre_crop_image)
|
210
|
-
last_ocr2_result = orig_text
|
211
|
-
last_sent_result = text
|
212
|
-
asyncio.run(send_result(text, time))
|
213
|
-
except json.JSONDecodeError:
|
214
|
-
print("Invalid JSON received.")
|
215
|
-
except Exception as e:
|
216
|
-
logger.exception(e)
|
217
|
-
print(f"Error processing message: {e}")
|
218
|
-
|
219
|
-
|
220
|
-
def save_result_image(img, pre_crop_image=None):
|
221
|
-
if isinstance(img, bytes):
|
222
|
-
with open(os.path.join(get_temporary_directory(), "last_successful_ocr.png"), "wb") as f:
|
223
|
-
f.write(img)
|
224
|
-
else:
|
225
|
-
img.save(os.path.join(get_temporary_directory(), "last_successful_ocr.png"))
|
226
|
-
run.set_last_image(pre_crop_image if pre_crop_image else img)
|
227
|
-
|
228
|
-
|
229
|
-
async def send_result(text, time):
|
230
|
-
if text:
|
231
|
-
text = do_text_replacements(text, OCR_REPLACEMENTS_FILE)
|
232
|
-
if get_ocr_send_to_clipboard():
|
233
|
-
import pyperclip
|
234
|
-
pyperclip.copy(text)
|
235
|
-
try:
|
236
|
-
await websocket_server_thread.send_text(text, time)
|
237
|
-
except Exception as e:
|
238
|
-
logger.debug(f"Error sending text to websocket: {e}")
|
239
|
-
|
240
|
-
|
241
|
-
previous_text_list = []
|
242
|
-
previous_text = "" # Store last OCR result
|
243
|
-
previous_ocr1_result = "" # Store last OCR1 result
|
244
|
-
last_oneocr_time = None # Store last OCR time
|
245
|
-
text_stable_start_time = None # Store the start time when text becomes stable
|
246
|
-
previous_img = None
|
247
|
-
previous_orig_text = "" # Store original text result
|
248
|
-
TEXT_APPEARENCE_DELAY = get_ocr_scan_rate() * 1000 + 500 # Adjust as needed
|
249
|
-
force_stable = False
|
250
|
-
|
251
|
-
class ConfigChangeCheckThread(threading.Thread):
|
252
|
-
def __init__(self):
|
253
|
-
super().__init__(daemon=True)
|
254
|
-
self.last_changes = None
|
255
|
-
self.config_callbacks = []
|
256
|
-
self.area_callbacks = []
|
257
|
-
|
258
|
-
def run(self):
|
259
|
-
global ocr_config
|
260
|
-
while True:
|
261
|
-
try:
|
262
|
-
section_changed, changes = has_ocr_config_changed()
|
263
|
-
if section_changed:
|
264
|
-
reload_electron_config()
|
265
|
-
self.last_changes = changes
|
266
|
-
# Only run this block after a change has occurred and then the section is stable (no change)
|
267
|
-
if self.last_changes is not None and not section_changed:
|
268
|
-
logger.info(f"Detected config changes: {self.last_changes}")
|
269
|
-
for cb in self.config_callbacks:
|
270
|
-
cb(self.last_changes)
|
271
|
-
if hasattr(run, 'handle_config_change'):
|
272
|
-
run.handle_config_change()
|
273
|
-
if any(c in self.last_changes for c in ('ocr1', 'ocr2', 'language', 'furigana_filter_sensitivity')):
|
274
|
-
reset_callback_vars()
|
275
|
-
self.last_changes = None
|
276
|
-
ocr_config_changed = has_config_changed(ocr_config)
|
277
|
-
if ocr_config_changed:
|
278
|
-
logger.info("OCR config has changed, reloading...")
|
279
|
-
ocr_config = get_ocr_config(use_window_for_config=True, window=obs.get_current_game())
|
280
|
-
for cb in self.area_callbacks:
|
281
|
-
cb(ocr_config)
|
282
|
-
if hasattr(run, 'handle_area_config_changes'):
|
283
|
-
run.handle_area_config_changes(ocr_config)
|
284
|
-
reset_callback_vars()
|
285
|
-
except Exception as e:
|
286
|
-
logger.debug(f"ConfigChangeCheckThread error: {e}")
|
287
|
-
time.sleep(0.25) # Lowered to 0.25s for more responsiveness
|
288
|
-
|
289
|
-
def add_config_callback(self, callback):
|
290
|
-
self.config_callbacks.append(callback)
|
291
|
-
|
292
|
-
def add_area_callback(self, callback):
|
293
|
-
self.area_callbacks.append(callback)
|
294
|
-
|
295
|
-
def reset_callback_vars():
|
296
|
-
global previous_text, last_oneocr_time, text_stable_start_time, previous_orig_text, previous_img, force_stable, previous_ocr1_result, previous_text_list, last_ocr2_result
|
297
|
-
previous_text = None
|
298
|
-
previous_orig_text = ""
|
299
|
-
previous_img = None
|
300
|
-
text_stable_start_time = None
|
301
|
-
last_oneocr_time = None
|
302
|
-
force_stable = False
|
303
|
-
previous_ocr1_result = ""
|
304
|
-
previous_text_list = []
|
305
|
-
last_ocr2_result = ""
|
306
|
-
run.set_last_image(None)
|
307
|
-
|
308
|
-
# class TwoPassOCRHandler:
|
309
|
-
# def __init__(self, ocr1, ocr2):
|
310
|
-
# self.ocr1 = ocr1
|
311
|
-
# self.ocr2 = ocr2
|
312
|
-
# self.second_ocr_queue = queue.Queue()
|
313
|
-
# self.previous_text = None
|
314
|
-
# self.previous_orig_text = ""
|
315
|
-
# self.previous_img = None
|
316
|
-
# self.text_stable_start_time = None
|
317
|
-
# self.last_oneocr_time = None
|
318
|
-
# self.force_stable = False
|
319
|
-
# self.previous_ocr1_result = ""
|
320
|
-
# self.last_sent_result = ""
|
321
|
-
# self.previous_text_list = []
|
322
|
-
|
323
|
-
# def check_first_ocr(self, text, orig_text, time, img=None, came_from_ss=False, filtering=None, crop_coords=None):
|
324
|
-
# if not twopassocr or not ocr2:
|
325
|
-
# return text_callback(text, orig_text, time, img=img, came_from_ss=came_from_ss, filtering=filtering, crop_coords=crop_coords)
|
326
|
-
|
327
|
-
# text_callback(text, orig_text, time, img=img, came_from_ss=came_from_ss, filtering=filtering, crop_coords=crop_coords)
|
328
|
-
|
329
|
-
# def set_ocr_ocr1(self, ocr1):
|
330
|
-
# self.ocr1 = ocr1
|
331
|
-
# def set_ocr_ocr2(self, ocr2):
|
332
|
-
# self.ocr2 = ocr2
|
333
|
-
|
334
|
-
# def get_ocr_ocr1(self):
|
335
|
-
# return self.ocr1
|
336
|
-
|
337
|
-
# def get_ocr_ocr2(self):
|
338
|
-
# return self.ocr2
|
339
|
-
|
340
|
-
def text_callback(text, orig_text, time, img=None, came_from_ss=False, filtering=None, crop_coords=None):
|
341
|
-
global twopassocr, ocr2, previous_text, last_oneocr_time, text_stable_start_time, previous_orig_text, previous_img, force_stable, previous_ocr1_result, previous_text_list, last_sent_result
|
342
|
-
orig_text_string = ''.join([item for item in orig_text if item is not None]) if orig_text else ""
|
343
|
-
if came_from_ss:
|
344
|
-
save_result_image(img)
|
345
|
-
asyncio.run(send_result(text, time))
|
346
|
-
return
|
347
|
-
|
348
|
-
if not text:
|
349
|
-
run.set_last_image(img)
|
350
|
-
|
351
|
-
line_start_time = time if time else datetime.now()
|
352
|
-
|
353
|
-
if manual or not get_ocr_two_pass_ocr():
|
354
|
-
if compare_ocr_results(last_sent_result, text, 80):
|
355
|
-
if text:
|
356
|
-
logger.info("Seems like Text we already sent, not doing anything.")
|
357
|
-
return
|
358
|
-
save_result_image(img)
|
359
|
-
asyncio.run(send_result(text, line_start_time))
|
360
|
-
last_sent_result = text
|
361
|
-
previous_orig_text = orig_text_string
|
362
|
-
previous_text = None
|
363
|
-
previous_img = None
|
364
|
-
text_stable_start_time = None
|
365
|
-
last_oneocr_time = None
|
366
|
-
return
|
367
|
-
if not text or force_stable:
|
368
|
-
force_stable = False
|
369
|
-
if previous_text and text_stable_start_time:
|
370
|
-
stable_time = text_stable_start_time
|
371
|
-
previous_img_local = previous_img
|
372
|
-
pre_crop_image = previous_img_local
|
373
|
-
if compare_ocr_results(previous_orig_text, orig_text_string):
|
374
|
-
if text:
|
375
|
-
logger.info("Seems like Text we already sent, not doing anything.")
|
376
|
-
previous_text = None
|
377
|
-
return
|
378
|
-
previous_orig_text = orig_text_string
|
379
|
-
previous_ocr1_result = previous_text
|
380
|
-
if crop_coords and get_ocr_optimize_second_scan():
|
381
|
-
x1, y1, x2, y2 = crop_coords
|
382
|
-
x1 = max(0, min(x1, img.width))
|
383
|
-
y1 = max(0, min(y1, img.height))
|
384
|
-
x2 = max(x1, min(x2, img.width))
|
385
|
-
y2 = max(y1, min(y2, img.height))
|
386
|
-
previous_img_local.save(os.path.join(get_temporary_directory(), "pre_oneocrcrop.png"))
|
387
|
-
try:
|
388
|
-
previous_img_local = previous_img_local.crop((x1, y1, x2, y2))
|
389
|
-
except ValueError:
|
390
|
-
logger.warning("Error cropping image, using original image")
|
391
|
-
second_ocr_queue.put((previous_text, stable_time, previous_img_local, filtering, pre_crop_image))
|
392
|
-
# threading.Thread(target=do_second_ocr, args=(previous_text, stable_time, previous_img_local, filtering), daemon=True).start()
|
393
|
-
previous_img = None
|
394
|
-
previous_text = None
|
395
|
-
text_stable_start_time = None
|
396
|
-
last_oneocr_time = None
|
397
|
-
previous_text = None
|
398
|
-
return
|
399
|
-
|
400
|
-
# Make sure it's an actual new line before starting the timer
|
401
|
-
if text and compare_ocr_results(orig_text_string, previous_orig_text):
|
402
|
-
return
|
403
|
-
|
404
|
-
if not text_stable_start_time:
|
405
|
-
text_stable_start_time = line_start_time
|
406
|
-
previous_text = text
|
407
|
-
previous_text_list = orig_text
|
408
|
-
last_oneocr_time = line_start_time
|
409
|
-
previous_img = img
|
410
|
-
|
411
|
-
done = False
|
412
|
-
|
413
|
-
# Create a queue for tasks
|
414
|
-
second_ocr_queue = queue.Queue()
|
415
|
-
|
416
|
-
def process_task_queue():
|
417
|
-
while True:
|
418
|
-
try:
|
419
|
-
task = second_ocr_queue.get()
|
420
|
-
if task is None: # Exit signal
|
421
|
-
break
|
422
|
-
ocr1_text, stable_time, previous_img_local, filtering, pre_crop_image = task
|
423
|
-
do_second_ocr(ocr1_text, stable_time, previous_img_local, filtering, pre_crop_image)
|
424
|
-
except Exception as e:
|
425
|
-
logger.exception(f"Error processing task: {e}")
|
426
|
-
finally:
|
427
|
-
second_ocr_queue.task_done()
|
428
|
-
|
429
|
-
|
430
|
-
def run_oneocr(ocr_config: OCRConfig, rectangles, config_check_thread):
|
431
|
-
global done
|
432
|
-
print("Running OneOCR")
|
433
|
-
screen_area = None
|
434
|
-
screen_areas = [",".join(str(c) for c in rect_config.coordinates) for rect_config in rectangles if not rect_config.is_excluded]
|
435
|
-
exclusions = list(rect.coordinates for rect in list(filter(lambda x: x.is_excluded, rectangles)))
|
436
|
-
|
437
|
-
run.init_config(False)
|
438
|
-
try:
|
439
|
-
read_from = ""
|
440
|
-
if obs_ocr:
|
441
|
-
read_from = "obs"
|
442
|
-
elif window:
|
443
|
-
read_from = "screencapture"
|
444
|
-
read_from_secondary = "clipboard" if ss_clipboard else None
|
445
|
-
run.run(read_from=read_from,
|
446
|
-
read_from_secondary=read_from_secondary,
|
447
|
-
write_to="callback",
|
448
|
-
screen_capture_area=screen_area,
|
449
|
-
# screen_capture_monitor=monitor_config['index'],
|
450
|
-
screen_capture_window=ocr_config.window if ocr_config and ocr_config.window else None,
|
451
|
-
screen_capture_delay_secs=get_ocr_scan_rate(), engine=ocr1,
|
452
|
-
text_callback=text_callback,
|
453
|
-
screen_capture_exclusions=exclusions,
|
454
|
-
monitor_index=None,
|
455
|
-
ocr1=ocr1,
|
456
|
-
ocr2=ocr2,
|
457
|
-
gsm_ocr_config=ocr_config,
|
458
|
-
screen_capture_areas=screen_areas,
|
459
|
-
furigana_filter_sensitivity=furigana_filter_sensitivity,
|
460
|
-
screen_capture_combo=manual_ocr_hotkey if manual_ocr_hotkey and manual else None,
|
461
|
-
config_check_thread=config_check_thread)
|
462
|
-
except Exception as e:
|
463
|
-
logger.exception(f"Error running OneOCR: {e}")
|
464
|
-
done = True
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
def add_ss_hotkey(ss_hotkey="ctrl+shift+g"):
|
469
|
-
import keyboard
|
470
|
-
|
471
|
-
def ocr_secondary_rectangles():
|
472
|
-
logger.info("Running secondary OCR rectangles...")
|
473
|
-
ocr_config = get_ocr_config()
|
474
|
-
img = obs.get_screenshot_PIL(compression=80, img_format="jpg")
|
475
|
-
ocr_config.scale_to_custom_size(img.width, img.height)
|
476
|
-
img = run.apply_ocr_config_to_image(img, ocr_config, is_secondary=True)
|
477
|
-
do_second_ocr("", datetime.now(), img, TextFiltering(lang=get_ocr_language()), ignore_furigana_filter=True, ignore_previous_result=True)
|
478
|
-
|
479
|
-
secret_ss_hotkey = "F14"
|
480
|
-
filtering = TextFiltering(lang=get_ocr_language())
|
481
|
-
cropper = ScreenCropper()
|
482
|
-
def capture():
|
483
|
-
print("Taking screenshot...")
|
484
|
-
img = cropper.run()
|
485
|
-
do_second_ocr("", datetime.now(), img, filtering, ignore_furigana_filter=True, ignore_previous_result=True)
|
486
|
-
def capture_main_monitor():
|
487
|
-
print("Taking screenshot of main monitor...")
|
488
|
-
with mss.mss() as sct:
|
489
|
-
main_monitor = sct.monitors[1] if len(sct.monitors) > 1 else sct.monitors[0]
|
490
|
-
img = sct.grab(main_monitor)
|
491
|
-
img_bytes = mss.tools.to_png(img.rgb, img.size)
|
492
|
-
do_second_ocr("", datetime.now(), img_bytes, filtering, ignore_furigana_filter=True, ignore_previous_result=True)
|
493
|
-
hotkey_reg = None
|
494
|
-
try:
|
495
|
-
hotkey_reg = keyboard.add_hotkey(ss_hotkey, capture)
|
496
|
-
if not manual:
|
497
|
-
secondary_hotkey_reg = keyboard.add_hotkey(get_ocr_manual_ocr_hotkey().lower(), ocr_secondary_rectangles)
|
498
|
-
if "f13" in ss_hotkey.lower():
|
499
|
-
secret_hotkey_reg = keyboard.add_hotkey(secret_ss_hotkey, capture_main_monitor)
|
500
|
-
print(f"Press {ss_hotkey} to take a screenshot.")
|
501
|
-
except Exception as e:
|
502
|
-
if hotkey_reg:
|
503
|
-
keyboard.remove_hotkey(hotkey_reg)
|
504
|
-
if secondary_hotkey_reg:
|
505
|
-
keyboard.remove_hotkey(secondary_hotkey_reg)
|
506
|
-
if secret_hotkey_reg:
|
507
|
-
keyboard.remove_hotkey(secret_hotkey_reg)
|
508
|
-
logger.error(f"Error setting up screenshot hotkey with keyboard, Attempting Backup: {e}")
|
509
|
-
logger.debug(e)
|
510
|
-
pynput_hotkey = ss_hotkey.replace("ctrl", "<ctrl>").replace("shift", "<shift>").replace("alt", "<alt>")
|
511
|
-
secret_ss_hotkey = secret_hotkey_reg.replace("ctrl", "<ctrl>").replace("shift", "<shift>").replace("alt", "<alt>")
|
512
|
-
secondary_ss_hotkey = secondary_hotkey_reg.replace("ctrl", "<ctrl>").replace("shift", "<shift>").replace("alt", "<alt>")
|
513
|
-
try:
|
514
|
-
from pynput import keyboard as pynput_keyboard
|
515
|
-
listener = pynput_keyboard.GlobalHotKeys({
|
516
|
-
pynput_hotkey: capture,
|
517
|
-
secondary_ss_hotkey: ocr_secondary_rectangles,
|
518
|
-
secret_ss_hotkey: capture_main_monitor
|
519
|
-
})
|
520
|
-
listener.start()
|
521
|
-
print(f"Press {pynput_hotkey} to take a screenshot.")
|
522
|
-
except Exception as e:
|
523
|
-
logger.error(f"Error setting up screenshot hotkey with pynput, Screenshot Hotkey Will not work: {e}")
|
524
|
-
|
525
|
-
def set_force_stable_hotkey():
|
526
|
-
import keyboard
|
527
|
-
global force_stable
|
528
|
-
def toggle_force_stable():
|
529
|
-
global force_stable
|
530
|
-
force_stable = not force_stable
|
531
|
-
if force_stable:
|
532
|
-
print("Force stable mode enabled.")
|
533
|
-
else:
|
534
|
-
print("Force stable mode disabled.")
|
535
|
-
keyboard.add_hotkey('p', toggle_force_stable)
|
536
|
-
print("Press Ctrl+Shift+F to toggle force stable mode.")
|
537
|
-
|
538
|
-
if __name__ == "__main__":
|
539
|
-
try:
|
540
|
-
global ocr1, ocr2, twopassocr, language, ss_clipboard, ss, ocr_config, furigana_filter_sensitivity, area_select_ocr_hotkey, window, optimize_second_scan, use_window_for_config, keep_newline, obs_ocr
|
541
|
-
import sys
|
542
|
-
|
543
|
-
import argparse
|
544
|
-
|
545
|
-
parser = argparse.ArgumentParser(description="OCR Configuration")
|
546
|
-
parser.add_argument("--language", type=str, default="ja", help="Language for OCR (default: ja)")
|
547
|
-
parser.add_argument("--ocr1", type=str, default="oneocr", help="Primary OCR engine (default: oneocr)")
|
548
|
-
parser.add_argument("--ocr2", type=str, default="glens", help="Secondary OCR engine (default: glens)")
|
549
|
-
parser.add_argument("--twopassocr", type=int, choices=[0, 1], default=1,
|
550
|
-
help="Enable two-pass OCR (default: 1)")
|
551
|
-
parser.add_argument("--manual", action="store_true", help="Use screenshot-only mode")
|
552
|
-
parser.add_argument("--clipboard", action="store_true", help="Use clipboard for input")
|
553
|
-
parser.add_argument("--clipboard-output", action="store_true", default=False, help="Use clipboard for output")
|
554
|
-
parser.add_argument("--window", type=str, help="Specify the window name for OCR")
|
555
|
-
parser.add_argument("--furigana_filter_sensitivity", type=float, default=0,
|
556
|
-
help="Furigana Filter Sensitivity for OCR (default: 0)")
|
557
|
-
parser.add_argument("--manual_ocr_hotkey", type=str, default=None, help="Hotkey for manual OCR (default: None)")
|
558
|
-
parser.add_argument("--area_select_ocr_hotkey", type=str, default="ctrl+shift+o",
|
559
|
-
help="Hotkey for area selection OCR (default: ctrl+shift+o)")
|
560
|
-
parser.add_argument("--optimize_second_scan", action="store_true",
|
561
|
-
help="Optimize second scan by cropping based on first scan results")
|
562
|
-
parser.add_argument("--use_window_for_config", action="store_true",
|
563
|
-
help="Use the specified window for loading OCR configuration")
|
564
|
-
parser.add_argument("--keep_newline", action="store_true", help="Keep new lines in OCR output")
|
565
|
-
parser.add_argument('--obs_ocr', action='store_true', help='Use OBS for Picture Source (not implemented)')
|
566
|
-
|
567
|
-
args = parser.parse_args()
|
568
|
-
|
569
|
-
language = args.language
|
570
|
-
ocr1 = args.ocr1
|
571
|
-
ocr2 = args.ocr2 if args.ocr2 else None
|
572
|
-
twopassocr = bool(args.twopassocr)
|
573
|
-
manual = args.manual
|
574
|
-
ss_clipboard = args.clipboard
|
575
|
-
window_name = args.window
|
576
|
-
furigana_filter_sensitivity = args.furigana_filter_sensitivity
|
577
|
-
ss_hotkey = args.area_select_ocr_hotkey.lower()
|
578
|
-
manual_ocr_hotkey = args.manual_ocr_hotkey.lower().replace("ctrl", "<ctrl>").replace("shift",
|
579
|
-
"<shift>").replace(
|
580
|
-
"alt", "<alt>") if args.manual_ocr_hotkey else None
|
581
|
-
clipboard_output = args.clipboard_output
|
582
|
-
optimize_second_scan = args.optimize_second_scan
|
583
|
-
use_window_for_config = args.use_window_for_config
|
584
|
-
keep_newline = args.keep_newline
|
585
|
-
obs_ocr = args.obs_ocr
|
586
|
-
|
587
|
-
obs.connect_to_obs_sync(retry=0)
|
588
|
-
|
589
|
-
# Start config change checker thread
|
590
|
-
config_check_thread = ConfigChangeCheckThread()
|
591
|
-
config_check_thread.start()
|
592
|
-
# Example: add a callback to config_check_thread if needed
|
593
|
-
# config_check_thread.add_callback(lambda: print("Config changed!"))
|
594
|
-
|
595
|
-
window = None
|
596
|
-
logger.info(f"Received arguments: {vars(args)}")
|
597
|
-
# set_force_stable_hotkey()
|
598
|
-
ocr_config: OCRConfig = get_ocr_config(window=window_name, use_window_for_config=use_window_for_config)
|
599
|
-
if ocr_config and not obs_ocr:
|
600
|
-
if ocr_config.window:
|
601
|
-
start_time = time.time()
|
602
|
-
while time.time() - start_time < 30:
|
603
|
-
window = get_window(ocr_config.window)
|
604
|
-
if window or manual:
|
605
|
-
if window:
|
606
|
-
ocr_config.scale_coords()
|
607
|
-
break
|
608
|
-
logger.info(f"Window: {ocr_config.window} Could not be found, retrying in 1 second...")
|
609
|
-
time.sleep(1)
|
610
|
-
else:
|
611
|
-
logger.error(f"Window '{ocr_config.window}' not found within 30 seconds.")
|
612
|
-
sys.exit(1)
|
613
|
-
logger.info(
|
614
|
-
f"Starting OCR with configuration: Window: {ocr_config.window}, Rectangles: {ocr_config.rectangles}, Engine 1: {ocr1}, Engine 2: {ocr2}, Two-pass OCR: {twopassocr}")
|
615
|
-
set_dpi_awareness()
|
616
|
-
if manual or ocr_config:
|
617
|
-
rectangles = ocr_config.rectangles if ocr_config and ocr_config.rectangles else []
|
618
|
-
oneocr_threads = []
|
619
|
-
ocr_thread = threading.Thread(target=run_oneocr, args=(ocr_config, rectangles, config_check_thread), daemon=True)
|
620
|
-
ocr_thread.start()
|
621
|
-
if not manual:
|
622
|
-
worker_thread = threading.Thread(target=process_task_queue, daemon=True)
|
623
|
-
worker_thread.start()
|
624
|
-
websocket_server_thread = WebsocketServerThread(read=True)
|
625
|
-
websocket_server_thread.start()
|
626
|
-
add_ss_hotkey(ss_hotkey)
|
627
|
-
try:
|
628
|
-
while not done:
|
629
|
-
time.sleep(1)
|
630
|
-
except KeyboardInterrupt as e:
|
631
|
-
pass
|
632
|
-
else:
|
633
|
-
print("Failed to load OCR configuration. Please check the logs.")
|
634
|
-
except Exception as e:
|
635
|
-
logger.info(e, exc_info=True)
|
636
|
-
logger.debug(e, exc_info=True)
|
637
|
-
logger.info("Closing in 5 seconds...")
|
638
|
-
time.sleep(5)
|