GameSentenceMiner 2.8.5__py3-none-any.whl → 2.8.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- GameSentenceMiner/ai/ai_prompting.py +201 -0
- GameSentenceMiner/anki.py +4 -3
- GameSentenceMiner/config_gui.py +42 -12
- GameSentenceMiner/configuration.py +39 -15
- GameSentenceMiner/gametext.py +26 -34
- GameSentenceMiner/gsm.py +58 -42
- GameSentenceMiner/obs.py +47 -24
- GameSentenceMiner/ocr/owocr_area_selector.py +4 -2
- GameSentenceMiner/ocr/owocr_helper.py +32 -3
- GameSentenceMiner/owocr/owocr/config.py +3 -1
- GameSentenceMiner/owocr/owocr/run.py +82 -15
- GameSentenceMiner/web/texthooking_page.py +172 -15
- {gamesentenceminer-2.8.5.dist-info → gamesentenceminer-2.8.7.dist-info}/METADATA +2 -1
- {gamesentenceminer-2.8.5.dist-info → gamesentenceminer-2.8.7.dist-info}/RECORD +18 -20
- {gamesentenceminer-2.8.5.dist-info → gamesentenceminer-2.8.7.dist-info}/WHEEL +1 -1
- GameSentenceMiner/ai/gemini.py +0 -143
- GameSentenceMiner/web/static/text_replacements.html +0 -238
- GameSentenceMiner/web/static/utility.html +0 -316
- {gamesentenceminer-2.8.5.dist-info → gamesentenceminer-2.8.7.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.8.5.dist-info → gamesentenceminer-2.8.7.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.8.5.dist-info → gamesentenceminer-2.8.7.dist-info}/top_level.txt +0 -0
GameSentenceMiner/gsm.py
CHANGED
@@ -1,36 +1,45 @@
|
|
1
|
-
import
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
import
|
6
|
-
import
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
from
|
12
|
-
|
13
|
-
|
14
|
-
from
|
15
|
-
|
16
|
-
|
17
|
-
from GameSentenceMiner import
|
18
|
-
from GameSentenceMiner import
|
19
|
-
from GameSentenceMiner import
|
20
|
-
from GameSentenceMiner import
|
21
|
-
from GameSentenceMiner import
|
22
|
-
from GameSentenceMiner
|
23
|
-
from GameSentenceMiner
|
24
|
-
from GameSentenceMiner
|
25
|
-
|
26
|
-
from GameSentenceMiner.
|
27
|
-
from GameSentenceMiner.
|
28
|
-
|
29
|
-
from GameSentenceMiner.
|
30
|
-
from GameSentenceMiner.
|
31
|
-
from GameSentenceMiner.
|
32
|
-
from GameSentenceMiner.
|
33
|
-
from GameSentenceMiner.
|
1
|
+
import asyncio
|
2
|
+
|
3
|
+
try:
|
4
|
+
import os.path
|
5
|
+
import signal
|
6
|
+
from subprocess import Popen
|
7
|
+
|
8
|
+
import keyboard
|
9
|
+
import psutil
|
10
|
+
import ttkbootstrap as ttk
|
11
|
+
from PIL import Image, ImageDraw
|
12
|
+
from pystray import Icon, Menu, MenuItem
|
13
|
+
from watchdog.events import FileSystemEventHandler
|
14
|
+
from watchdog.observers import Observer
|
15
|
+
|
16
|
+
|
17
|
+
from GameSentenceMiner import anki
|
18
|
+
from GameSentenceMiner import config_gui
|
19
|
+
from GameSentenceMiner import configuration
|
20
|
+
from GameSentenceMiner import ffmpeg
|
21
|
+
from GameSentenceMiner import gametext
|
22
|
+
from GameSentenceMiner import notification
|
23
|
+
from GameSentenceMiner import obs
|
24
|
+
from GameSentenceMiner import util
|
25
|
+
from GameSentenceMiner.communication import Message
|
26
|
+
from GameSentenceMiner.communication.send import send_restart_signal
|
27
|
+
from GameSentenceMiner.communication.websocket import connect_websocket, register_websocket_message_handler, \
|
28
|
+
FunctionName
|
29
|
+
from GameSentenceMiner.configuration import *
|
30
|
+
from GameSentenceMiner.downloader.download_tools import download_obs_if_needed, download_ffmpeg_if_needed
|
31
|
+
from GameSentenceMiner.ffmpeg import get_audio_and_trim, get_video_timings
|
32
|
+
from GameSentenceMiner.obs import check_obs_folder_is_correct
|
33
|
+
from GameSentenceMiner.text_log import GameLine, get_text_event, get_mined_line, get_all_lines
|
34
|
+
from GameSentenceMiner.util import *
|
35
|
+
from GameSentenceMiner.web import texthooking_page
|
36
|
+
from GameSentenceMiner.web.texthooking_page import run_text_hooker_page
|
37
|
+
except Exception as e:
|
38
|
+
from GameSentenceMiner.configuration import logger
|
39
|
+
import time
|
40
|
+
logger.info("Something bad happened during import/initialization, closing in 5 seconds")
|
41
|
+
logger.exception(e)
|
42
|
+
time.sleep(5)
|
34
43
|
|
35
44
|
if is_windows():
|
36
45
|
import win32api
|
@@ -449,10 +458,12 @@ def run_tray():
|
|
449
458
|
|
450
459
|
def close_obs():
|
451
460
|
obs.disconnect_from_obs()
|
452
|
-
if obs.
|
461
|
+
if obs.obs_process_pid:
|
453
462
|
try:
|
454
|
-
subprocess.run(["taskkill", "/PID", str(obs.
|
455
|
-
print(f"OBS (PID {obs.
|
463
|
+
subprocess.run(["taskkill", "/PID", str(obs.obs_process_pid), "/F"], check=True, capture_output=True, text=True)
|
464
|
+
print(f"OBS (PID {obs.obs_process_pid}) has been terminated.")
|
465
|
+
if os.path.exists(obs.OBS_PID_FILE):
|
466
|
+
os.remove(obs.OBS_PID_FILE)
|
456
467
|
except subprocess.CalledProcessError as e:
|
457
468
|
print(f"Error terminating OBS: {e.stderr}")
|
458
469
|
else:
|
@@ -460,9 +471,9 @@ def close_obs():
|
|
460
471
|
|
461
472
|
|
462
473
|
def restart_obs():
|
463
|
-
if obs.
|
474
|
+
if obs.obs_process_pid:
|
464
475
|
close_obs()
|
465
|
-
time.sleep(
|
476
|
+
time.sleep(1)
|
466
477
|
obs.start_obs()
|
467
478
|
obs.connect_to_obs()
|
468
479
|
|
@@ -532,7 +543,7 @@ def initialize(reloading=False):
|
|
532
543
|
# whisper_helper.initialize_whisper_model()
|
533
544
|
|
534
545
|
def initialize_async():
|
535
|
-
tasks = [
|
546
|
+
tasks = [connect_websocket, run_tray]
|
536
547
|
threads = []
|
537
548
|
tasks.append(anki.start_monitoring_anki)
|
538
549
|
for task in tasks:
|
@@ -555,7 +566,6 @@ def post_init():
|
|
555
566
|
whisper_helper.initialize_whisper_model()
|
556
567
|
if get_config().vad.is_silero():
|
557
568
|
from GameSentenceMiner.vad import silero_trim
|
558
|
-
start_web_server()
|
559
569
|
|
560
570
|
util.run_new_thread(do_post_init)
|
561
571
|
|
@@ -572,8 +582,10 @@ def handle_websocket_message(message: Message):
|
|
572
582
|
case _:
|
573
583
|
logger.debug(f"unknown message from electron websocket: {message.to_json()}")
|
574
584
|
|
585
|
+
def post_init2():
|
586
|
+
asyncio.run(gametext.start_text_monitor())
|
575
587
|
|
576
|
-
def main(reloading=False):
|
588
|
+
async def main(reloading=False):
|
577
589
|
global root, settings_window
|
578
590
|
logger.info("Script started.")
|
579
591
|
root = ttk.Window(themename='darkly')
|
@@ -586,12 +598,16 @@ def main(reloading=False):
|
|
586
598
|
if not is_linux():
|
587
599
|
register_hotkeys()
|
588
600
|
|
601
|
+
util.run_new_thread(post_init2)
|
602
|
+
util.run_new_thread(run_text_hooker_page)
|
603
|
+
|
589
604
|
# Register signal handlers for graceful shutdown
|
590
605
|
signal.signal(signal.SIGTERM, handle_exit()) # Handle `kill` commands
|
591
606
|
signal.signal(signal.SIGINT, handle_exit()) # Handle Ctrl+C
|
592
607
|
if is_windows():
|
593
608
|
win32api.SetConsoleCtrlHandler(handle_exit())
|
594
609
|
|
610
|
+
|
595
611
|
try:
|
596
612
|
# if get_config().general.open_config_on_startup:
|
597
613
|
# root.after(0, settings_window.show)
|
@@ -612,7 +628,7 @@ def main(reloading=False):
|
|
612
628
|
if __name__ == "__main__":
|
613
629
|
logger.info("Starting GSM")
|
614
630
|
try:
|
615
|
-
main()
|
631
|
+
asyncio.run(main())
|
616
632
|
except Exception as e:
|
617
633
|
logger.exception(e)
|
618
634
|
time.sleep(5)
|
GameSentenceMiner/obs.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import os.path
|
2
2
|
import subprocess
|
3
3
|
import time
|
4
|
+
import psutil
|
4
5
|
|
5
6
|
from obswebsocket import obsws, requests
|
6
7
|
|
@@ -9,8 +10,9 @@ from GameSentenceMiner.configuration import *
|
|
9
10
|
from GameSentenceMiner.model import *
|
10
11
|
|
11
12
|
client: obsws = None
|
12
|
-
|
13
|
+
obs_process_pid = None
|
13
14
|
logging.getLogger('obswebsocket').setLevel(logging.CRITICAL)
|
15
|
+
OBS_PID_FILE = os.path.join(configuration.get_app_directory(), 'obs-studio', 'obs_pid.txt')
|
14
16
|
|
15
17
|
# REFERENCE: https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md
|
16
18
|
|
@@ -18,20 +20,43 @@ logging.getLogger('obswebsocket').setLevel(logging.CRITICAL)
|
|
18
20
|
def get_obs_path():
|
19
21
|
return os.path.join(configuration.get_app_directory(), 'obs-studio/bin/64bit/obs64.exe')
|
20
22
|
|
23
|
+
def is_process_running(pid):
|
24
|
+
try:
|
25
|
+
process = psutil.Process(pid)
|
26
|
+
return 'obs' in process.exe()
|
27
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied, OSError):
|
28
|
+
if os.path.exists(OBS_PID_FILE):
|
29
|
+
os.remove(OBS_PID_FILE)
|
30
|
+
return False
|
31
|
+
|
21
32
|
def start_obs():
|
22
|
-
global
|
33
|
+
global obs_process_pid
|
34
|
+
if os.path.exists(OBS_PID_FILE):
|
35
|
+
with open(OBS_PID_FILE, "r") as f:
|
36
|
+
try:
|
37
|
+
obs_process_pid = int(f.read().strip())
|
38
|
+
if is_process_running(obs_process_pid):
|
39
|
+
print(f"OBS is already running with PID: {obs_process_pid}")
|
40
|
+
connect_to_obs()
|
41
|
+
return obs_process_pid
|
42
|
+
except ValueError:
|
43
|
+
print("Invalid PID found in file. Launching new OBS instance.")
|
44
|
+
except OSError:
|
45
|
+
print("No process found with the stored PID. Launching new OBS instance.")
|
46
|
+
|
23
47
|
obs_path = get_obs_path()
|
24
48
|
if not os.path.exists(obs_path):
|
25
|
-
|
49
|
+
print(f"OBS not found at {obs_path}. Please install OBS.")
|
26
50
|
return None
|
27
|
-
|
28
51
|
try:
|
29
|
-
obs_process = subprocess.Popen([obs_path, '--disable-shutdown-check', '--portable', '--startreplaybuffer'], cwd=os.path.dirname(obs_path))
|
30
|
-
|
31
|
-
|
32
|
-
|
52
|
+
obs_process = subprocess.Popen([obs_path, '--disable-shutdown-check', '--portable', '--startreplaybuffer', ], cwd=os.path.dirname(obs_path))
|
53
|
+
obs_process_pid = obs_process.pid
|
54
|
+
with open(OBS_PID_FILE, "w") as f:
|
55
|
+
f.write(str(obs_process_pid))
|
56
|
+
print(f"OBS launched with PID: {obs_process_pid}")
|
57
|
+
return obs_process_pid
|
33
58
|
except Exception as e:
|
34
|
-
|
59
|
+
print(f"Error launching OBS: {e}")
|
35
60
|
return None
|
36
61
|
|
37
62
|
def check_obs_folder_is_correct():
|
@@ -69,33 +94,32 @@ def get_obs_websocket_config_values():
|
|
69
94
|
|
70
95
|
if get_config().obs.password == 'your_password':
|
71
96
|
logger.info("OBS WebSocket password is not set. Setting it now...")
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
97
|
+
full_config = get_master_config()
|
98
|
+
full_config.get_config().obs.port = server_port
|
99
|
+
full_config.get_config().obs.password = server_password
|
100
|
+
full_config.sync_shared_fields()
|
101
|
+
full_config.save()
|
77
102
|
reload_config()
|
78
103
|
|
79
104
|
|
80
|
-
|
105
|
+
connected = False
|
81
106
|
|
82
107
|
def on_connect(obs):
|
83
|
-
global
|
108
|
+
global connected
|
84
109
|
logger.info("Reconnected to OBS WebSocket.")
|
85
|
-
|
86
|
-
|
87
|
-
reconnecting = False
|
110
|
+
start_replay_buffer()
|
111
|
+
connected = True
|
88
112
|
|
89
113
|
|
90
114
|
def on_disconnect(obs):
|
91
|
-
global
|
115
|
+
global connected
|
92
116
|
logger.error("OBS Connection Lost!")
|
93
|
-
|
117
|
+
connected = False
|
94
118
|
|
95
119
|
|
96
120
|
def connect_to_obs():
|
97
121
|
global client
|
98
|
-
if get_config().obs.enabled:
|
122
|
+
if get_config().obs.enabled and not client:
|
99
123
|
if util.is_windows():
|
100
124
|
get_obs_websocket_config_values()
|
101
125
|
client = obsws(host=get_config().obs.host, port=get_config().obs.port,
|
@@ -133,7 +157,6 @@ def do_obs_call(request, from_dict = None, retry=10):
|
|
133
157
|
time.sleep(1)
|
134
158
|
return do_obs_call(request, from_dict, retry - 1)
|
135
159
|
else:
|
136
|
-
logger.error(f"Error doing obs call: {e}")
|
137
160
|
raise e
|
138
161
|
return None
|
139
162
|
|
@@ -187,7 +210,7 @@ def get_current_scene():
|
|
187
210
|
try:
|
188
211
|
return do_obs_call(requests.GetCurrentProgramScene(), SceneInfo.from_dict, retry=0).sceneName
|
189
212
|
except Exception as e:
|
190
|
-
logger.
|
213
|
+
logger.debug(f"Couldn't get scene: {e}")
|
191
214
|
return ''
|
192
215
|
|
193
216
|
|
@@ -88,7 +88,6 @@ class ScreenSelector:
|
|
88
88
|
try:
|
89
89
|
windows = gw.getWindowsWithTitle(self.window_name)
|
90
90
|
if windows:
|
91
|
-
# TODO: Handle multiple matches if necessary (e.g., let user choose?)
|
92
91
|
if len(windows) > 1:
|
93
92
|
print(f"Warning: Multiple windows found with title '{self.window_name}'. Using the first one.")
|
94
93
|
return windows[0]
|
@@ -235,7 +234,7 @@ class ScreenSelector:
|
|
235
234
|
# --- End Conversion ---
|
236
235
|
|
237
236
|
# Validate size using the final absolute pixel coordinates
|
238
|
-
if abs_coords and abs_coords[2] >= MIN_RECT_WIDTH and abs_coords[3] >= MIN_RECT_HEIGHT:
|
237
|
+
if coordinate_system == COORD_SYSTEM_PERCENTAGE or (abs_coords and abs_coords[2] >= MIN_RECT_WIDTH and abs_coords[3] >= MIN_RECT_HEIGHT):
|
239
238
|
# Find the correct monitor dict from self.monitors based on index
|
240
239
|
monitor_index = monitor_data['index']
|
241
240
|
target_monitor = next((m for m in self.monitors if m['index'] == monitor_index), None)
|
@@ -863,6 +862,9 @@ if __name__ == "__main__":
|
|
863
862
|
# if not target_window_title:
|
864
863
|
# target_window_title = get_ocr_config().window
|
865
864
|
|
865
|
+
if not target_window_title:
|
866
|
+
target_window_title = "Windowed Projector (Preview)"
|
867
|
+
|
866
868
|
# Get the selection result
|
867
869
|
selection_result = get_screen_selection(target_window_title)
|
868
870
|
|
@@ -254,8 +254,13 @@ def text_callback(text, orig_text, rectangle_index, time, img=None):
|
|
254
254
|
# if orig_text:
|
255
255
|
# print(orig_text_string)
|
256
256
|
if not twopassocr:
|
257
|
+
if previous_orig_text and fuzz.ratio(orig_text_string, previous_orig_text) >= 80:
|
258
|
+
logger.info("Seems like Text we already sent, not doing anything.")
|
259
|
+
return
|
257
260
|
img.save(os.path.join(get_temporary_directory(), "last_successful_ocr.png"))
|
258
261
|
send_result(text, time)
|
262
|
+
orig_text_results[rectangle_index] = orig_text_string
|
263
|
+
last_ocr1_results[rectangle_index] = previous_text
|
259
264
|
if not text:
|
260
265
|
if previous_text:
|
261
266
|
if rectangle_index in text_stable_start_times:
|
@@ -269,8 +274,8 @@ def text_callback(text, orig_text, rectangle_index, time, img=None):
|
|
269
274
|
logger.info("Seems like Text we already sent, not doing anything.")
|
270
275
|
return
|
271
276
|
orig_text_results[rectangle_index] = orig_text_string
|
272
|
-
do_second_ocr(previous_text, rectangle_index, stable_time, previous_img)
|
273
277
|
last_ocr1_results[rectangle_index] = previous_text
|
278
|
+
do_second_ocr(previous_text, rectangle_index, stable_time, previous_img)
|
274
279
|
return
|
275
280
|
return
|
276
281
|
|
@@ -315,10 +320,24 @@ def run_oneocr(ocr_config: OCRConfig, i, area=False):
|
|
315
320
|
text_callback=text_callback,
|
316
321
|
screen_capture_exclusions=exclusions,
|
317
322
|
rectangle=i,
|
318
|
-
|
323
|
+
language="ja")
|
319
324
|
done = True
|
320
325
|
|
321
326
|
|
327
|
+
def get_window(window_name):
|
328
|
+
import pygetwindow as gw
|
329
|
+
try:
|
330
|
+
windows = gw.getWindowsWithTitle(window_name)
|
331
|
+
if windows:
|
332
|
+
if len(windows) > 1:
|
333
|
+
print(f"Warning: Multiple windows found with title '{window_name}'. Using the first one.")
|
334
|
+
return windows[0]
|
335
|
+
else:
|
336
|
+
return None
|
337
|
+
except Exception as e:
|
338
|
+
print(f"Error finding window '{self.window_name}': {e}")
|
339
|
+
return None
|
340
|
+
|
322
341
|
if __name__ == "__main__":
|
323
342
|
global ocr1, ocr2, twopassocr
|
324
343
|
import sys
|
@@ -343,7 +362,17 @@ if __name__ == "__main__":
|
|
343
362
|
logger.info(f"Received arguments: ocr1={ocr1}, ocr2={ocr2}, twopassocr={twopassocr}")
|
344
363
|
global ocr_config
|
345
364
|
ocr_config: OCRConfig = get_ocr_config()
|
346
|
-
|
365
|
+
if ocr_config:
|
366
|
+
if ocr_config.window:
|
367
|
+
start_time = time.time()
|
368
|
+
while time.time() - start_time < 30:
|
369
|
+
if get_window(ocr_config.window):
|
370
|
+
break
|
371
|
+
logger.info(f"Window: {ocr_config.window} Could not be found, retrying in 1 second...")
|
372
|
+
time.sleep(1)
|
373
|
+
else:
|
374
|
+
logger.error(f"Window '{ocr_config.window}' not found within 30 seconds.")
|
375
|
+
sys.exit(1)
|
347
376
|
logger.info(f"Starting OCR with configuration: Window: {ocr_config.window}, Rectangles: {len(ocr_config.rectangles)}, Engine 1: {ocr1}, Engine 2: {ocr2}, Two-pass OCR: {twopassocr}")
|
348
377
|
if ocr_config:
|
349
378
|
rectangles = list(filter(lambda rect: not rect.is_excluded, ocr_config.rectangles))
|
@@ -114,12 +114,14 @@ class Config:
|
|
114
114
|
for sub_key in config[key]:
|
115
115
|
self.__engine_config[key.lower()][sub_key.lower()] = self.__parse(config[key][sub_key])
|
116
116
|
|
117
|
-
def get_general(self, value):
|
117
|
+
def get_general(self, value, default_value=None):
|
118
118
|
if self.__provided_cli_args.get(value, None) is not None:
|
119
119
|
return self.__provided_cli_args[value]
|
120
120
|
try:
|
121
121
|
return self.__general_config[value]
|
122
122
|
except KeyError:
|
123
|
+
if default_value:
|
124
|
+
return default_value
|
123
125
|
if value in self.__default_config:
|
124
126
|
return self.__default_config[value]
|
125
127
|
else:
|
@@ -222,9 +222,6 @@ class WindowsWindowTracker(threading.Thread):
|
|
222
222
|
if self.window_active != is_active:
|
223
223
|
on_window_activated(is_active)
|
224
224
|
self.window_active = is_active
|
225
|
-
elif ignore_window_visiblity:
|
226
|
-
on_window_minimized(False)
|
227
|
-
self.window_minimized = False
|
228
225
|
else:
|
229
226
|
is_minimized = win32gui.IsIconic(self.window_handle)
|
230
227
|
if self.window_minimized != is_minimized:
|
@@ -308,8 +305,21 @@ class TextFiltering:
|
|
308
305
|
|
309
306
|
def __init__(self):
|
310
307
|
from pysbd import Segmenter
|
311
|
-
self.segmenter = Segmenter(language=
|
308
|
+
self.segmenter = Segmenter(language=lang, clean=True)
|
312
309
|
self.kana_kanji_regex = re.compile(r'[\u3041-\u3096\u30A1-\u30FA\u4E00-\u9FFF]')
|
310
|
+
self.chinese_common_regex = re.compile(r'[\u4E00-\u9FFF]')
|
311
|
+
self.english_regex = re.compile(r'[a-zA-Z0-9.,!?;:"\'()\[\]{}]')
|
312
|
+
self.kana_kanji_regex = re.compile(r'[\u3041-\u3096\u30A1-\u30FA\u4E00-\u9FFF]')
|
313
|
+
self.chinese_common_regex = re.compile(r'[\u4E00-\u9FFF]')
|
314
|
+
self.english_regex = re.compile(r'[a-zA-Z0-9.,!?;:"\'()\[\]{}]')
|
315
|
+
self.korean_regex = re.compile(r'[\uAC00-\uD7AF]')
|
316
|
+
self.arabic_regex = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]')
|
317
|
+
self.russian_regex = re.compile(r'[\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u1C80-\u1C8F]')
|
318
|
+
self.greek_regex = re.compile(r'[\u0370-\u03FF\u1F00-\u1FFF]')
|
319
|
+
self.hebrew_regex = re.compile(r'[\u0590-\u05FF\uFB1D-\uFB4F]')
|
320
|
+
self.thai_regex = re.compile(r'[\u0E00-\u0E7F]')
|
321
|
+
self.latin_extended_regex = re.compile(
|
322
|
+
r'[a-zA-Z\u00C0-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u1D00-\u1D7F\u1D80-\u1DBF\u1E00-\u1EFF\u2C60-\u2C7F\uA720-\uA7FF\uAB30-\uAB6F]')
|
313
323
|
try:
|
314
324
|
from transformers import pipeline, AutoTokenizer
|
315
325
|
import torch
|
@@ -337,7 +347,28 @@ class TextFiltering:
|
|
337
347
|
|
338
348
|
orig_text_filtered = []
|
339
349
|
for block in orig_text:
|
340
|
-
|
350
|
+
if lang == "ja":
|
351
|
+
block_filtered = self.kana_kanji_regex.findall(block)
|
352
|
+
elif lang == "zh":
|
353
|
+
block_filtered = self.chinese_common_regex.findall(block)
|
354
|
+
elif lang == "ko":
|
355
|
+
block_filtered = self.korean_regex.findall(block)
|
356
|
+
elif lang == "ar":
|
357
|
+
block_filtered = self.arabic_regex.findall(block)
|
358
|
+
elif lang == "ru":
|
359
|
+
block_filtered = self.russian_regex.findall(block)
|
360
|
+
elif lang == "el":
|
361
|
+
block_filtered = self.greek_regex.findall(block)
|
362
|
+
elif lang == "he":
|
363
|
+
block_filtered = self.hebrew_regex.findall(block)
|
364
|
+
elif lang == "th":
|
365
|
+
block_filtered = self.thai_regex.findall(block)
|
366
|
+
elif lang in ["en", "fr", "de", "es", "it", "pt", "nl", "sv", "da", "no",
|
367
|
+
"fi"]: # Many European languages use extended Latin
|
368
|
+
block_filtered = self.latin_extended_regex.findall(block)
|
369
|
+
else:
|
370
|
+
block_filtered = self.english_regex.findall(block)
|
371
|
+
|
341
372
|
if block_filtered:
|
342
373
|
orig_text_filtered.append(''.join(block_filtered))
|
343
374
|
else:
|
@@ -358,12 +389,12 @@ class TextFiltering:
|
|
358
389
|
detection_results = self.pipe(new_blocks, top_k=3, truncation=True)
|
359
390
|
for idx, block in enumerate(new_blocks):
|
360
391
|
for result in detection_results[idx]:
|
361
|
-
if result['label'] ==
|
392
|
+
if result['label'] == lang:
|
362
393
|
final_blocks.append(block)
|
363
394
|
break
|
364
395
|
else:
|
365
396
|
for block in new_blocks:
|
366
|
-
if self.classify(block)[0] ==
|
397
|
+
if self.classify(block)[0] == lang:
|
367
398
|
final_blocks.append(block)
|
368
399
|
|
369
400
|
text = '\n'.join(final_blocks)
|
@@ -565,10 +596,16 @@ def process_and_write_results(img_or_path, write_to, notifications, last_result,
|
|
565
596
|
|
566
597
|
orig_text = []
|
567
598
|
engine_color = config.get_general('engine_color')
|
599
|
+
# print(filtering)
|
600
|
+
#
|
601
|
+
#
|
602
|
+
# print(lang)
|
603
|
+
|
568
604
|
if res:
|
569
605
|
if filtering:
|
570
606
|
text, orig_text = filtering(text, last_result)
|
571
|
-
|
607
|
+
if lang == "ja" or lang == "zh":
|
608
|
+
text = post_process(text)
|
572
609
|
logger.opt(ansi=True).info(f'Text recognized in {t1 - t0:0.03f}s using <{engine_color}>{engine_instance.readable_name}</{engine_color}>: {text}')
|
573
610
|
if notifications:
|
574
611
|
notifier.send(title='owocr', message='Text recognized: ' + text)
|
@@ -588,6 +625,9 @@ def process_and_write_results(img_or_path, write_to, notifications, last_result,
|
|
588
625
|
else:
|
589
626
|
logger.opt(ansi=True).info(f'<{engine_color}>{engine_instance.readable_name}</{engine_color}> reported an error after {t1 - t0:0.03f}s: {text}')
|
590
627
|
|
628
|
+
# print(orig_text)
|
629
|
+
# print(text)
|
630
|
+
|
591
631
|
return orig_text, text
|
592
632
|
|
593
633
|
|
@@ -620,7 +660,7 @@ def run(read_from=None,
|
|
620
660
|
screen_capture_event_bus=None,
|
621
661
|
rectangle=None,
|
622
662
|
text_callback=None,
|
623
|
-
|
663
|
+
language=None,
|
624
664
|
):
|
625
665
|
"""
|
626
666
|
Japanese OCR client
|
@@ -646,18 +686,45 @@ def run(read_from=None,
|
|
646
686
|
:param screen_capture_combo: When reading with screen capture, specifies a combo to wait on for taking a screenshot instead of using the delay. As an example: "<ctrl>+<shift>+s". The list of keys can be found here: https://pynput.readthedocs.io/en/latest/keyboard.html#pynput.keyboard.Key
|
647
687
|
"""
|
648
688
|
|
649
|
-
if
|
689
|
+
if read_from is None:
|
650
690
|
read_from = config.get_general('read_from')
|
651
691
|
|
652
|
-
if
|
692
|
+
if screen_capture_area is None:
|
653
693
|
screen_capture_area = config.get_general('screen_capture_area')
|
654
694
|
|
655
|
-
if
|
695
|
+
if screen_capture_only_active_windows is None:
|
656
696
|
screen_capture_only_active_windows = config.get_general('screen_capture_only_active_windows')
|
657
697
|
|
658
|
-
if
|
698
|
+
if screen_capture_exclusions is None:
|
699
|
+
screen_capture_exclusions = config.get_general('screen_capture_exclusions')
|
700
|
+
|
701
|
+
if screen_capture_window is None:
|
702
|
+
screen_capture_window = config.get_general('screen_capture_window')
|
703
|
+
|
704
|
+
if screen_capture_delay_secs is None:
|
705
|
+
screen_capture_delay_secs = config.get_general('screen_capture_delay_secs')
|
706
|
+
|
707
|
+
if screen_capture_combo is None:
|
708
|
+
screen_capture_combo = config.get_general('screen_capture_combo')
|
709
|
+
|
710
|
+
if stop_running_flag is None:
|
711
|
+
stop_running_flag = config.get_general('stop_running_flag')
|
712
|
+
|
713
|
+
if screen_capture_event_bus is None:
|
714
|
+
screen_capture_event_bus = config.get_general('screen_capture_event_bus')
|
715
|
+
|
716
|
+
if rectangle is None:
|
717
|
+
rectangle = config.get_general('rectangle')
|
718
|
+
|
719
|
+
if text_callback is None:
|
720
|
+
text_callback = config.get_general('text_callback')
|
721
|
+
|
722
|
+
if write_to is None:
|
659
723
|
write_to = config.get_general('write_to')
|
660
724
|
|
725
|
+
if language is None:
|
726
|
+
language = config.get_general('language', "ja")
|
727
|
+
|
661
728
|
logger.configure(handlers=[{'sink': sys.stderr, 'format': config.get_general('logger_format')}])
|
662
729
|
|
663
730
|
if config.has_config:
|
@@ -669,8 +736,8 @@ def run(read_from=None,
|
|
669
736
|
|
670
737
|
global engine_instances
|
671
738
|
global engine_keys
|
672
|
-
global
|
673
|
-
|
739
|
+
global lang
|
740
|
+
lang = language
|
674
741
|
engine_instances = []
|
675
742
|
config_engines = []
|
676
743
|
engine_keys = []
|