GameSentenceMiner 2.11.8__tar.gz → 2.12.0.dev1__tar.gz

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.
Files changed (76) hide show
  1. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/gametext.py +16 -1
  2. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/obs.py +11 -3
  3. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/owocr/owocr/ocr.py +102 -43
  4. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/owocr/owocr/run.py +37 -4
  5. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/configuration.py +0 -1
  6. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/vad.py +1 -1
  7. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/web/texthooking_page.py +8 -3
  8. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner.egg-info/PKG-INFO +2 -2
  9. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/PKG-INFO +2 -2
  10. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/pyproject.toml +2 -2
  11. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/__init__.py +0 -0
  12. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/ai/__init__.py +0 -0
  13. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/ai/ai_prompting.py +0 -0
  14. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/anki.py +0 -0
  15. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/assets/__init__.py +0 -0
  16. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/assets/icon.png +0 -0
  17. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/assets/icon128.png +0 -0
  18. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/assets/icon256.png +0 -0
  19. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/assets/icon32.png +0 -0
  20. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/assets/icon512.png +0 -0
  21. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/assets/icon64.png +0 -0
  22. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/assets/pickaxe.png +0 -0
  23. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/config_gui.py +0 -0
  24. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/gsm.py +0 -0
  25. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/ocr/__init__.py +0 -0
  26. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/ocr/gsm_ocr_config.py +0 -0
  27. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/ocr/ocrconfig.py +0 -0
  28. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/ocr/owocr_area_selector.py +0 -0
  29. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/ocr/owocr_helper.py +0 -0
  30. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/ocr/ss_picker.py +0 -0
  31. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/owocr/owocr/__init__.py +0 -0
  32. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/owocr/owocr/__main__.py +0 -0
  33. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/owocr/owocr/config.py +0 -0
  34. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -0
  35. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -0
  36. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/__init__.py +0 -0
  37. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/audio_offset_selector.py +0 -0
  38. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/communication/__init__.py +0 -0
  39. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/communication/send.py +0 -0
  40. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/communication/websocket.py +0 -0
  41. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/downloader/Untitled_json.py +0 -0
  42. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/downloader/__init__.py +0 -0
  43. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/downloader/download_tools.py +0 -0
  44. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/downloader/oneocr_dl.py +0 -0
  45. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/electron_config.py +0 -0
  46. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/ffmpeg.py +0 -0
  47. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/gsm_utils.py +0 -0
  48. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/model.py +0 -0
  49. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/notification.py +0 -0
  50. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/package.py +0 -0
  51. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/ss_selector.py +0 -0
  52. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/text_log.py +0 -0
  53. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/util/window_transparency.py +0 -0
  54. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/web/__init__.py +0 -0
  55. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/web/service.py +0 -0
  56. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/web/static/__init__.py +0 -0
  57. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
  58. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/web/static/favicon-96x96.png +0 -0
  59. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/web/static/favicon.ico +0 -0
  60. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/web/static/favicon.svg +0 -0
  61. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/web/static/site.webmanifest +0 -0
  62. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/web/static/style.css +0 -0
  63. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
  64. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
  65. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/web/templates/__init__.py +0 -0
  66. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/web/templates/index.html +0 -0
  67. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/web/templates/text_replacements.html +0 -0
  68. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner/web/templates/utility.html +0 -0
  69. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner.egg-info/SOURCES.txt +0 -0
  70. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner.egg-info/dependency_links.txt +0 -0
  71. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner.egg-info/entry_points.txt +0 -0
  72. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner.egg-info/requires.txt +0 -0
  73. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/GameSentenceMiner.egg-info/top_level.txt +0 -0
  74. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/LICENSE +0 -0
  75. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/README.md +0 -0
  76. {gamesentenceminer-2.11.8 → gamesentenceminer-2.12.0.dev1}/setup.cfg +0 -0
@@ -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
@@ -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=0, width=None, height=None):
361
+ def get_screenshot_base64(compression=75, width=None, height=None):
360
362
  try:
361
- # update_current_game()
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
 
@@ -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.kana_kanji_regex = re.compile(r'[\u3041-\u3096\u30A1-\u30FA\u4E00-\u9FFF]')
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
- # with open(os.path.join(r"C:\Users\Beangate\GSM\Electron App\test", 'glens_response.json'), 'w', encoding='utf-8') as f:
313
- # json.dump(response_dict, f, indent=4, ensure_ascii=False)
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
- if furigana_filter_sensitivity < line['geometry']['bounding_box']['width'] * img.width and furigana_filter_sensitivity < line['geometry']['bounding_box']['height'] * img.height:
335
- for word in line['words']:
336
- res += word['plain_text'] + word['text_separator']
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.append(word['plain_text'] for word in line['words'])
389
+ skipped.extend([w['word'] for w in words_info])
339
390
  continue
340
391
  else:
341
- for word in line['words']:
342
- res += word['plain_text'] + word['text_separator']
343
- else:
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
- x = (True, res)
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 get_regex(self, lang):
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
- # img.close()
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
- terminated = True
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:
@@ -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=00)
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.11.8
4
- Summary: A tool for mining sentences from games. Update: Full UI Re-design
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
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.11.8
4
- Summary: A tool for mining sentences from games. Update: Full UI Re-design
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
@@ -7,8 +7,8 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "GameSentenceMiner"
10
- version = "2.11.8"
11
- description = "A tool for mining sentences from games. Update: Full UI Re-design"
10
+ version = "2.12.0-dev1"
11
+ description = "A tool for mining sentences from games. Update: Overlay?"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.10"
14
14
  license = { text = "MIT License" }