GameSentenceMiner 2.12.0.dev5__tar.gz → 2.12.1__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 (78) hide show
  1. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/ai/ai_prompting.py +8 -11
  2. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/anki.py +30 -0
  3. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/config_gui.py +10 -0
  4. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/gametext.py +2 -2
  5. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/gsm.py +9 -0
  6. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/owocr/owocr/ocr.py +122 -52
  7. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/owocr/owocr/run.py +37 -4
  8. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/configuration.py +13 -3
  9. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/text_log.py +2 -2
  10. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/web/texthooking_page.py +16 -14
  11. gamesentenceminer-2.12.1/GameSentenceMiner/wip/get_overlay_coords.py +244 -0
  12. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner.egg-info/PKG-INFO +1 -1
  13. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/PKG-INFO +1 -1
  14. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/pyproject.toml +1 -1
  15. gamesentenceminer-2.12.0.dev5/GameSentenceMiner/wip/get_overlay_coords.py +0 -95
  16. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/__init__.py +0 -0
  17. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/ai/__init__.py +0 -0
  18. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/assets/__init__.py +0 -0
  19. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/assets/icon.png +0 -0
  20. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/assets/icon128.png +0 -0
  21. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/assets/icon256.png +0 -0
  22. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/assets/icon32.png +0 -0
  23. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/assets/icon512.png +0 -0
  24. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/assets/icon64.png +0 -0
  25. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/assets/pickaxe.png +0 -0
  26. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/obs.py +0 -0
  27. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/ocr/__init__.py +0 -0
  28. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/ocr/gsm_ocr_config.py +0 -0
  29. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/ocr/ocrconfig.py +0 -0
  30. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/ocr/owocr_area_selector.py +0 -0
  31. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/ocr/owocr_helper.py +0 -0
  32. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/ocr/ss_picker.py +0 -0
  33. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/owocr/owocr/__init__.py +0 -0
  34. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/owocr/owocr/__main__.py +0 -0
  35. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/owocr/owocr/config.py +0 -0
  36. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -0
  37. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -0
  38. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/__init__.py +0 -0
  39. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/audio_offset_selector.py +0 -0
  40. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/communication/__init__.py +0 -0
  41. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/communication/send.py +0 -0
  42. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/communication/websocket.py +0 -0
  43. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/downloader/Untitled_json.py +0 -0
  44. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/downloader/__init__.py +0 -0
  45. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/downloader/download_tools.py +0 -0
  46. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/downloader/oneocr_dl.py +0 -0
  47. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/electron_config.py +0 -0
  48. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/ffmpeg.py +0 -0
  49. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/gsm_utils.py +0 -0
  50. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/model.py +0 -0
  51. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/notification.py +0 -0
  52. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/package.py +0 -0
  53. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/ss_selector.py +0 -0
  54. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/util/window_transparency.py +0 -0
  55. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/vad.py +0 -0
  56. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/web/__init__.py +0 -0
  57. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/web/service.py +0 -0
  58. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/web/static/__init__.py +0 -0
  59. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
  60. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/web/static/favicon-96x96.png +0 -0
  61. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/web/static/favicon.ico +0 -0
  62. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/web/static/favicon.svg +0 -0
  63. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/web/static/site.webmanifest +0 -0
  64. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/web/static/style.css +0 -0
  65. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
  66. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
  67. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/web/templates/__init__.py +0 -0
  68. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/web/templates/index.html +0 -0
  69. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/web/templates/text_replacements.html +0 -0
  70. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner/web/templates/utility.html +0 -0
  71. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner.egg-info/SOURCES.txt +0 -0
  72. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner.egg-info/dependency_links.txt +0 -0
  73. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner.egg-info/entry_points.txt +0 -0
  74. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner.egg-info/requires.txt +0 -0
  75. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/GameSentenceMiner.egg-info/top_level.txt +0 -0
  76. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/LICENSE +0 -0
  77. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/README.md +0 -0
  78. {gamesentenceminer-2.12.0.dev5 → gamesentenceminer-2.12.1}/setup.cfg +0 -0
@@ -5,16 +5,6 @@ from abc import ABC, abstractmethod
5
5
  from dataclasses import dataclass
6
6
  from enum import Enum
7
7
  from typing import List, Optional
8
-
9
-
10
- try:
11
- import torch
12
- from transformers import AutoTokenizer, AutoModelForCausalLM, AutoModelForSeq2SeqLM, pipeline
13
-
14
- TRANSFORMERS_AVAILABLE = True
15
- except ImportError:
16
- TRANSFORMERS_AVAILABLE = False
17
-
18
8
  from google import genai
19
9
  from google.genai import types
20
10
  from groq import Groq
@@ -136,6 +126,13 @@ class AIManager(ABC):
136
126
  class LocalAIManager(AIManager):
137
127
  def __init__(self, model, logger: Optional[logging.Logger] = None):
138
128
  super().__init__(LocalAIConfig(model=model), logger)
129
+ try:
130
+ import torch
131
+ from transformers import AutoTokenizer, AutoModelForCausalLM, AutoModelForSeq2SeqLM, pipeline
132
+
133
+ self.transformers_available = True
134
+ except (ImportError, OSError):
135
+ self.transformers_available = False
139
136
  self.model_name = self.ai_config.model
140
137
  if MANUAL_MODEL_OVERRIDE:
141
138
  self.model_name = MANUAL_MODEL_OVERRIDE
@@ -147,7 +144,7 @@ class LocalAIManager(AIManager):
147
144
  self.is_encoder_decoder = False
148
145
  self.is_nllb = "nllb" in self.model_name.lower()
149
146
 
150
- if not TRANSFORMERS_AVAILABLE:
147
+ if not self.transformers_available:
151
148
  self.logger.error("Local AI dependencies not found. Please run: pip install torch transformers sentencepiece")
152
149
  return
153
150
 
@@ -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,6 +162,35 @@ 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 = ''
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
164
194
  if selected_lines:
165
195
  try:
166
196
  sentence_in_anki = last_note.get_field(get_config().anki.sentence_field)
@@ -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)
@@ -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)
@@ -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
@@ -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, 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
- x_coords = [line['bounding_rect'][f'x{i}'] for line in filtered_lines for i in range(1, 5)]
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 ocr_resp['lines']:
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
- x = (True, res, crop_coords)
955
-
956
- # img.close()
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
- terminated = True
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 = 55003
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, ws_port):
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.ws_port = ws_port
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.ws_port,
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():
@@ -0,0 +1,244 @@
1
+ import asyncio
2
+ import io
3
+ import base64
4
+ from PIL import Image
5
+ from GameSentenceMiner.util.configuration import get_config
6
+
7
+ if get_config().wip.overlay_websocket_send:
8
+ from GameSentenceMiner.owocr.owocr.ocr import GoogleLens, OneOCR
9
+ from GameSentenceMiner.obs import *
10
+
11
+ # OBS WebSocket settings
12
+ OBS_HOST = 'localhost'
13
+ OBS_PORT = 7274
14
+ OBS_PASSWORD = 'your_obs_websocket_password' # Set your OBS WebSocket password here, if any
15
+
16
+ WINDOW_NAME = "Nier:Automata"
17
+ WIDTH = 2560
18
+ HEIGHT = 1440
19
+ if get_config().wip.overlay_websocket_send:
20
+ oneocr = OneOCR()
21
+ lens = GoogleLens()
22
+
23
+ def correct_ocr_text(detected_text: str, reference_text: str) -> str:
24
+ """
25
+ Correct OCR text by comparing character-by-character with reference text.
26
+ When mismatches are found, look for subsequent matches and correct previous mismatches.
27
+ """
28
+ if not detected_text or not reference_text:
29
+ return detected_text
30
+
31
+ detected_chars = list(detected_text)
32
+ reference_chars = list(reference_text)
33
+
34
+ # Track positions where mismatches occurred
35
+ mismatched_positions = []
36
+
37
+ min_length = min(len(detected_chars), len(reference_chars))
38
+
39
+ for i in range(min_length):
40
+ if detected_chars[i] != reference_chars[i]:
41
+ mismatched_positions.append(i)
42
+ logger.info(f"Mismatch at position {i}: detected '{detected_chars[i]}' vs reference '{reference_chars[i]}'")
43
+ else:
44
+ # We found a match - if we have previous mismatches, correct the most recent one
45
+ if mismatched_positions:
46
+ # Correct the most recent mismatch (simple 1-for-1 strategy)
47
+ last_mismatch_pos = mismatched_positions.pop()
48
+ old_char = detected_chars[last_mismatch_pos]
49
+ detected_chars[last_mismatch_pos] = reference_chars[last_mismatch_pos]
50
+ logger.info(f"Corrected position {last_mismatch_pos}: '{old_char}' -> '{reference_chars[last_mismatch_pos]}'")
51
+
52
+ corrected_text = ''.join(detected_chars)
53
+ return corrected_text
54
+
55
+ def redistribute_corrected_text(original_boxes: list, original_text: str, corrected_text: str) -> list:
56
+ """
57
+ Redistribute corrected text back to the original text boxes while maintaining their positions.
58
+ """
59
+ if original_text == corrected_text:
60
+ return original_boxes
61
+
62
+ corrected_boxes = []
63
+ text_position = 0
64
+
65
+ for box in original_boxes:
66
+ original_word = box['text']
67
+ word_length = len(original_word)
68
+
69
+ # Extract the corrected portion for this box
70
+ if text_position + word_length <= len(corrected_text):
71
+ corrected_word = corrected_text[text_position:text_position + word_length]
72
+ else:
73
+ # Handle case where corrected text is shorter
74
+ corrected_word = corrected_text[text_position:] if text_position < len(corrected_text) else ""
75
+
76
+ # Create a new box with corrected text but same coordinates
77
+ corrected_box = box.copy()
78
+ corrected_box['text'] = corrected_word
79
+ corrected_boxes.append(corrected_box)
80
+
81
+ text_position += word_length
82
+
83
+ logger.info(f"Redistributed: '{original_word}' -> '{corrected_word}'")
84
+
85
+ return corrected_boxes
86
+
87
+ async def get_full_screenshot() -> Image.Image | None:
88
+ # logger.info(f"Attempting to connect to OBS WebSocket at ws://{OBS_HOST}:{OBS_PORT}")
89
+ # try:
90
+ # client = obs.ReqClient(host=OBS_HOST, port=OBS_PORT, password=OBS_PASSWORD, timeout=30)
91
+ # logger.info("Connected to OBS WebSocket.")
92
+ # except Exception as e:
93
+ # logger.info(f"Failed to connect to OBS: {e}")
94
+ # return None
95
+ #
96
+ # try:
97
+ # response = client.get_source_screenshot(
98
+ # name=WINDOW_NAME,
99
+ # img_format='png',
100
+ # quality=75,
101
+ # width=WIDTH,
102
+ # height=HEIGHT,
103
+ # )
104
+ #
105
+ # if not response.image_data:
106
+ # logger.info("Failed to get screenshot data from OBS.")
107
+ # return None
108
+
109
+ logger.info("Getting Screenshot from OBS")
110
+ try:
111
+ update_current_game()
112
+ start_time = time.time()
113
+ image_data = get_screenshot_base64(compression=75, width=1280, height=720)
114
+ image_data = base64.b64decode(image_data)
115
+ img = Image.open(io.BytesIO(image_data)).convert("RGBA").resize((WIDTH, HEIGHT), Image.Resampling.LANCZOS)
116
+ # img.show()
117
+ logger.info(f"Screenshot captured in {time.time() - start_time:.2f} seconds.")
118
+
119
+ return img
120
+
121
+ except Exception as e:
122
+ logger.info(f"An unexpected error occurred during screenshot capture: {e}")
123
+ return None
124
+
125
+ async def do_work(sentence_to_check=None):
126
+ # connect_to_obs_sync(5)
127
+ logger.info("in find_box")
128
+ # await asyncio.sleep(.5)
129
+ logger.info("after_initial_sleep")
130
+ full_screenshot_image = await get_full_screenshot()
131
+ if os.path.exists("C:\\Users\\Beangate\\GSM\\temp"):
132
+ full_screenshot_image.save("C:\\Users\\Beangate\\GSM\\temp\\full_screenshot.png")
133
+ # full_screenshot_image.show()
134
+ if full_screenshot_image:
135
+ logger.info("Full screenshot captured successfully. Now performing local OCR...")
136
+ ocr_results = oneocr(full_screenshot_image, return_coords=True)
137
+
138
+ boxes_of_text = ocr_results[2]
139
+ # logger.info(f"Boxes of text found: {boxes_of_text}")
140
+
141
+ words = []
142
+
143
+ # If we have a reference sentence, perform character-by-character correction
144
+ if sentence_to_check:
145
+ # Concatenate all OCR text to form the detected sentence
146
+ detected_sentence = ''.join([box['text'] for box in boxes_of_text])
147
+ logger.info(f"Original detected sentence: '{detected_sentence}'")
148
+ logger.info(f"Reference sentence: '{sentence_to_check}'")
149
+
150
+ # Perform character-by-character comparison and correction
151
+ corrected_sentence = correct_ocr_text(detected_sentence, sentence_to_check)
152
+ logger.info(f"Corrected sentence: '{corrected_sentence}'")
153
+
154
+ # Redistribute corrected text back to boxes while maintaining positions
155
+ corrected_boxes = redistribute_corrected_text(boxes_of_text, detected_sentence, corrected_sentence)
156
+ else:
157
+ corrected_boxes = boxes_of_text
158
+
159
+ sentence_position = 0
160
+ for box in corrected_boxes:
161
+ word = box['text']
162
+ # logger.info(f"Box: {box}")
163
+ x1, y1 = box['bounding_rect']['x1'], box['bounding_rect']['y1']
164
+ x2, y2 = box['bounding_rect']['x3'], box['bounding_rect']['y3']
165
+ words.append({
166
+ "x1": x1,
167
+ "y1": y1,
168
+ "x2": x2,
169
+ "y2": y2,
170
+ "word": box['text']
171
+ })
172
+
173
+ # logger.info(f"Returning words: {words}")
174
+
175
+ ret = [
176
+ {
177
+ "words": words,
178
+ }
179
+ ]
180
+ # cropped_sections = []
181
+ # for box in boxes_of_text:
182
+ # # Ensure crop coordinates are within image bounds
183
+ # left = max(0, box['bounding_rect']['x1'])
184
+ # top = max(0, box['bounding_rect']['y1'])
185
+ # right = min(full_screenshot_image.width, box['bounding_rect']['x3'])
186
+ # bottom = min(full_screenshot_image.height, box['bounding_rect']['y3'])
187
+ # cropped_sections.append(full_screenshot_image.crop((left, top, right, bottom)))
188
+
189
+ # if len(cropped_sections) > 1:
190
+ # # Create a transparent image with the same size as the full screenshot
191
+ # combined_img = Image.new("RGBA", (full_screenshot_image.width, full_screenshot_image.height), (0, 0, 0, 0))
192
+
193
+ # combined_img.show()
194
+
195
+ # # Paste each cropped section at its original coordinates
196
+ # for box, section in zip(boxes_of_text, cropped_sections):
197
+ # left = max(0, box['bounding_rect']['x1'])
198
+ # top = max(0, box['bounding_rect']['y1'])
199
+ # combined_img.paste(section, (left, top))
200
+
201
+ # new_image = combined_img
202
+ # elif cropped_sections:
203
+ # new_image = cropped_sections[0]
204
+ # else:
205
+ # new_image = Image.new("RGBA", full_screenshot_image.size)
206
+
207
+ # new_image.show()
208
+ # ocr_results = lens(new_image, return_coords=True)
209
+ # ocr_results = oneocr(full_screenshot_image, sentence_to_check=sentence_to_check)
210
+ # logger.info("\n--- OCR Results ---")
211
+ # logger.info(ocr_results)
212
+
213
+ return ret, 48
214
+ # from PIL import ImageDraw
215
+ # draw = ImageDraw.Draw(full_screenshot_image)
216
+ # draw.rectangle([x1, y1, x2, y2], outline="red", width=3)
217
+ # full_screenshot_image.save("full_screenshot_with_ocr.png")
218
+ # full_screenshot_image.show()
219
+ #
220
+ # logger.info(ocr_results)
221
+ # if ocr_results:
222
+ # for i, result in enumerate(ocr_results):
223
+ # logger.info(f"Result {i + 1}:\n{result}\n")
224
+ # else:
225
+ # logger.info("No OCR results found.")
226
+ else:
227
+ logger.info("Failed to get full screenshot for OCR.")
228
+
229
+ async def find_box_for_sentence(sentence_to_check):
230
+ try:
231
+ return await do_work(sentence_to_check=sentence_to_check)
232
+ except Exception as e:
233
+ logger.info(f"Error in find_box_for_sentence: {e}", exc_info=True)
234
+ return [], 48
235
+
236
+ async def main():
237
+ connect_to_obs_sync(5)
238
+ await find_box_for_sentence("はじめから")
239
+
240
+ if __name__ == '__main__':
241
+ try:
242
+ asyncio.run(main())
243
+ except KeyboardInterrupt:
244
+ logger.info("Script terminated by user.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.12.0.dev5
3
+ Version: 2.12.1
4
4
  Summary: A tool for mining sentences from games. Update: Overlay?
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.12.0.dev5
3
+ Version: 2.12.1
4
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 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "GameSentenceMiner"
10
- version = "2.12.0-dev5"
10
+ version = "2.12.1"
11
11
  description = "A tool for mining sentences from games. Update: Overlay?"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.10"
@@ -1,95 +0,0 @@
1
- import asyncio
2
- import io
3
- import base64
4
- from PIL import Image
5
- import obsws_python as obs
6
-
7
- from GameSentenceMiner.owocr.owocr.ocr import GoogleLens, OneOCR
8
- from GameSentenceMiner.obs import *
9
-
10
- # OBS WebSocket settings
11
- OBS_HOST = 'localhost'
12
- OBS_PORT = 7274
13
- OBS_PASSWORD = 'your_obs_websocket_password' # Set your OBS WebSocket password here, if any
14
-
15
- WINDOW_NAME = "Nier:Automata"
16
- WIDTH = 2560
17
- HEIGHT = 1440
18
- oneocr = OneOCR()
19
- lens = GoogleLens()
20
-
21
- async def get_full_screenshot() -> Image.Image | None:
22
- # print(f"Attempting to connect to OBS WebSocket at ws://{OBS_HOST}:{OBS_PORT}")
23
- # try:
24
- # client = obs.ReqClient(host=OBS_HOST, port=OBS_PORT, password=OBS_PASSWORD, timeout=30)
25
- # print("Connected to OBS WebSocket.")
26
- # except Exception as e:
27
- # print(f"Failed to connect to OBS: {e}")
28
- # return None
29
- #
30
- # try:
31
- # response = client.get_source_screenshot(
32
- # name=WINDOW_NAME,
33
- # img_format='png',
34
- # quality=75,
35
- # width=WIDTH,
36
- # height=HEIGHT,
37
- # )
38
- #
39
- # if not response.image_data:
40
- # print("Failed to get screenshot data from OBS.")
41
- # return None
42
-
43
- print("Getting Screenshot from OBS")
44
- try:
45
- update_current_game()
46
- start_time = time.time()
47
- image_data = get_screenshot_base64(compression=75, width=1280, height=720)
48
- image_data = base64.b64decode(image_data)
49
- img = Image.open(io.BytesIO(image_data)).convert("RGBA").resize((WIDTH, HEIGHT), Image.Resampling.LANCZOS)
50
- # img.show()
51
- print(f"Screenshot captured in {time.time() - start_time:.2f} seconds.")
52
-
53
- return img
54
-
55
- except Exception as e:
56
- print(f"An unexpected error occurred during screenshot capture: {e}")
57
- return None
58
-
59
- async def find_box_for_sentence(sentence_to_check):
60
- # connect_to_obs_sync(5)
61
- print("in find_box")
62
- # await asyncio.sleep(.5)
63
- print("after_initial_sleep")
64
- full_screenshot_image = await get_full_screenshot()
65
- if full_screenshot_image:
66
- print("Full screenshot captured successfully. Now performing local OCR...")
67
- ocr_results = lens(full_screenshot_image, return_coords=True)
68
- # ocr_results = oneocr(full_screenshot_image, sentence_to_check=sentence_to_check)
69
- print("\n--- OCR Results ---")
70
- print(ocr_results)
71
-
72
- return ocr_results[2], 48
73
- # from PIL import ImageDraw
74
- # draw = ImageDraw.Draw(full_screenshot_image)
75
- # draw.rectangle([x1, y1, x2, y2], outline="red", width=3)
76
- # full_screenshot_image.save("full_screenshot_with_ocr.png")
77
- # full_screenshot_image.show()
78
- #
79
- # print(ocr_results)
80
- # if ocr_results:
81
- # for i, result in enumerate(ocr_results):
82
- # print(f"Result {i + 1}:\n{result}\n")
83
- # else:
84
- # print("No OCR results found.")
85
- else:
86
- print("Failed to get full screenshot for OCR.")
87
-
88
- async def main():
89
- await find_box_for_sentence("はじめから")
90
-
91
- if __name__ == '__main__':
92
- try:
93
- asyncio.run(main())
94
- except KeyboardInterrupt:
95
- print("Script terminated by user.")