GameSentenceMiner 2.12.0.dev5__py3-none-any.whl → 2.12.0.dev6__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/anki.py +31 -1
- GameSentenceMiner/config_gui.py +10 -0
- GameSentenceMiner/gametext.py +2 -2
- GameSentenceMiner/gsm.py +12 -0
- GameSentenceMiner/owocr/owocr/ocr.py +122 -52
- GameSentenceMiner/owocr/owocr/run.py +37 -4
- GameSentenceMiner/util/configuration.py +13 -3
- GameSentenceMiner/util/text_log.py +2 -2
- GameSentenceMiner/web/texthooking_page.py +16 -14
- GameSentenceMiner/wip/get_overlay_coords.py +168 -22
- {gamesentenceminer-2.12.0.dev5.dist-info → gamesentenceminer-2.12.0.dev6.dist-info}/METADATA +1 -1
- {gamesentenceminer-2.12.0.dev5.dist-info → gamesentenceminer-2.12.0.dev6.dist-info}/RECORD +16 -16
- {gamesentenceminer-2.12.0.dev5.dist-info → gamesentenceminer-2.12.0.dev6.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.12.0.dev5.dist-info → gamesentenceminer-2.12.0.dev6.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.12.0.dev5.dist-info → gamesentenceminer-2.12.0.dev6.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.12.0.dev5.dist-info → gamesentenceminer-2.12.0.dev6.dist-info}/top_level.txt +0 -0
GameSentenceMiner/anki.py
CHANGED
@@ -19,6 +19,7 @@ from GameSentenceMiner.util.model import AnkiCard
|
|
19
19
|
from GameSentenceMiner.util.text_log import get_all_lines, get_text_event, get_mined_line, lines_match
|
20
20
|
from GameSentenceMiner.obs import get_current_game
|
21
21
|
from GameSentenceMiner.web import texthooking_page
|
22
|
+
import re
|
22
23
|
|
23
24
|
# Global variables to track state
|
24
25
|
previous_note_ids = set()
|
@@ -161,7 +162,36 @@ def get_initial_card_info(last_note: AnkiCard, selected_lines):
|
|
161
162
|
game_line = get_text_event(last_note)
|
162
163
|
sentences = []
|
163
164
|
sentences_text = ''
|
164
|
-
|
165
|
+
|
166
|
+
# TODO: REMOVE THIS, I DON'T THINK IT'S NEEDED
|
167
|
+
if get_config().wip.overlay_websocket_send:
|
168
|
+
sentence_in_anki = last_note.get_field(get_config().anki.sentence_field).replace("\n", "").replace("\r", "").strip()
|
169
|
+
if lines_match(game_line.text, remove_html_and_cloze_tags(sentence_in_anki)):
|
170
|
+
logger.info("Found matching line in Anki, Preserving HTML and fix spacing!")
|
171
|
+
if "<b>" in sentence_in_anki:
|
172
|
+
text_inside_bold = re.findall(r'<b>(.*?)</b>', sentence_in_anki)
|
173
|
+
logger.info(text_inside_bold)
|
174
|
+
if text_inside_bold:
|
175
|
+
text = text_inside_bold[0].replace(" ", "").replace('\n', '').strip()
|
176
|
+
note['fields'][get_config().anki.sentence_field] = game_line.text.replace(text_inside_bold[0], f"<b>{text}</b>")
|
177
|
+
logger.info(f"Preserved bold Tag for Sentence: {note['fields'][get_config().anki.sentence_field]}")
|
178
|
+
if "<i>" in sentence_in_anki:
|
179
|
+
text_inside_italic = re.findall(r'<i>(.*?)</i>', sentence_in_anki)
|
180
|
+
if text_inside_italic:
|
181
|
+
text = text_inside_italic[0].replace(" ", "").replace('\n', '').strip()
|
182
|
+
note['fields'][get_config().anki.sentence_field] = game_line.text.replace(text_inside_italic[0], f"<i>{text}</i>")
|
183
|
+
logger.info(f"Preserved italic Tag for Sentence: {note['fields'][get_config().anki.sentence_field]}")
|
184
|
+
if "<u>" in sentence_in_anki:
|
185
|
+
text_inside_underline = re.findall(r'<u>(.*?)</u>', sentence_in_anki)
|
186
|
+
if text_inside_underline:
|
187
|
+
text = text_inside_underline[0].replace(" ", "").replace('\n', '').strip()
|
188
|
+
note['fields'][get_config().anki.sentence_field] = game_line.text.replace(text_inside_underline[0], f"<u>{text}</u>")
|
189
|
+
logger.info(f"Preserved underline Tag for Sentence: {note['fields'][get_config().anki.sentence_field]}")
|
190
|
+
|
191
|
+
if get_config().anki.sentence_field not in note['fields']:
|
192
|
+
logger.info("No HTML tags found to preserve, just fixing spacing")
|
193
|
+
note['fields'][get_config().anki.sentence_field] = game_line.text
|
194
|
+
elif selected_lines:
|
165
195
|
try:
|
166
196
|
sentence_in_anki = last_note.get_field(get_config().anki.sentence_field)
|
167
197
|
logger.info(f"Attempting Preserve HTML for multi-line")
|
GameSentenceMiner/config_gui.py
CHANGED
@@ -113,6 +113,7 @@ class ConfigApp:
|
|
113
113
|
self.window.title('GameSentenceMiner Configuration')
|
114
114
|
self.window.protocol("WM_DELETE_WINDOW", self.hide)
|
115
115
|
self.obs_scene_listbox_changed = False
|
116
|
+
self.test_func = None
|
116
117
|
|
117
118
|
# self.window.geometry("800x500")
|
118
119
|
self.current_row = 0
|
@@ -162,6 +163,9 @@ class ConfigApp:
|
|
162
163
|
self.window.update_idletasks()
|
163
164
|
self.window.geometry("")
|
164
165
|
self.window.withdraw()
|
166
|
+
|
167
|
+
def set_test_func(self, func):
|
168
|
+
self.test_func = func
|
165
169
|
|
166
170
|
def create_tabs(self):
|
167
171
|
self.create_general_tab()
|
@@ -580,6 +584,12 @@ class ConfigApp:
|
|
580
584
|
text="Every Label in settings has a tooltip with more information if you hover over them.",
|
581
585
|
font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
|
582
586
|
self.current_row += 1
|
587
|
+
|
588
|
+
if is_beangate:
|
589
|
+
ttk.Button(self.general_tab, text="Run Function", command=self.test_func, bootstyle="info").grid(
|
590
|
+
row=self.current_row, column=0, pady=5
|
591
|
+
)
|
592
|
+
self.current_row += 1
|
583
593
|
|
584
594
|
# Add Reset to Default button
|
585
595
|
self.add_reset_button(self.general_tab, "general", self.current_row, column=0, recreate_tab=self.create_general_tab)
|
GameSentenceMiner/gametext.py
CHANGED
@@ -9,7 +9,7 @@ from websockets import InvalidStatus
|
|
9
9
|
from GameSentenceMiner.util.gsm_utils import do_text_replacements, TEXT_REPLACEMENTS_FILE, run_new_thread
|
10
10
|
from GameSentenceMiner.util.configuration import *
|
11
11
|
from GameSentenceMiner.util.text_log import *
|
12
|
-
from GameSentenceMiner.web.texthooking_page import add_event_to_texthooker, send_word_coordinates_to_overlay
|
12
|
+
from GameSentenceMiner.web.texthooking_page import add_event_to_texthooker, send_word_coordinates_to_overlay, overlay_server_thread
|
13
13
|
from GameSentenceMiner.wip import get_overlay_coords
|
14
14
|
|
15
15
|
current_line = ''
|
@@ -125,7 +125,7 @@ async def handle_new_text_event(current_clipboard, line_time=None):
|
|
125
125
|
add_line(current_line_after_regex, line_time)
|
126
126
|
if len(get_text_log().values) > 0:
|
127
127
|
await add_event_to_texthooker(get_text_log()[-1])
|
128
|
-
if get_config().wip.overlay_websocket_port and get_config().wip.overlay_websocket_send:
|
128
|
+
if get_config().wip.overlay_websocket_port and get_config().wip.overlay_websocket_send and overlay_server_thread.has_clients():
|
129
129
|
boxes = await find_box_for_sentence(current_line_after_regex)
|
130
130
|
if boxes:
|
131
131
|
await send_word_coordinates_to_overlay(boxes)
|
GameSentenceMiner/gsm.py
CHANGED
@@ -545,6 +545,8 @@ def async_loop():
|
|
545
545
|
await check_obs_folder_is_correct()
|
546
546
|
logger.info("Post-Initialization started.")
|
547
547
|
vad_processor.init()
|
548
|
+
# if is_beangate:
|
549
|
+
# await run_test_code()
|
548
550
|
|
549
551
|
asyncio.run(loop())
|
550
552
|
|
@@ -577,6 +579,13 @@ async def register_scene_switcher_callback():
|
|
577
579
|
update_icon()
|
578
580
|
|
579
581
|
await obs.register_scene_change_callback(scene_switcher_callback)
|
582
|
+
|
583
|
+
async def run_test_code():
|
584
|
+
if get_config().wip.overlay_websocket_port and get_config().wip.overlay_websocket_send:
|
585
|
+
boxes = await gametext.find_box_for_sentence("ちぇっ少しなの?")
|
586
|
+
if boxes:
|
587
|
+
await texthooking_page.send_word_coordinates_to_overlay(boxes)
|
588
|
+
await asyncio.sleep(2)
|
580
589
|
|
581
590
|
async def async_main(reloading=False):
|
582
591
|
global root, settings_window
|
@@ -600,6 +609,9 @@ async def async_main(reloading=False):
|
|
600
609
|
signal.signal(signal.SIGINT, handle_exit()) # Handle Ctrl+C
|
601
610
|
if is_windows():
|
602
611
|
win32api.SetConsoleCtrlHandler(handle_exit())
|
612
|
+
|
613
|
+
if is_beangate:
|
614
|
+
settings_window.set_test_func(lambda: run_new_thread(run_test_code))
|
603
615
|
|
604
616
|
gsm_status.ready = True
|
605
617
|
gsm_status.status = "Ready"
|
@@ -6,13 +6,14 @@ from pathlib import Path
|
|
6
6
|
import sys
|
7
7
|
import platform
|
8
8
|
import logging
|
9
|
-
from math import sqrt
|
9
|
+
from math import sqrt, floor
|
10
10
|
import json
|
11
11
|
import base64
|
12
12
|
from urllib.parse import urlparse, parse_qs
|
13
13
|
|
14
14
|
import jaconv
|
15
15
|
import numpy as np
|
16
|
+
import rapidfuzz.fuzz
|
16
17
|
from PIL import Image
|
17
18
|
from loguru import logger
|
18
19
|
import requests
|
@@ -164,6 +165,28 @@ def limit_image_size(img, max_size):
|
|
164
165
|
return False, ''
|
165
166
|
|
166
167
|
|
168
|
+
def get_regex(lang):
|
169
|
+
if lang == "ja":
|
170
|
+
return re.compile(r'[\u3041-\u3096\u30A1-\u30FA\u4E00-\u9FFF]')
|
171
|
+
elif lang == "zh":
|
172
|
+
return re.compile(r'[\u4E00-\u9FFF]')
|
173
|
+
elif lang == "ko":
|
174
|
+
return re.compile(r'[\uAC00-\uD7AF]')
|
175
|
+
elif lang == "ar":
|
176
|
+
return re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]')
|
177
|
+
elif lang == "ru":
|
178
|
+
return re.compile(r'[\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u1C80-\u1C8F]')
|
179
|
+
elif lang == "el":
|
180
|
+
return re.compile(r'[\u0370-\u03FF\u1F00-\u1FFF]')
|
181
|
+
elif lang == "he":
|
182
|
+
return re.compile(r'[\u0590-\u05FF\uFB1D-\uFB4F]')
|
183
|
+
elif lang == "th":
|
184
|
+
return re.compile(r'[\u0E00-\u0E7F]')
|
185
|
+
else:
|
186
|
+
return re.compile(
|
187
|
+
r'[a-zA-Z\u00C0-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u1D00-\u1D7F\u1D80-\u1DBF\u1E00-\u1EFF\u2C60-\u2C7F\uA720-\uA7FF\uAB30-\uAB6F]')
|
188
|
+
|
189
|
+
|
167
190
|
class MangaOcr:
|
168
191
|
name = 'mangaocr'
|
169
192
|
readable_name = 'Manga OCR'
|
@@ -243,15 +266,20 @@ class GoogleLens:
|
|
243
266
|
available = False
|
244
267
|
|
245
268
|
def __init__(self, lang='ja'):
|
246
|
-
self.
|
269
|
+
self.regex = get_regex(lang)
|
270
|
+
self.initial_lang = lang
|
247
271
|
if 'betterproto' not in sys.modules:
|
248
272
|
logger.warning('betterproto not available, Google Lens will not work!')
|
249
273
|
else:
|
250
274
|
self.available = True
|
251
275
|
logger.info('Google Lens ready')
|
252
276
|
|
253
|
-
def __call__(self, img, furigana_filter_sensitivity=0):
|
277
|
+
def __call__(self, img, furigana_filter_sensitivity=0, return_coords=False):
|
278
|
+
lang = get_ocr_language()
|
254
279
|
img, is_path = input_to_pil_image(img)
|
280
|
+
if lang != self.initial_lang:
|
281
|
+
self.initial_lang = lang
|
282
|
+
self.regex = get_regex(lang)
|
255
283
|
if not img:
|
256
284
|
return (False, 'Invalid image provided')
|
257
285
|
|
@@ -309,12 +337,14 @@ class GoogleLens:
|
|
309
337
|
response_proto = LensOverlayServerResponse().FromString(res.content)
|
310
338
|
response_dict = response_proto.to_dict(betterproto.Casing.SNAKE)
|
311
339
|
|
312
|
-
|
313
|
-
|
340
|
+
if os.path.exists(r"C:\Users\Beangate\GSM\Electron App\test"):
|
341
|
+
with open(os.path.join(r"C:\Users\Beangate\GSM\Electron App\test", 'glens_response.json'), 'w', encoding='utf-8') as f:
|
342
|
+
json.dump(response_dict, f, indent=4, ensure_ascii=False)
|
314
343
|
res = ''
|
315
344
|
text = response_dict['objects_response']['text']
|
316
345
|
skipped = []
|
317
346
|
previous_line = None
|
347
|
+
lines = []
|
318
348
|
if 'text_layout' in text:
|
319
349
|
for paragraph in text['text_layout']['paragraphs']:
|
320
350
|
if previous_line:
|
@@ -330,18 +360,38 @@ class GoogleLens:
|
|
330
360
|
if vertical_space > avg_height * 2:
|
331
361
|
res += 'BLANK_LINE'
|
332
362
|
for line in paragraph['lines']:
|
363
|
+
# Build a list of word boxes for this line
|
364
|
+
words_info = []
|
365
|
+
for word in line['words']:
|
366
|
+
word_info = {
|
367
|
+
"word": word['plain_text'],
|
368
|
+
"x1": int(word['geometry']['bounding_box']['center_x'] * img.width - (word['geometry']['bounding_box']['width'] * img.width) / 2),
|
369
|
+
"y1": int(word['geometry']['bounding_box']['center_y'] * img.height - (word['geometry']['bounding_box']['height'] * img.height) / 2),
|
370
|
+
"x2": int(word['geometry']['bounding_box']['center_x'] * img.width + (word['geometry']['bounding_box']['width'] * img.width) / 2),
|
371
|
+
"y2": int(word['geometry']['bounding_box']['center_y'] * img.height + (word['geometry']['bounding_box']['height'] * img.height) / 2)
|
372
|
+
}
|
373
|
+
words_info.append(word_info)
|
374
|
+
|
375
|
+
line_text = ''.join([w['word'] for w in words_info])
|
376
|
+
line_box = {
|
377
|
+
"sentence": line_text,
|
378
|
+
"words": words_info
|
379
|
+
}
|
380
|
+
|
381
|
+
# Optionally apply furigana filter
|
333
382
|
if furigana_filter_sensitivity:
|
334
|
-
|
335
|
-
|
336
|
-
|
383
|
+
line_width = line['geometry']['bounding_box']['width'] * img.width
|
384
|
+
line_height = line['geometry']['bounding_box']['height'] * img.height
|
385
|
+
if furigana_filter_sensitivity < line_width and furigana_filter_sensitivity < line_height and self.regex.search(line_text):
|
386
|
+
for w in words_info:
|
387
|
+
res += w['word']
|
337
388
|
else:
|
338
|
-
skipped.
|
389
|
+
skipped.extend([w['word'] for w in words_info])
|
339
390
|
continue
|
340
391
|
else:
|
341
|
-
for
|
342
|
-
|
343
|
-
|
344
|
-
continue
|
392
|
+
for w in words_info:
|
393
|
+
res += w['word']
|
394
|
+
lines.append(line_box)
|
345
395
|
previous_line = paragraph
|
346
396
|
res += '\n'
|
347
397
|
# logger.info(
|
@@ -384,8 +434,11 @@ class GoogleLens:
|
|
384
434
|
# else:
|
385
435
|
# continue
|
386
436
|
# res += '\n'
|
387
|
-
|
388
|
-
|
437
|
+
|
438
|
+
if return_coords:
|
439
|
+
x = (True, res, lines)
|
440
|
+
else:
|
441
|
+
x = (True, res)
|
389
442
|
|
390
443
|
# img.close()
|
391
444
|
return x
|
@@ -812,7 +865,7 @@ class OneOCR:
|
|
812
865
|
|
813
866
|
def __init__(self, config={}, lang='ja'):
|
814
867
|
self.initial_lang = lang
|
815
|
-
self.get_regex(lang)
|
868
|
+
self.regex = get_regex(lang)
|
816
869
|
if sys.platform == 'win32':
|
817
870
|
if int(platform.release()) < 10:
|
818
871
|
logger.warning('OneOCR is not supported on Windows older than 10!')
|
@@ -834,32 +887,11 @@ class OneOCR:
|
|
834
887
|
except:
|
835
888
|
logger.warning('Error reading URL from config, OneOCR will not work!')
|
836
889
|
|
837
|
-
def
|
838
|
-
if lang == "ja":
|
839
|
-
self.regex = re.compile(r'[\u3041-\u3096\u30A1-\u30FA\u4E00-\u9FFF]')
|
840
|
-
elif lang == "zh":
|
841
|
-
self.regex = re.compile(r'[\u4E00-\u9FFF]')
|
842
|
-
elif lang == "ko":
|
843
|
-
self.regex = re.compile(r'[\uAC00-\uD7AF]')
|
844
|
-
elif lang == "ar":
|
845
|
-
self.regex = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]')
|
846
|
-
elif lang == "ru":
|
847
|
-
self.regex = re.compile(r'[\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u1C80-\u1C8F]')
|
848
|
-
elif lang == "el":
|
849
|
-
self.regex = re.compile(r'[\u0370-\u03FF\u1F00-\u1FFF]')
|
850
|
-
elif lang == "he":
|
851
|
-
self.regex = re.compile(r'[\u0590-\u05FF\uFB1D-\uFB4F]')
|
852
|
-
elif lang == "th":
|
853
|
-
self.regex = re.compile(r'[\u0E00-\u0E7F]')
|
854
|
-
else:
|
855
|
-
self.regex = re.compile(
|
856
|
-
r'[a-zA-Z\u00C0-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u1D00-\u1D7F\u1D80-\u1DBF\u1E00-\u1EFF\u2C60-\u2C7F\uA720-\uA7FF\uAB30-\uAB6F]')
|
857
|
-
|
858
|
-
def __call__(self, img, furigana_filter_sensitivity=0):
|
890
|
+
def __call__(self, img, furigana_filter_sensitivity=0, sentence_to_check=None, return_coords=False):
|
859
891
|
lang = get_ocr_language()
|
860
892
|
if lang != self.initial_lang:
|
861
893
|
self.initial_lang = lang
|
862
|
-
self.get_regex(lang)
|
894
|
+
self.regex = get_regex(lang)
|
863
895
|
img, is_path = input_to_pil_image(img)
|
864
896
|
if img.width < 51 or img.height < 51:
|
865
897
|
new_width = max(img.width, 51)
|
@@ -873,20 +905,18 @@ class OneOCR:
|
|
873
905
|
if sys.platform == 'win32':
|
874
906
|
try:
|
875
907
|
ocr_resp = self.model.recognize_pil(img)
|
908
|
+
if os.path.exists(os.path.expanduser("~/GSM/temp")):
|
909
|
+
with open(os.path.join(os.path.expanduser("~/GSM/temp"), 'oneocr_response.json'), 'w',
|
910
|
+
encoding='utf-8') as f:
|
911
|
+
json.dump(ocr_resp, f, indent=4, ensure_ascii=False)
|
876
912
|
# print(json.dumps(ocr_resp))
|
877
913
|
filtered_lines = [line for line in ocr_resp['lines'] if self.regex.search(line['text'])]
|
878
|
-
|
879
|
-
y_coords = [line['bounding_rect'][f'y{i}'] for line in filtered_lines for i in range(1, 5)]
|
880
|
-
if x_coords and y_coords:
|
881
|
-
crop_coords = (min(x_coords) - 5, min(y_coords) - 5, max(x_coords) + 5, max(y_coords) + 5)
|
882
|
-
|
883
|
-
# with open(os.path.join(get_temporary_directory(), 'oneocr_response.json'), 'w',
|
884
|
-
# encoding='utf-8') as f:
|
885
|
-
# json.dump(ocr_resp, f, indent=4, ensure_ascii=False)
|
914
|
+
# logger.info(filtered_lines)
|
886
915
|
res = ''
|
887
916
|
skipped = []
|
917
|
+
boxes = []
|
888
918
|
if furigana_filter_sensitivity > 0:
|
889
|
-
for line in
|
919
|
+
for line in filtered_lines:
|
890
920
|
x1, x2, x3, x4 = line['bounding_rect']['x1'], line['bounding_rect']['x2'], \
|
891
921
|
line['bounding_rect']['x3'], line['bounding_rect']['x4']
|
892
922
|
y1, y2, y3, y4 = line['bounding_rect']['y1'], line['bounding_rect']['y2'], \
|
@@ -934,8 +964,46 @@ class OneOCR:
|
|
934
964
|
# else:
|
935
965
|
# continue
|
936
966
|
# 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
|
+
elif return_coords:
|
992
|
+
for line in filtered_lines:
|
993
|
+
for word in line['words']:
|
994
|
+
box = {
|
995
|
+
"text": word['text'],
|
996
|
+
"bounding_rect": word['bounding_rect']
|
997
|
+
}
|
998
|
+
boxes.append(box)
|
999
|
+
res = ocr_resp['text']
|
937
1000
|
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)
|
938
1005
|
res = ocr_resp['text']
|
1006
|
+
|
939
1007
|
except RuntimeError as e:
|
940
1008
|
return (False, e)
|
941
1009
|
else:
|
@@ -950,10 +1018,12 @@ class OneOCR:
|
|
950
1018
|
return (False, 'Unknown error!')
|
951
1019
|
|
952
1020
|
res = res.json()['text']
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
1021
|
+
if return_coords:
|
1022
|
+
x = (True, res, boxes)
|
1023
|
+
else:
|
1024
|
+
x = (True, res, crop_coords)
|
1025
|
+
if is_path:
|
1026
|
+
img.close()
|
957
1027
|
return x
|
958
1028
|
|
959
1029
|
def _preprocess(self, img):
|
@@ -384,7 +384,6 @@ class TextFiltering:
|
|
384
384
|
block_filtered = self.latin_extended_regex.findall(block)
|
385
385
|
else:
|
386
386
|
block_filtered = self.latin_extended_regex.findall(block)
|
387
|
-
|
388
387
|
if block_filtered:
|
389
388
|
orig_text_filtered.append(''.join(block_filtered))
|
390
389
|
else:
|
@@ -548,6 +547,39 @@ class ScreenshotThread(threading.Thread):
|
|
548
547
|
else:
|
549
548
|
raise ValueError('Window capture is only currently supported on Windows and macOS')
|
550
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
|
+
|
551
583
|
def get_windows_window_handle(self, window_title):
|
552
584
|
def callback(hwnd, window_title_part):
|
553
585
|
window_title = win32gui.GetWindowText(hwnd)
|
@@ -570,7 +602,7 @@ class ScreenshotThread(threading.Thread):
|
|
570
602
|
|
571
603
|
def windows_window_tracker(self):
|
572
604
|
found = True
|
573
|
-
while not terminated:
|
605
|
+
while not terminated or window_open:
|
574
606
|
found = win32gui.IsWindow(self.window_handle)
|
575
607
|
if not found:
|
576
608
|
break
|
@@ -1086,10 +1118,11 @@ def signal_handler(sig, frame):
|
|
1086
1118
|
|
1087
1119
|
|
1088
1120
|
def on_window_closed(alive):
|
1089
|
-
global terminated
|
1121
|
+
global terminated, window_open
|
1090
1122
|
if not (alive or terminated):
|
1091
1123
|
logger.info('Window closed or error occurred, terminated!')
|
1092
|
-
|
1124
|
+
window_open = False
|
1125
|
+
# terminated = True
|
1093
1126
|
|
1094
1127
|
|
1095
1128
|
def on_screenshot_combo():
|
@@ -282,7 +282,7 @@ class CommonLanguages(str, Enum):
|
|
282
282
|
Raises ValueError if not found.
|
283
283
|
"""
|
284
284
|
try:
|
285
|
-
return cls[name]
|
285
|
+
return cls[name.upper()]
|
286
286
|
except KeyError:
|
287
287
|
raise ValueError(f"Language '{name}' not found in CommonLanguages")
|
288
288
|
|
@@ -527,7 +527,7 @@ class Ai:
|
|
527
527
|
@dataclass_json
|
528
528
|
@dataclass
|
529
529
|
class WIP:
|
530
|
-
overlay_websocket_port: int =
|
530
|
+
overlay_websocket_port: int = 55499
|
531
531
|
overlay_websocket_send: bool = False
|
532
532
|
|
533
533
|
|
@@ -549,7 +549,14 @@ class ProfileConfig:
|
|
549
549
|
advanced: Advanced = field(default_factory=Advanced)
|
550
550
|
ai: Ai = field(default_factory=Ai)
|
551
551
|
wip: WIP = field(default_factory=WIP)
|
552
|
-
|
552
|
+
|
553
|
+
|
554
|
+
def get_field_value(self, section: str, field_name: str):
|
555
|
+
section_obj = getattr(self, section, None)
|
556
|
+
if section_obj and hasattr(section_obj, field_name):
|
557
|
+
return getattr(section_obj, field_name)
|
558
|
+
else:
|
559
|
+
raise ValueError(f"Field '{field_name}' not found in section '{section}' of ProfileConfig.")
|
553
560
|
|
554
561
|
# This is just for legacy support
|
555
562
|
def load_from_toml(self, file_path: str):
|
@@ -992,4 +999,7 @@ anki_results = {}
|
|
992
999
|
gsm_state = GsmAppState()
|
993
1000
|
is_dev = is_running_from_source()
|
994
1001
|
|
1002
|
+
is_beangate = os.path.exists("C:/Users/Beangate")
|
1003
|
+
|
995
1004
|
logger.debug(f"Running in development mode: {is_dev}")
|
1005
|
+
logger.debug(f"Running on Beangate's PC: {is_beangate}")
|
@@ -113,8 +113,8 @@ def similar(a, b):
|
|
113
113
|
|
114
114
|
|
115
115
|
def lines_match(texthooker_sentence, anki_sentence):
|
116
|
-
texthooker_sentence = texthooker_sentence.replace("\n", "").replace("\r", "").strip()
|
117
|
-
anki_sentence = anki_sentence.replace("\n", "").replace("\r", "").strip()
|
116
|
+
texthooker_sentence = texthooker_sentence.replace("\n", "").replace("\r", "").replace(' ', '').strip()
|
117
|
+
anki_sentence = anki_sentence.replace("\n", "").replace("\r", "").replace(' ', '').strip()
|
118
118
|
similarity = similar(texthooker_sentence, anki_sentence)
|
119
119
|
if texthooker_sentence in anki_sentence:
|
120
120
|
logger.debug(f"One contains the other: {texthooker_sentence} in {anki_sentence} - Similarity: {similarity}")
|
@@ -389,19 +389,18 @@ def start_web_server():
|
|
389
389
|
app.run(host='0.0.0.0', port=port, debug=False) # debug=True provides helpful error messages during development
|
390
390
|
|
391
391
|
|
392
|
-
websocket_server_thread = None
|
393
392
|
websocket_queue = queue.Queue()
|
394
393
|
paused = False
|
395
394
|
|
396
395
|
|
397
396
|
class WebsocketServerThread(threading.Thread):
|
398
|
-
def __init__(self, read,
|
397
|
+
def __init__(self, read, get_ws_port_func):
|
399
398
|
super().__init__(daemon=True)
|
400
399
|
self._loop = None
|
401
400
|
self.read = read
|
402
401
|
self.clients = set()
|
403
402
|
self._event = threading.Event()
|
404
|
-
self.
|
403
|
+
self.get_ws_port_func = get_ws_port_func
|
405
404
|
self.backedup_text = []
|
406
405
|
|
407
406
|
@property
|
@@ -446,6 +445,9 @@ class WebsocketServerThread(threading.Thread):
|
|
446
445
|
text = json.dumps(text)
|
447
446
|
return asyncio.run_coroutine_threadsafe(
|
448
447
|
self.send_text_coroutine(text), self.loop)
|
448
|
+
|
449
|
+
def has_clients(self):
|
450
|
+
return len(self.clients) > 0
|
449
451
|
|
450
452
|
def stop_server(self):
|
451
453
|
self.loop.call_soon_threadsafe(self._stop_event.set)
|
@@ -459,7 +461,7 @@ class WebsocketServerThread(threading.Thread):
|
|
459
461
|
try:
|
460
462
|
self.server = start_server = websockets.serve(self.server_handler,
|
461
463
|
"0.0.0.0",
|
462
|
-
self.
|
464
|
+
self.get_ws_port_func(),
|
463
465
|
max_size=1000000000)
|
464
466
|
async with start_server:
|
465
467
|
await stop_event.wait()
|
@@ -474,6 +476,16 @@ def handle_exit_signal(loop):
|
|
474
476
|
logger.info("Received exit signal. Shutting down...")
|
475
477
|
for task in asyncio.all_tasks(loop):
|
476
478
|
task.cancel()
|
479
|
+
|
480
|
+
websocket_server_thread = WebsocketServerThread(read=True, get_ws_port_func=lambda : get_config().get_field_value('advanced', 'texthooker_communication_websocket_port'))
|
481
|
+
websocket_server_thread.start()
|
482
|
+
|
483
|
+
if get_config().advanced.plaintext_websocket_port:
|
484
|
+
plaintext_websocket_server_thread = WebsocketServerThread(read=False, get_ws_port_func=lambda : get_config().get_field_value('advanced', 'plaintext_websocket_port'))
|
485
|
+
plaintext_websocket_server_thread.start()
|
486
|
+
|
487
|
+
overlay_server_thread = WebsocketServerThread(read=False, get_ws_port_func=lambda : get_config().get_field_value('wip', 'overlay_websocket_port'))
|
488
|
+
overlay_server_thread.start()
|
477
489
|
|
478
490
|
async def texthooker_page_coro():
|
479
491
|
global websocket_server_thread, plaintext_websocket_server_thread, overlay_server_thread
|
@@ -482,16 +494,6 @@ async def texthooker_page_coro():
|
|
482
494
|
flask_thread.daemon = True
|
483
495
|
flask_thread.start()
|
484
496
|
|
485
|
-
websocket_server_thread = WebsocketServerThread(read=True, ws_port=get_config().advanced.texthooker_communication_websocket_port)
|
486
|
-
websocket_server_thread.start()
|
487
|
-
|
488
|
-
if get_config().advanced.plaintext_websocket_port:
|
489
|
-
plaintext_websocket_server_thread = WebsocketServerThread(read=False, ws_port=get_config().advanced.plaintext_websocket_port)
|
490
|
-
plaintext_websocket_server_thread.start()
|
491
|
-
|
492
|
-
overlay_server_thread = WebsocketServerThread(read=False, ws_port=get_config().wip.overlay_websocket_port)
|
493
|
-
overlay_server_thread.start()
|
494
|
-
|
495
497
|
# Keep the main asyncio event loop running (for the WebSocket server)
|
496
498
|
|
497
499
|
def run_text_hooker_page():
|
@@ -2,7 +2,6 @@ import asyncio
|
|
2
2
|
import io
|
3
3
|
import base64
|
4
4
|
from PIL import Image
|
5
|
-
import obsws_python as obs
|
6
5
|
|
7
6
|
from GameSentenceMiner.owocr.owocr.ocr import GoogleLens, OneOCR
|
8
7
|
from GameSentenceMiner.obs import *
|
@@ -18,13 +17,77 @@ HEIGHT = 1440
|
|
18
17
|
oneocr = OneOCR()
|
19
18
|
lens = GoogleLens()
|
20
19
|
|
20
|
+
def correct_ocr_text(detected_text: str, reference_text: str) -> str:
|
21
|
+
"""
|
22
|
+
Correct OCR text by comparing character-by-character with reference text.
|
23
|
+
When mismatches are found, look for subsequent matches and correct previous mismatches.
|
24
|
+
"""
|
25
|
+
if not detected_text or not reference_text:
|
26
|
+
return detected_text
|
27
|
+
|
28
|
+
detected_chars = list(detected_text)
|
29
|
+
reference_chars = list(reference_text)
|
30
|
+
|
31
|
+
# Track positions where mismatches occurred
|
32
|
+
mismatched_positions = []
|
33
|
+
|
34
|
+
min_length = min(len(detected_chars), len(reference_chars))
|
35
|
+
|
36
|
+
for i in range(min_length):
|
37
|
+
if detected_chars[i] != reference_chars[i]:
|
38
|
+
mismatched_positions.append(i)
|
39
|
+
logger.info(f"Mismatch at position {i}: detected '{detected_chars[i]}' vs reference '{reference_chars[i]}'")
|
40
|
+
else:
|
41
|
+
# We found a match - if we have previous mismatches, correct the most recent one
|
42
|
+
if mismatched_positions:
|
43
|
+
# Correct the most recent mismatch (simple 1-for-1 strategy)
|
44
|
+
last_mismatch_pos = mismatched_positions.pop()
|
45
|
+
old_char = detected_chars[last_mismatch_pos]
|
46
|
+
detected_chars[last_mismatch_pos] = reference_chars[last_mismatch_pos]
|
47
|
+
logger.info(f"Corrected position {last_mismatch_pos}: '{old_char}' -> '{reference_chars[last_mismatch_pos]}'")
|
48
|
+
|
49
|
+
corrected_text = ''.join(detected_chars)
|
50
|
+
return corrected_text
|
51
|
+
|
52
|
+
def redistribute_corrected_text(original_boxes: list, original_text: str, corrected_text: str) -> list:
|
53
|
+
"""
|
54
|
+
Redistribute corrected text back to the original text boxes while maintaining their positions.
|
55
|
+
"""
|
56
|
+
if original_text == corrected_text:
|
57
|
+
return original_boxes
|
58
|
+
|
59
|
+
corrected_boxes = []
|
60
|
+
text_position = 0
|
61
|
+
|
62
|
+
for box in original_boxes:
|
63
|
+
original_word = box['text']
|
64
|
+
word_length = len(original_word)
|
65
|
+
|
66
|
+
# Extract the corrected portion for this box
|
67
|
+
if text_position + word_length <= len(corrected_text):
|
68
|
+
corrected_word = corrected_text[text_position:text_position + word_length]
|
69
|
+
else:
|
70
|
+
# Handle case where corrected text is shorter
|
71
|
+
corrected_word = corrected_text[text_position:] if text_position < len(corrected_text) else ""
|
72
|
+
|
73
|
+
# Create a new box with corrected text but same coordinates
|
74
|
+
corrected_box = box.copy()
|
75
|
+
corrected_box['text'] = corrected_word
|
76
|
+
corrected_boxes.append(corrected_box)
|
77
|
+
|
78
|
+
text_position += word_length
|
79
|
+
|
80
|
+
logger.info(f"Redistributed: '{original_word}' -> '{corrected_word}'")
|
81
|
+
|
82
|
+
return corrected_boxes
|
83
|
+
|
21
84
|
async def get_full_screenshot() -> Image.Image | None:
|
22
|
-
#
|
85
|
+
# logger.info(f"Attempting to connect to OBS WebSocket at ws://{OBS_HOST}:{OBS_PORT}")
|
23
86
|
# try:
|
24
87
|
# client = obs.ReqClient(host=OBS_HOST, port=OBS_PORT, password=OBS_PASSWORD, timeout=30)
|
25
|
-
#
|
88
|
+
# logger.info("Connected to OBS WebSocket.")
|
26
89
|
# except Exception as e:
|
27
|
-
#
|
90
|
+
# logger.info(f"Failed to connect to OBS: {e}")
|
28
91
|
# return None
|
29
92
|
#
|
30
93
|
# try:
|
@@ -37,10 +100,10 @@ async def get_full_screenshot() -> Image.Image | None:
|
|
37
100
|
# )
|
38
101
|
#
|
39
102
|
# if not response.image_data:
|
40
|
-
#
|
103
|
+
# logger.info("Failed to get screenshot data from OBS.")
|
41
104
|
# return None
|
42
105
|
|
43
|
-
|
106
|
+
logger.info("Getting Screenshot from OBS")
|
44
107
|
try:
|
45
108
|
update_current_game()
|
46
109
|
start_time = time.time()
|
@@ -48,48 +111,131 @@ async def get_full_screenshot() -> Image.Image | None:
|
|
48
111
|
image_data = base64.b64decode(image_data)
|
49
112
|
img = Image.open(io.BytesIO(image_data)).convert("RGBA").resize((WIDTH, HEIGHT), Image.Resampling.LANCZOS)
|
50
113
|
# img.show()
|
51
|
-
|
114
|
+
logger.info(f"Screenshot captured in {time.time() - start_time:.2f} seconds.")
|
52
115
|
|
53
116
|
return img
|
54
117
|
|
55
118
|
except Exception as e:
|
56
|
-
|
119
|
+
logger.info(f"An unexpected error occurred during screenshot capture: {e}")
|
57
120
|
return None
|
58
|
-
|
59
|
-
async def
|
121
|
+
|
122
|
+
async def do_work(sentence_to_check=None):
|
60
123
|
# connect_to_obs_sync(5)
|
61
|
-
|
124
|
+
logger.info("in find_box")
|
62
125
|
# await asyncio.sleep(.5)
|
63
|
-
|
126
|
+
logger.info("after_initial_sleep")
|
64
127
|
full_screenshot_image = await get_full_screenshot()
|
128
|
+
if os.path.exists("C:\\Users\\Beangate\\GSM\\temp"):
|
129
|
+
full_screenshot_image.save("C:\\Users\\Beangate\\GSM\\temp\\full_screenshot.png")
|
130
|
+
# full_screenshot_image.show()
|
65
131
|
if full_screenshot_image:
|
66
|
-
|
67
|
-
ocr_results =
|
132
|
+
logger.info("Full screenshot captured successfully. Now performing local OCR...")
|
133
|
+
ocr_results = oneocr(full_screenshot_image, return_coords=True)
|
134
|
+
|
135
|
+
boxes_of_text = ocr_results[2]
|
136
|
+
# logger.info(f"Boxes of text found: {boxes_of_text}")
|
137
|
+
|
138
|
+
words = []
|
139
|
+
|
140
|
+
# If we have a reference sentence, perform character-by-character correction
|
141
|
+
if sentence_to_check:
|
142
|
+
# Concatenate all OCR text to form the detected sentence
|
143
|
+
detected_sentence = ''.join([box['text'] for box in boxes_of_text])
|
144
|
+
logger.info(f"Original detected sentence: '{detected_sentence}'")
|
145
|
+
logger.info(f"Reference sentence: '{sentence_to_check}'")
|
146
|
+
|
147
|
+
# Perform character-by-character comparison and correction
|
148
|
+
corrected_sentence = correct_ocr_text(detected_sentence, sentence_to_check)
|
149
|
+
logger.info(f"Corrected sentence: '{corrected_sentence}'")
|
150
|
+
|
151
|
+
# Redistribute corrected text back to boxes while maintaining positions
|
152
|
+
corrected_boxes = redistribute_corrected_text(boxes_of_text, detected_sentence, corrected_sentence)
|
153
|
+
else:
|
154
|
+
corrected_boxes = boxes_of_text
|
155
|
+
|
156
|
+
sentence_position = 0
|
157
|
+
for box in corrected_boxes:
|
158
|
+
word = box['text']
|
159
|
+
# logger.info(f"Box: {box}")
|
160
|
+
x1, y1 = box['bounding_rect']['x1'], box['bounding_rect']['y1']
|
161
|
+
x2, y2 = box['bounding_rect']['x3'], box['bounding_rect']['y3']
|
162
|
+
words.append({
|
163
|
+
"x1": x1,
|
164
|
+
"y1": y1,
|
165
|
+
"x2": x2,
|
166
|
+
"y2": y2,
|
167
|
+
"word": box['text']
|
168
|
+
})
|
169
|
+
|
170
|
+
# logger.info(f"Returning words: {words}")
|
171
|
+
|
172
|
+
ret = [
|
173
|
+
{
|
174
|
+
"words": words,
|
175
|
+
}
|
176
|
+
]
|
177
|
+
# cropped_sections = []
|
178
|
+
# for box in boxes_of_text:
|
179
|
+
# # Ensure crop coordinates are within image bounds
|
180
|
+
# left = max(0, box['bounding_rect']['x1'])
|
181
|
+
# top = max(0, box['bounding_rect']['y1'])
|
182
|
+
# right = min(full_screenshot_image.width, box['bounding_rect']['x3'])
|
183
|
+
# bottom = min(full_screenshot_image.height, box['bounding_rect']['y3'])
|
184
|
+
# cropped_sections.append(full_screenshot_image.crop((left, top, right, bottom)))
|
185
|
+
|
186
|
+
# if len(cropped_sections) > 1:
|
187
|
+
# # Create a transparent image with the same size as the full screenshot
|
188
|
+
# combined_img = Image.new("RGBA", (full_screenshot_image.width, full_screenshot_image.height), (0, 0, 0, 0))
|
189
|
+
|
190
|
+
# combined_img.show()
|
191
|
+
|
192
|
+
# # Paste each cropped section at its original coordinates
|
193
|
+
# for box, section in zip(boxes_of_text, cropped_sections):
|
194
|
+
# left = max(0, box['bounding_rect']['x1'])
|
195
|
+
# top = max(0, box['bounding_rect']['y1'])
|
196
|
+
# combined_img.paste(section, (left, top))
|
197
|
+
|
198
|
+
# new_image = combined_img
|
199
|
+
# elif cropped_sections:
|
200
|
+
# new_image = cropped_sections[0]
|
201
|
+
# else:
|
202
|
+
# new_image = Image.new("RGBA", full_screenshot_image.size)
|
203
|
+
|
204
|
+
# new_image.show()
|
205
|
+
# ocr_results = lens(new_image, return_coords=True)
|
68
206
|
# ocr_results = oneocr(full_screenshot_image, sentence_to_check=sentence_to_check)
|
69
|
-
|
70
|
-
|
207
|
+
# logger.info("\n--- OCR Results ---")
|
208
|
+
# logger.info(ocr_results)
|
71
209
|
|
72
|
-
return
|
210
|
+
return ret, 48
|
73
211
|
# from PIL import ImageDraw
|
74
212
|
# draw = ImageDraw.Draw(full_screenshot_image)
|
75
213
|
# draw.rectangle([x1, y1, x2, y2], outline="red", width=3)
|
76
214
|
# full_screenshot_image.save("full_screenshot_with_ocr.png")
|
77
215
|
# full_screenshot_image.show()
|
78
216
|
#
|
79
|
-
#
|
217
|
+
# logger.info(ocr_results)
|
80
218
|
# if ocr_results:
|
81
219
|
# for i, result in enumerate(ocr_results):
|
82
|
-
#
|
220
|
+
# logger.info(f"Result {i + 1}:\n{result}\n")
|
83
221
|
# else:
|
84
|
-
#
|
222
|
+
# logger.info("No OCR results found.")
|
85
223
|
else:
|
86
|
-
|
224
|
+
logger.info("Failed to get full screenshot for OCR.")
|
225
|
+
|
226
|
+
async def find_box_for_sentence(sentence_to_check):
|
227
|
+
try:
|
228
|
+
return await do_work(sentence_to_check=sentence_to_check)
|
229
|
+
except Exception as e:
|
230
|
+
logger.info(f"Error in find_box_for_sentence: {e}", exc_info=True)
|
231
|
+
return [], 48
|
87
232
|
|
88
233
|
async def main():
|
234
|
+
connect_to_obs_sync(5)
|
89
235
|
await find_box_for_sentence("はじめから")
|
90
236
|
|
91
237
|
if __name__ == '__main__':
|
92
238
|
try:
|
93
239
|
asyncio.run(main())
|
94
240
|
except KeyboardInterrupt:
|
95
|
-
|
241
|
+
logger.info("Script terminated by user.")
|
@@ -1,8 +1,8 @@
|
|
1
1
|
GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
GameSentenceMiner/anki.py,sha256=
|
3
|
-
GameSentenceMiner/config_gui.py,sha256=
|
4
|
-
GameSentenceMiner/gametext.py,sha256=
|
5
|
-
GameSentenceMiner/gsm.py,sha256=
|
2
|
+
GameSentenceMiner/anki.py,sha256=VC87YMX_XiigB-Pqsz5y1zMh7DLmPAyhXeryAhmI1nI,19101
|
3
|
+
GameSentenceMiner/config_gui.py,sha256=bK7mHLxDIXhghOJqL0VphgUOsEAPyI3mjl0uWtR4mPk,103249
|
4
|
+
GameSentenceMiner/gametext.py,sha256=fNkz1dvvJCLQ1AD6NuAJhSiGUjG-OSNGzwQOGv8anGo,7776
|
5
|
+
GameSentenceMiner/gsm.py,sha256=MO2ekb3C8vWDflrslcgWRMQIpoqIpzn2pqg_3NDkCgQ,25447
|
6
6
|
GameSentenceMiner/obs.py,sha256=-5j4k1_sYYR1Lnbn9C-_yN9prqgGLICgx5l3uguv4xk,15917
|
7
7
|
GameSentenceMiner/vad.py,sha256=zo9JpuEOCXczPXM-dq8lbr-zM-MPpfJ8aajggR3mKk4,18710
|
8
8
|
GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -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=6ArGr0xd-Fhkw9uPn4MH3urxbLBwZ-UmxfwoKUUgxio,63459
|
29
|
+
GameSentenceMiner/owocr/owocr/run.py,sha256=nkDpXICJCTKgJTS4MYRnaz-GYqAS-GskcSg1ZkGIRuE,67285
|
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=XUKA4-XFCk4tEjqj9GtB-Yq9OgBRrILXlcthRkLQpP8,35959
|
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
|
@@ -38,7 +38,7 @@ GameSentenceMiner/util/model.py,sha256=hmA_seopP2bK40v9T4ulua9TrAeWtbkdCv-sTBPBQ
|
|
38
38
|
GameSentenceMiner/util/notification.py,sha256=0OnEYjn3DUEZ6c6OtPjdVZe-DG-QSoMAl9fetjjCvNU,3874
|
39
39
|
GameSentenceMiner/util/package.py,sha256=u1ym5z869lw5EHvIviC9h9uH97bzUXSXXA8KIn8rUvk,1157
|
40
40
|
GameSentenceMiner/util/ss_selector.py,sha256=cbjMxiKOCuOfbRvLR_PCRlykBrGtm1LXd6u5czPqkmc,4793
|
41
|
-
GameSentenceMiner/util/text_log.py,sha256=
|
41
|
+
GameSentenceMiner/util/text_log.py,sha256=Gbm8H2DHUX4u0QBnbjs3jOfzkS6_5WBczgD3mwant7Q,6031
|
42
42
|
GameSentenceMiner/util/window_transparency.py,sha256=hmeQYqK3mUEh47hZ8pODldUbxCC5eluMddanXfC_epQ,7325
|
43
43
|
GameSentenceMiner/util/communication/__init__.py,sha256=xh__yn2MhzXi9eLi89PeZWlJPn-cbBSjskhi1BRraXg,643
|
44
44
|
GameSentenceMiner/util/communication/send.py,sha256=Wki9qIY2CgYnuHbmnyKVIYkcKAN_oYS4up93XMikBaI,222
|
@@ -49,7 +49,7 @@ GameSentenceMiner/util/downloader/download_tools.py,sha256=zR-aEHiFVkyo-9oPoSx6n
|
|
49
49
|
GameSentenceMiner/util/downloader/oneocr_dl.py,sha256=EJbKISaZ9p2x9P4x0rpMM5nAInTTc9b7arraGBcd-SA,10381
|
50
50
|
GameSentenceMiner/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
51
51
|
GameSentenceMiner/web/service.py,sha256=S7bYf2kSk08u-8R9Qpv7piM-pxfFjYZUvU825xupmuI,5279
|
52
|
-
GameSentenceMiner/web/texthooking_page.py,sha256=
|
52
|
+
GameSentenceMiner/web/texthooking_page.py,sha256=2ZS89CAI17xVkx64rGmHHbF96eKR8gPWiR_WAoDJ0Mw,17399
|
53
53
|
GameSentenceMiner/web/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
54
54
|
GameSentenceMiner/web/static/apple-touch-icon.png,sha256=OcMI8af_68DA_tweOsQ5LytTyMwm7-hPW07IfrOVgEs,46132
|
55
55
|
GameSentenceMiner/web/static/favicon-96x96.png,sha256=lOePzjiKl1JY2J1kT_PMdyEnrlJmi5GWbmXJunM12B4,16502
|
@@ -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.0.
|
68
|
-
gamesentenceminer-2.12.0.
|
69
|
-
gamesentenceminer-2.12.0.
|
70
|
-
gamesentenceminer-2.12.0.
|
71
|
-
gamesentenceminer-2.12.0.
|
72
|
-
gamesentenceminer-2.12.0.
|
66
|
+
GameSentenceMiner/wip/get_overlay_coords.py,sha256=dzP8EbexXuRnSEP4lWUzyMn24B9UQkAGA6zbxGS_W2M,9506
|
67
|
+
gamesentenceminer-2.12.0.dev6.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
68
|
+
gamesentenceminer-2.12.0.dev6.dist-info/METADATA,sha256=DvW1QUhxzRIx2pQdp8JLrV7vPohTFPYd7pnM7INR27o,7004
|
69
|
+
gamesentenceminer-2.12.0.dev6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
70
|
+
gamesentenceminer-2.12.0.dev6.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
71
|
+
gamesentenceminer-2.12.0.dev6.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
72
|
+
gamesentenceminer-2.12.0.dev6.dist-info/RECORD,,
|
File without changes
|
{gamesentenceminer-2.12.0.dev5.dist-info → gamesentenceminer-2.12.0.dev6.dist-info}/entry_points.txt
RENAMED
File without changes
|
{gamesentenceminer-2.12.0.dev5.dist-info → gamesentenceminer-2.12.0.dev6.dist-info}/licenses/LICENSE
RENAMED
File without changes
|
{gamesentenceminer-2.12.0.dev5.dist-info → gamesentenceminer-2.12.0.dev6.dist-info}/top_level.txt
RENAMED
File without changes
|