GameSentenceMiner 2.11.7__py3-none-any.whl → 2.12.0.dev1__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/gametext.py +16 -1
- GameSentenceMiner/obs.py +11 -3
- GameSentenceMiner/ocr/owocr_helper.py +1 -1
- GameSentenceMiner/owocr/owocr/ocr.py +102 -43
- GameSentenceMiner/owocr/owocr/run.py +37 -4
- GameSentenceMiner/util/configuration.py +0 -1
- GameSentenceMiner/vad.py +1 -1
- GameSentenceMiner/web/texthooking_page.py +8 -3
- {gamesentenceminer-2.11.7.dist-info → gamesentenceminer-2.12.0.dev1.dist-info}/METADATA +8 -13
- {gamesentenceminer-2.11.7.dist-info → gamesentenceminer-2.12.0.dev1.dist-info}/RECORD +14 -14
- {gamesentenceminer-2.11.7.dist-info → gamesentenceminer-2.12.0.dev1.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.11.7.dist-info → gamesentenceminer-2.12.0.dev1.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.11.7.dist-info → gamesentenceminer-2.12.0.dev1.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.11.7.dist-info → gamesentenceminer-2.12.0.dev1.dist-info}/top_level.txt +0 -0
GameSentenceMiner/gametext.py
CHANGED
@@ -2,6 +2,7 @@ import asyncio
|
|
2
2
|
import re
|
3
3
|
|
4
4
|
import pyperclip
|
5
|
+
import requests
|
5
6
|
import websockets
|
6
7
|
from websockets import InvalidStatus
|
7
8
|
|
@@ -9,6 +10,7 @@ from GameSentenceMiner.util.gsm_utils import do_text_replacements, TEXT_REPLACEM
|
|
9
10
|
from GameSentenceMiner.util.configuration import *
|
10
11
|
from GameSentenceMiner.util.text_log import *
|
11
12
|
from GameSentenceMiner.web.texthooking_page import add_event_to_texthooker
|
13
|
+
from GameSentenceMiner.wip import get_overlay_coords
|
12
14
|
|
13
15
|
current_line = ''
|
14
16
|
current_line_after_regex = ''
|
@@ -121,8 +123,21 @@ async def handle_new_text_event(current_clipboard, line_time=None):
|
|
121
123
|
current_line_time = line_time if line_time else datetime.now()
|
122
124
|
gsm_status.last_line_received = current_line_time.strftime("%Y-%m-%d %H:%M:%S")
|
123
125
|
add_line(current_line_after_regex, line_time)
|
126
|
+
boxes = await find_box_for_sentence(current_line)
|
124
127
|
if len(get_text_log().values) > 0:
|
125
|
-
await add_event_to_texthooker(get_text_log()[-1])
|
128
|
+
await add_event_to_texthooker(get_text_log()[-1], boxes=boxes)
|
129
|
+
|
130
|
+
async def find_box_for_sentence(sentence):
|
131
|
+
boxes = []
|
132
|
+
logger.info(f"Finding Box for Sentence: {sentence}")
|
133
|
+
boxes, font_size = await get_overlay_coords.find_box_for_sentence(sentence)
|
134
|
+
logger.info(f"Found Boxes: {boxes}, Font Size: {font_size}")
|
135
|
+
# if boxes:
|
136
|
+
# x1, y1, x2, y2 = box
|
137
|
+
# boxes.append({'sentence': sentence, 'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2, 'fontSize': font_size})
|
138
|
+
# x1, y1, x2, y2 = box
|
139
|
+
# requests.post("http://localhost:3000/open-overlay", json={"sentence": sentence, "x1": x1, "y1": y1, "x2": x2, "y2": y2, "fontSize": font_size})
|
140
|
+
return boxes
|
126
141
|
|
127
142
|
def reset_line_hotkey_pressed():
|
128
143
|
global current_line_time
|
GameSentenceMiner/obs.py
CHANGED
@@ -3,6 +3,8 @@ import os.path
|
|
3
3
|
import subprocess
|
4
4
|
import threading
|
5
5
|
import time
|
6
|
+
from pprint import pprint
|
7
|
+
|
6
8
|
import psutil
|
7
9
|
|
8
10
|
import obsws_python as obs
|
@@ -356,9 +358,9 @@ def get_screenshot(compression=-1):
|
|
356
358
|
logger.error(f"Error getting screenshot: {e}")
|
357
359
|
return None
|
358
360
|
|
359
|
-
def get_screenshot_base64(compression=
|
361
|
+
def get_screenshot_base64(compression=75, width=None, height=None):
|
360
362
|
try:
|
361
|
-
|
363
|
+
update_current_game()
|
362
364
|
current_game = get_current_game()
|
363
365
|
if not current_game:
|
364
366
|
logger.error("No active game scene found.")
|
@@ -368,7 +370,11 @@ def get_screenshot_base64(compression=0, width=None, height=None):
|
|
368
370
|
if not current_source_name:
|
369
371
|
logger.error("No active source found in the current scene.")
|
370
372
|
return None
|
373
|
+
# version = client.send("GetVersion", raw=True)
|
374
|
+
# pprint(version)
|
375
|
+
# responseraw = client.send("GetSourceScreenshot", {"sourceName": current_source_name, "imageFormat": "png", "imageWidth": width, "imageHeight": height, "compressionQuality": compression}, raw=True)
|
371
376
|
response = client.get_source_screenshot(name=current_source_name, img_format='png', quality=compression, width=width, height=height)
|
377
|
+
# print(responseraw)
|
372
378
|
if response and response.image_data:
|
373
379
|
return response.image_data.split(',', 1)[-1] # Remove data:image/png;base64, prefix if present
|
374
380
|
else:
|
@@ -428,5 +434,7 @@ def main():
|
|
428
434
|
|
429
435
|
if __name__ == '__main__':
|
430
436
|
logging.basicConfig(level=logging.INFO)
|
431
|
-
main()
|
437
|
+
# main()
|
438
|
+
connect_to_obs_sync()
|
439
|
+
print(get_screenshot_base64(compression=75, width=1280, height=720))
|
432
440
|
|
@@ -195,7 +195,7 @@ all_cords = None
|
|
195
195
|
rectangles = None
|
196
196
|
last_ocr2_result = []
|
197
197
|
|
198
|
-
def do_second_ocr(ocr1_text, time, img, filtering, pre_crop_image, ignore_furigana_filter=False, ignore_previous_result=False):
|
198
|
+
def do_second_ocr(ocr1_text, time, img, filtering, pre_crop_image=None, ignore_furigana_filter=False, ignore_previous_result=False):
|
199
199
|
global twopassocr, ocr2, last_ocr2_result
|
200
200
|
try:
|
201
201
|
orig_text, text = run.process_and_write_results(img, None, last_ocr2_result if not ignore_previous_result else None, filtering, None,
|
@@ -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):
|
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)
|
@@ -879,7 +911,6 @@ class OneOCR:
|
|
879
911
|
y_coords = [line['bounding_rect'][f'y{i}'] for line in filtered_lines for i in range(1, 5)]
|
880
912
|
if x_coords and y_coords:
|
881
913
|
crop_coords = (min(x_coords) - 5, min(y_coords) - 5, max(x_coords) + 5, max(y_coords) + 5)
|
882
|
-
|
883
914
|
# with open(os.path.join(get_temporary_directory(), 'oneocr_response.json'), 'w',
|
884
915
|
# encoding='utf-8') as f:
|
885
916
|
# json.dump(ocr_resp, f, indent=4, ensure_ascii=False)
|
@@ -934,8 +965,37 @@ class OneOCR:
|
|
934
965
|
# else:
|
935
966
|
# continue
|
936
967
|
# res += '\n'
|
968
|
+
elif sentence_to_check:
|
969
|
+
lines_to_build_area = []
|
970
|
+
widths = []
|
971
|
+
heights = []
|
972
|
+
for line in ocr_resp['lines']:
|
973
|
+
print(line['text'])
|
974
|
+
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:
|
975
|
+
lines_to_build_area.append(line)
|
976
|
+
res += line['text']
|
977
|
+
for word in line['words']:
|
978
|
+
widths.append(word['bounding_rect']['x2'] - word['bounding_rect']['x1'])
|
979
|
+
heights.append(word['bounding_rect']['y3'] - word['bounding_rect']['y1'])
|
980
|
+
|
981
|
+
x_coords = [line['bounding_rect'][f'x{i}'] for line in lines_to_build_area for i in
|
982
|
+
range(1, 5)]
|
983
|
+
y_coords = [line['bounding_rect'][f'y{i}'] for line in lines_to_build_area for i in
|
984
|
+
range(1, 5)]
|
985
|
+
if widths:
|
986
|
+
avg_width = sum(widths) / len(widths)
|
987
|
+
if heights:
|
988
|
+
avg_height = sum(heights) / len(heights)
|
989
|
+
if x_coords and y_coords:
|
990
|
+
crop_coords = (
|
991
|
+
min(x_coords) - 5, min(y_coords) - 5, max(x_coords) + 5, max(y_coords) + 5)
|
937
992
|
else:
|
993
|
+
x_coords = [line['bounding_rect'][f'x{i}'] for line in ocr_resp['lines'] for i in range(1, 5)]
|
994
|
+
y_coords = [line['bounding_rect'][f'y{i}'] for line in ocr_resp['lines'] for i in range(1, 5)]
|
995
|
+
if x_coords and y_coords:
|
996
|
+
crop_coords = (min(x_coords) - 5, min(y_coords) - 5, max(x_coords) + 5, max(y_coords) + 5)
|
938
997
|
res = ocr_resp['text']
|
998
|
+
|
939
999
|
except RuntimeError as e:
|
940
1000
|
return (False, e)
|
941
1001
|
else:
|
@@ -950,10 +1010,9 @@ class OneOCR:
|
|
950
1010
|
return (False, 'Unknown error!')
|
951
1011
|
|
952
1012
|
res = res.json()['text']
|
953
|
-
|
954
1013
|
x = (True, res, crop_coords)
|
955
|
-
|
956
|
-
|
1014
|
+
if is_path:
|
1015
|
+
img.close()
|
957
1016
|
return x
|
958
1017
|
|
959
1018
|
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():
|
@@ -253,7 +253,6 @@ class Advanced:
|
|
253
253
|
multi_line_sentence_storage_field: str = ''
|
254
254
|
ocr_websocket_port: int = 9002
|
255
255
|
texthooker_communication_websocket_port: int = 55001
|
256
|
-
use_anki_note_creation_time: bool = True
|
257
256
|
|
258
257
|
def __post_init__(self):
|
259
258
|
if self.plaintext_websocket_port == -1:
|
GameSentenceMiner/vad.py
CHANGED
@@ -136,7 +136,7 @@ class VADProcessor(ABC):
|
|
136
136
|
if get_config().vad.cut_and_splice_segments:
|
137
137
|
self.extract_audio_and_combine_segments(input_audio, voice_activity, output_audio, padding=get_config().vad.splice_padding)
|
138
138
|
else:
|
139
|
-
ffmpeg.trim_audio(input_audio, start_time + get_config().vad.beginning_offset, end_time + get_config().audio.end_offset, output_audio, trim_beginning=get_config().vad.trim_beginning, fade_in_duration=0.05, fade_out_duration=
|
139
|
+
ffmpeg.trim_audio(input_audio, start_time + get_config().vad.beginning_offset, end_time + get_config().audio.end_offset, output_audio, trim_beginning=get_config().vad.trim_beginning, fade_in_duration=0.05, fade_out_duration=0)
|
140
140
|
return VADResult(True, start_time + get_config().vad.beginning_offset, end_time + get_config().audio.end_offset, self.vad_system_name, voice_activity, output_audio)
|
141
141
|
|
142
142
|
class SileroVADProcessor(VADProcessor):
|
@@ -259,7 +259,7 @@ def clear_history():
|
|
259
259
|
return jsonify({'message': 'History cleared successfully'}), 200
|
260
260
|
|
261
261
|
|
262
|
-
async def add_event_to_texthooker(line: GameLine):
|
262
|
+
async def add_event_to_texthooker(line: GameLine, boxes=None):
|
263
263
|
new_event = event_manager.add_gameline(line)
|
264
264
|
await websocket_server_thread.send_text({
|
265
265
|
'event': 'text_received',
|
@@ -268,6 +268,8 @@ async def add_event_to_texthooker(line: GameLine):
|
|
268
268
|
})
|
269
269
|
if get_config().advanced.plaintext_websocket_port:
|
270
270
|
await plaintext_websocket_server_thread.send_text(line.text)
|
271
|
+
if boxes and len(boxes) > 0 and overlay_server_thread:
|
272
|
+
await overlay_server_thread.send_text(boxes)
|
271
273
|
|
272
274
|
|
273
275
|
@app.route('/update_checkbox', methods=['POST'])
|
@@ -437,7 +439,7 @@ class WebsocketServerThread(threading.Thread):
|
|
437
439
|
|
438
440
|
async def send_text(self, text):
|
439
441
|
if text:
|
440
|
-
if isinstance(text, dict):
|
442
|
+
if isinstance(text, dict) or isinstance(text, list):
|
441
443
|
text = json.dumps(text)
|
442
444
|
return asyncio.run_coroutine_threadsafe(
|
443
445
|
self.send_text_coroutine(text), self.loop)
|
@@ -471,7 +473,7 @@ def handle_exit_signal(loop):
|
|
471
473
|
task.cancel()
|
472
474
|
|
473
475
|
async def texthooker_page_coro():
|
474
|
-
global websocket_server_thread, plaintext_websocket_server_thread
|
476
|
+
global websocket_server_thread, plaintext_websocket_server_thread, overlay_server_thread
|
475
477
|
# Run the WebSocket server in the asyncio event loop
|
476
478
|
flask_thread = threading.Thread(target=start_web_server)
|
477
479
|
flask_thread.daemon = True
|
@@ -483,6 +485,9 @@ async def texthooker_page_coro():
|
|
483
485
|
if get_config().advanced.plaintext_websocket_port:
|
484
486
|
plaintext_websocket_server_thread = WebsocketServerThread(read=False, ws_port=get_config().advanced.plaintext_websocket_port)
|
485
487
|
plaintext_websocket_server_thread.start()
|
488
|
+
|
489
|
+
overlay_server_thread = WebsocketServerThread(read=False, ws_port=49999)
|
490
|
+
overlay_server_thread.start()
|
486
491
|
|
487
492
|
# Keep the main asyncio event loop running (for the WebSocket server)
|
488
493
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: GameSentenceMiner
|
3
|
-
Version: 2.
|
4
|
-
Summary: A tool for mining sentences from games. Update:
|
3
|
+
Version: 2.12.0.dev1
|
4
|
+
Summary: A tool for mining sentences from games. Update: Overlay?
|
5
5
|
Author-email: Beangate <bpwhelan95@gmail.com>
|
6
6
|
License: MIT License
|
7
7
|
Project-URL: Homepage, https://github.com/bpwhelan/GameSentenceMiner
|
@@ -69,18 +69,15 @@ GSM significantly enhances your Anki cards with rich contextual information:
|
|
69
69
|
* **AI Translation**: Integrates AI to provide quick translations of the captured sentence. Custom Prompts also supported. (Optional, Bring your own Key)
|
70
70
|
|
71
71
|
|
72
|
-
#### Game Example
|
73
|
-

|
72
|
+
#### Game Example (Has Audio)
|
74
73
|
|
75
|
-
|
74
|
+
https://github.com/user-attachments/assets/df6bc38e-d74d-423e-b270-8a82eec2394c
|
76
75
|
|
77
76
|
---
|
78
77
|
|
79
|
-
#### VN Example
|
80
|
-

|
81
|
-
|
82
|
-
Audio: https://github.com/user-attachments/assets/2d7b3967-cd5c-4132-b489-75058ea20921
|
78
|
+
#### VN Example (Has Audio)
|
83
79
|
|
80
|
+
https://github.com/user-attachments/assets/ee670fda-1a8b-4dec-b9e6-072264155c6e
|
84
81
|
|
85
82
|
### OCR
|
86
83
|
|
@@ -97,10 +94,8 @@ GSM runs a fork of [OwOCR](https://github.com/AuroraWright/owocr/) to provide ac
|
|
97
94
|
* **More Language Support**: Stock OwOCR is hard-coded to Japanese, while in GSM you can use a variety of languages.
|
98
95
|
|
99
96
|
|
100
|
-
|
101
|
-

|
97
|
+
https://github.com/user-attachments/assets/07240472-831a-40e6-be22-c64b880b0d66
|
102
98
|
|
103
|
-
Audio: https://github.com/user-attachments/assets/8c44780a-9b74-41af-bf16-28a742f4de12
|
104
99
|
|
105
100
|
|
106
101
|
### Game Launcher Capabilities (WIP)
|
@@ -113,7 +108,7 @@ This is probably the feature I care least about, but if you are lazy like me, yo
|
|
113
108
|
|
114
109
|
This feature simplifies the process of launching games and (potentially) hooking them, making the entire workflow more efficient.
|
115
110
|
|
116
|
-
|
111
|
+
<img width="2560" height="1392" alt="GameSentenceMiner_1zuov0R9xK" src="https://github.com/user-attachments/assets/205769bb-3dd2-493b-9383-2d6e2ca05c2d" />
|
117
112
|
|
118
113
|
## Basic Requirements
|
119
114
|
|
@@ -1,10 +1,10 @@
|
|
1
1
|
GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
GameSentenceMiner/anki.py,sha256=3BVFXAM7tpJAxHMbsMpnMHUoDfyqHQ1JSYJThW18QWA,16846
|
3
3
|
GameSentenceMiner/config_gui.py,sha256=QTK1yBDcfHaIUR_JyekkRQY9CVI_rh3Cae0bi7lviIo,99198
|
4
|
-
GameSentenceMiner/gametext.py,sha256=
|
4
|
+
GameSentenceMiner/gametext.py,sha256=boj55Sf5spEHDKHh3uv5x3rzn-I1EuN8XZYXcqSiBF8,7503
|
5
5
|
GameSentenceMiner/gsm.py,sha256=qVHxnvly-yJ85v9RAxsGN2MqZxU-C1JA5wSRxVxMPMg,24950
|
6
|
-
GameSentenceMiner/obs.py,sha256
|
7
|
-
GameSentenceMiner/vad.py,sha256=
|
6
|
+
GameSentenceMiner/obs.py,sha256=-5j4k1_sYYR1Lnbn9C-_yN9prqgGLICgx5l3uguv4xk,15917
|
7
|
+
GameSentenceMiner/vad.py,sha256=zo9JpuEOCXczPXM-dq8lbr-zM-MPpfJ8aajggR3mKk4,18710
|
8
8
|
GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
9
|
GameSentenceMiner/ai/ai_prompting.py,sha256=ojp7i_xg2YB1zALgFbivwtXPMVkThnSbPoUiAs-nz_g,25892
|
10
10
|
GameSentenceMiner/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -19,18 +19,18 @@ GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
|
|
19
19
|
GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=Ezj-0k6Wo-una91FvYhMp6KGkRhWYihXzLAoh_Wu2xY,5329
|
20
20
|
GameSentenceMiner/ocr/ocrconfig.py,sha256=_tY8mjnzHMJrLS8E5pHqYXZjMuLoGKYgJwdhYgN-ny4,6466
|
21
21
|
GameSentenceMiner/ocr/owocr_area_selector.py,sha256=O8qKOTDglk-D4N-2_ORLeZacXT-OVOCNxUI8sQHAlx4,25538
|
22
|
-
GameSentenceMiner/ocr/owocr_helper.py,sha256=
|
22
|
+
GameSentenceMiner/ocr/owocr_helper.py,sha256=hP9j7bP9G_jwhRaWgbyVkzoPiNTmxuOTn0e1VQY9XMQ,25306
|
23
23
|
GameSentenceMiner/ocr/ss_picker.py,sha256=0IhxUdaKruFpZyBL-8SpxWg7bPrlGpy3lhTcMMZ5rwo,5224
|
24
24
|
GameSentenceMiner/owocr/owocr/__init__.py,sha256=87hfN5u_PbL_onLfMACbc0F5j4KyIK9lKnRCj6oZgR0,49
|
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=JB9dfln9FJIWd9WaFIuOykAuQ656OY6-UJj500r-VQk,63154
|
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=4VxVO_rOyhaSaZ9peIuJ0a_M04BfTfHMuACTWoRCJ9I,28954
|
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
|
@@ -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=su58fY2PoVzXgduNngU9oIYh71Xqf7KUPACfMmCkNPc,17128
|
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,9 +63,9 @@ 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-2.
|
67
|
-
gamesentenceminer-2.
|
68
|
-
gamesentenceminer-2.
|
69
|
-
gamesentenceminer-2.
|
70
|
-
gamesentenceminer-2.
|
71
|
-
gamesentenceminer-2.
|
66
|
+
gamesentenceminer-2.12.0.dev1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
67
|
+
gamesentenceminer-2.12.0.dev1.dist-info/METADATA,sha256=7NxPEhMcs6J3Towxx30gWBzIserkQkKijpzspWN3wdM,7004
|
68
|
+
gamesentenceminer-2.12.0.dev1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
69
|
+
gamesentenceminer-2.12.0.dev1.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
70
|
+
gamesentenceminer-2.12.0.dev1.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
71
|
+
gamesentenceminer-2.12.0.dev1.dist-info/RECORD,,
|
File without changes
|
{gamesentenceminer-2.11.7.dist-info → gamesentenceminer-2.12.0.dev1.dist-info}/entry_points.txt
RENAMED
File without changes
|
{gamesentenceminer-2.11.7.dist-info → gamesentenceminer-2.12.0.dev1.dist-info}/licenses/LICENSE
RENAMED
File without changes
|
{gamesentenceminer-2.11.7.dist-info → gamesentenceminer-2.12.0.dev1.dist-info}/top_level.txt
RENAMED
File without changes
|