GameSentenceMiner 2.13.15__py3-none-any.whl → 2.14.0rc1__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.
Files changed (29) hide show
  1. GameSentenceMiner/ai/ai_prompting.py +77 -132
  2. GameSentenceMiner/anki.py +48 -6
  3. GameSentenceMiner/config_gui.py +196 -30
  4. GameSentenceMiner/gametext.py +8 -19
  5. GameSentenceMiner/gsm.py +5 -4
  6. GameSentenceMiner/locales/en_us.json +21 -11
  7. GameSentenceMiner/locales/ja_jp.json +21 -11
  8. GameSentenceMiner/locales/zh_cn.json +9 -11
  9. GameSentenceMiner/owocr/owocr/ocr.py +20 -23
  10. GameSentenceMiner/tools/__init__.py +0 -0
  11. GameSentenceMiner/util/configuration.py +241 -105
  12. GameSentenceMiner/util/db.py +408 -0
  13. GameSentenceMiner/util/ffmpeg.py +2 -10
  14. GameSentenceMiner/util/get_overlay_coords.py +324 -0
  15. GameSentenceMiner/util/model.py +8 -2
  16. GameSentenceMiner/util/text_log.py +1 -1
  17. GameSentenceMiner/web/texthooking_page.py +1 -1
  18. GameSentenceMiner/wip/__init___.py +0 -0
  19. {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/METADATA +5 -1
  20. {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/RECORD +27 -25
  21. GameSentenceMiner/util/package.py +0 -37
  22. GameSentenceMiner/wip/get_overlay_coords.py +0 -535
  23. /GameSentenceMiner/{util → tools}/audio_offset_selector.py +0 -0
  24. /GameSentenceMiner/{util → tools}/ss_selector.py +0 -0
  25. /GameSentenceMiner/{util → tools}/window_transparency.py +0 -0
  26. {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/WHEEL +0 -0
  27. {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/entry_points.txt +0 -0
  28. {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/licenses/LICENSE +0 -0
  29. {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,324 @@
1
+ import asyncio
2
+ import io
3
+ import base64
4
+ import math
5
+ import os
6
+ import time
7
+ from PIL import Image
8
+ from typing import Dict, Any, List, Tuple
9
+
10
+ # Local application imports
11
+ from GameSentenceMiner.util.configuration import get_config
12
+ from GameSentenceMiner.util.electron_config import get_ocr_language
13
+ from GameSentenceMiner.obs import get_screenshot_PIL, logger
14
+ from GameSentenceMiner.web.texthooking_page import send_word_coordinates_to_overlay
15
+
16
+ # Conditionally import OCR engines
17
+ try:
18
+ from GameSentenceMiner.owocr.owocr.ocr import GoogleLens, OneOCR, get_regex
19
+ except ImportError:
20
+ GoogleLens, OneOCR, get_regex = None, None, None
21
+
22
+ # Conditionally import screenshot library
23
+ try:
24
+ import mss
25
+ except ImportError:
26
+ mss = None
27
+
28
+ class OverlayProcessor:
29
+ """
30
+ Handles the entire overlay process from screen capture to text extraction.
31
+
32
+ This class encapsulates the logic for taking screenshots, identifying text
33
+ regions, performing OCR, and processing the results into a structured format
34
+ with pixel coordinates.
35
+ """
36
+
37
+ def __init__(self):
38
+ """Initializes the OCR engines and configuration."""
39
+ self.config = get_config()
40
+ self.oneocr = None
41
+ self.lens = None
42
+ self.regex = None
43
+
44
+ if self.config.overlay.websocket_port and all([GoogleLens, OneOCR, get_regex]):
45
+ logger.info("Initializing OCR engines...")
46
+ self.oneocr = OneOCR(lang=get_ocr_language())
47
+ self.lens = GoogleLens(lang=get_ocr_language())
48
+ self.ocr_language = get_ocr_language()
49
+ self.regex = get_regex(self.ocr_language)
50
+ logger.info("OCR engines initialized.")
51
+ else:
52
+ logger.warning("OCR dependencies not found or websocket port not configured. OCR functionality will be disabled.")
53
+
54
+ if not mss:
55
+ logger.warning("MSS library not found. Screenshot functionality may be limited.")
56
+
57
+ async def find_box_and_send_to_overlay(self, sentence_to_check: str = None):
58
+ """
59
+ Sends the detected text boxes to the overlay via WebSocket.
60
+ """
61
+ boxes = await self.find_box_for_sentence(sentence_to_check)
62
+ logger.info(f"Sending {len(boxes)} boxes to overlay.")
63
+ await send_word_coordinates_to_overlay(boxes)
64
+
65
+ async def find_box_for_sentence(self, sentence_to_check: str = None) -> List[Dict[str, Any]]:
66
+ """
67
+ Public method to perform OCR and find text boxes for a given sentence.
68
+
69
+ This is a wrapper around the main work-horse method, providing
70
+ error handling.
71
+ """
72
+ try:
73
+ return await self._do_work(sentence_to_check)
74
+ except Exception as e:
75
+ logger.error(f"Error during OCR processing: {e}", exc_info=True)
76
+ return []
77
+
78
+ def _get_full_screenshot(self) -> Tuple[Image.Image | None, int, int]:
79
+ """Captures a screenshot of the configured monitor."""
80
+ if not mss:
81
+ raise RuntimeError("MSS screenshot library is not installed.")
82
+
83
+ with mss.mss() as sct:
84
+ monitors = sct.monitors
85
+ # Index 0 is the 'all monitors' virtual screen, so we skip it.
86
+ monitor_list = monitors[1:] if len(monitors) > 1 else [monitors[0]]
87
+
88
+ monitor_index = self.config.overlay.monitor_to_capture
89
+ if monitor_index >= len(monitor_list):
90
+ logger.error(f"Monitor index {monitor_index} is out of bounds. Found {len(monitor_list)} monitors.")
91
+ return None, 0, 0
92
+
93
+ monitor = monitor_list[monitor_index]
94
+ sct_img = sct.grab(monitor)
95
+ img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
96
+
97
+ return img, monitor['width'], monitor['height']
98
+
99
+ def _create_composite_image(
100
+ self,
101
+ full_screenshot: Image.Image,
102
+ crop_coords_list: List[Tuple[int, int, int, int]],
103
+ monitor_width: int,
104
+ monitor_height: int
105
+ ) -> Image.Image:
106
+ """
107
+ Creates a new image by pasting cropped text regions onto a transparent background.
108
+ This isolates text for more accurate secondary OCR.
109
+ """
110
+ if not crop_coords_list:
111
+ return full_screenshot
112
+
113
+ # Create a transparent canvas
114
+ composite_img = Image.new("RGBA", (monitor_width, monitor_height), (0, 0, 0, 0))
115
+
116
+ for crop_coords in crop_coords_list:
117
+ cropped_image = full_screenshot.crop(crop_coords)
118
+ # Paste the cropped image onto the canvas at its original location
119
+ paste_x = math.floor(crop_coords[0])
120
+ paste_y = math.floor(crop_coords[1])
121
+ composite_img.paste(cropped_image, (paste_x, paste_y))
122
+
123
+ return composite_img
124
+
125
+ async def _do_work(self, sentence_to_check: str = None) -> Tuple[List[Dict[str, Any]], int]:
126
+ """The main OCR workflow."""
127
+ if not self.oneocr or not self.lens:
128
+ raise RuntimeError("OCR engines are not initialized. Cannot perform OCR.")
129
+
130
+ # 1. Get screenshot
131
+ full_screenshot, monitor_width, monitor_height = self._get_full_screenshot()
132
+ if not full_screenshot:
133
+ logger.warning("Failed to get a screenshot.")
134
+ return []
135
+
136
+ # 2. Use OneOCR to find general text areas (fast)
137
+ _, _, _, crop_coords_list = self.oneocr(
138
+ full_screenshot,
139
+ return_coords=True,
140
+ multiple_crop_coords=True,
141
+ return_one_box=False,
142
+ furigana_filter_sensitivity=0
143
+ )
144
+
145
+ # 3. Create a composite image with only the detected text regions
146
+ composite_image = self._create_composite_image(
147
+ full_screenshot,
148
+ crop_coords_list,
149
+ monitor_width,
150
+ monitor_height
151
+ )
152
+
153
+ # 4. Use Google Lens on the cleaner composite image for higher accuracy
154
+ res, text, coords = self.lens(
155
+ composite_image,
156
+ return_coords=True,
157
+ furigana_filter_sensitivity=0
158
+ )
159
+
160
+ if not res or not coords:
161
+ return []
162
+
163
+ # 5. Process the high-accuracy results into the desired format
164
+ extracted_data = self._extract_text_with_pixel_boxes(
165
+ api_response=coords,
166
+ original_width=monitor_width,
167
+ original_height=monitor_height,
168
+ crop_x=0,
169
+ crop_y=0,
170
+ crop_width=composite_image.width,
171
+ crop_height=composite_image.height
172
+ )
173
+
174
+ return extracted_data
175
+
176
+ def _extract_text_with_pixel_boxes(
177
+ self,
178
+ api_response: Dict[str, Any],
179
+ original_width: int,
180
+ original_height: int,
181
+ crop_x: int,
182
+ crop_y: int,
183
+ crop_width: int,
184
+ crop_height: int
185
+ ) -> List[Dict[str, Any]]:
186
+ """
187
+ Parses Google Lens API response and converts normalized coordinates
188
+ to absolute pixel coordinates.
189
+ """
190
+ results = []
191
+ try:
192
+ paragraphs = api_response["objects_response"]["text"]["text_layout"]["paragraphs"]
193
+ except (KeyError, TypeError):
194
+ return [] # Return empty if the expected structure isn't present
195
+
196
+ for para in paragraphs:
197
+ for line in para.get("lines", []):
198
+ line_text_parts = []
199
+ word_list = []
200
+
201
+ for word in line.get("words", []):
202
+ word_text = word.get("plain_text", "")
203
+ line_text_parts.append(word_text)
204
+
205
+ word_box = self._convert_box_to_pixels_v2(
206
+ word["geometry"]["bounding_box"],
207
+ crop_x, crop_y, crop_width, crop_height
208
+ )
209
+
210
+ word_list.append({
211
+ "text": word_text,
212
+ "bounding_rect": word_box
213
+ })
214
+
215
+ if not line_text_parts:
216
+ continue
217
+
218
+ full_line_text = "".join(line_text_parts)
219
+ line_box = self._convert_box_to_pixels_v2(
220
+ line["geometry"]["bounding_box"],
221
+ crop_x, crop_y, crop_width, crop_height
222
+ )
223
+
224
+ results.append({
225
+ "text": full_line_text,
226
+ "bounding_rect": line_box,
227
+ "words": word_list
228
+ })
229
+ return results
230
+
231
+ def _convert_box_to_pixels_v2(
232
+ self,
233
+ bbox_data: Dict[str, float],
234
+ crop_x: int,
235
+ crop_y: int,
236
+ crop_width: int,
237
+ crop_height: int
238
+ ) -> Dict[str, float]:
239
+ """
240
+ Simplified conversion: scales normalized bbox to pixel coordinates within
241
+ the cropped region, then offsets by the crop position. Ignores rotation.
242
+ """
243
+ cx, cy = bbox_data['center_x'], bbox_data['center_y']
244
+ w, h = bbox_data['width'], bbox_data['height']
245
+
246
+ # Scale normalized coordinates to pixel coordinates relative to the crop area
247
+ box_width_px = w * crop_width
248
+ box_height_px = h * crop_height
249
+
250
+ # Calculate center within the cropped area and then add the crop offset
251
+ center_x_px = (cx * crop_width) + crop_x
252
+ center_y_px = (cy * crop_height) + crop_y
253
+
254
+ # Calculate corners (unrotated)
255
+ half_w_px, half_h_px = box_width_px / 2, box_height_px / 2
256
+ return {
257
+ "x1": center_x_px - half_w_px, "y1": center_y_px - half_h_px,
258
+ "x2": center_x_px + half_w_px, "y2": center_y_px - half_h_px,
259
+ "x3": center_x_px + half_w_px, "y3": center_y_px + half_h_px,
260
+ "x4": center_x_px - half_w_px, "y4": center_y_px + half_h_px,
261
+ }
262
+
263
+ async def main_test_screenshot():
264
+ """
265
+ A test function to demonstrate screenshot and image composition.
266
+ This is preserved from your original __main__ block.
267
+ """
268
+ processor = OverlayProcessor()
269
+
270
+ # Use the class method to get the screenshot
271
+ img, monitor_width, monitor_height = processor._get_full_screenshot()
272
+ if not img:
273
+ logger.error("Could not get screenshot for test.")
274
+ return
275
+
276
+ img.show()
277
+
278
+ # Create a transparent image with the same size as the monitor
279
+ new_img = Image.new("RGBA", (monitor_width, monitor_height), (0, 0, 0, 0))
280
+
281
+ # Calculate coordinates to center the captured image (if it's not full-screen)
282
+ left = (monitor_width - img.width) // 2
283
+ top = (monitor_height - img.height) // 2
284
+
285
+ print(f"Image size: {img.size}, Monitor size: {monitor_width}x{monitor_height}")
286
+ print(f"Pasting at: Left={left}, Top={top}")
287
+
288
+ new_img.paste(img, (left, top))
289
+ new_img.show()
290
+
291
+ async def main_run_ocr():
292
+ """
293
+ Main function to demonstrate running the full OCR process.
294
+ """
295
+ processor = OverlayProcessor()
296
+ results, _ = await processor.find_box_for_sentence()
297
+ if results:
298
+ import json
299
+ print("OCR process completed successfully.")
300
+ # print(json.dumps(results, indent=2, ensure_ascii=False))
301
+ # Find first result with some text
302
+ for result in results:
303
+ if result.get("text"):
304
+ print(f"Found line: '{result['text']}'")
305
+ print(f" - Line BBox: {result['bounding_rect']}")
306
+ if result.get("words"):
307
+ print(f" - First word: '{result['words'][0]['text']}' BBox: {result['words'][0]['bounding_rect']}")
308
+ break
309
+ else:
310
+ print("OCR process did not find any text.")
311
+
312
+
313
+ if __name__ == '__main__':
314
+ try:
315
+ # To run the screenshot test:
316
+ # asyncio.run(main_test_screenshot())
317
+
318
+ # To run the full OCR process:
319
+ asyncio.run(main_run_ocr())
320
+
321
+ except KeyboardInterrupt:
322
+ logger.info("Script terminated by user.")
323
+ except Exception as e:
324
+ logger.error(f"An error occurred in the main execution block: {e}", exc_info=True)
@@ -109,12 +109,18 @@ class SceneListResponse:
109
109
  # videoActive: bool
110
110
  # videoShowing: bool
111
111
 
112
+ @dataclass_json
113
+ @dataclass
114
+ class AnkiField:
115
+ value: str
116
+ order: int
117
+
112
118
  @dataclass_json
113
119
  @dataclass
114
120
  class AnkiCard:
115
121
  noteId: int
116
122
  tags: list[str]
117
- fields: dict[str, dict[str, str]]
123
+ fields: dict[str, AnkiField]
118
124
  cards: list[int]
119
125
  alternatives = {
120
126
  "word_field": ["Front", "Word", "TargetWord", "Expression"],
@@ -125,7 +131,7 @@ class AnkiCard:
125
131
 
126
132
  def get_field(self, field_name: str) -> str:
127
133
  if self.has_field(field_name):
128
- return self.fields[field_name]['value']
134
+ return self.fields[field_name].value
129
135
  else:
130
136
  raise ValueError(f"Field '{field_name}' not found in AnkiCard. Please make sure your Anki Field Settings in GSM Match your fields in your Anki Note!")
131
137
 
@@ -129,7 +129,7 @@ def lines_match(texthooker_sentence, anki_sentence):
129
129
  logger.debug(f"One contains the other: {texthooker_sentence} in {anki_sentence} - Similarity: {similarity}")
130
130
  elif anki_sentence in texthooker_sentence:
131
131
  logger.debug(f"One contains the other: {anki_sentence} in {texthooker_sentence} - Similarity: {similarity}")
132
- return (anki_sentence in texthooker_sentence) or (texthooker_sentence in anki_sentence and similarity > 80)
132
+ return (anki_sentence in texthooker_sentence) or (texthooker_sentence in anki_sentence) or (similarity >= 80)
133
133
 
134
134
 
135
135
  def get_text_event(last_note) -> GameLine:
@@ -523,7 +523,7 @@ if get_config().advanced.plaintext_websocket_port:
523
523
  plaintext_websocket_server_thread.start()
524
524
 
525
525
  overlay_server_thread = WebsocketServerThread(
526
- read=False, get_ws_port_func=lambda: get_config().get_field_value('wip', 'overlay_websocket_port'))
526
+ read=False, get_ws_port_func=lambda: get_config().get_field_value('overlay', 'websocket_port'))
527
527
  overlay_server_thread.start()
528
528
 
529
529
  websocket_server_threads = [
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.13.15
3
+ Version: 2.14.0rc1
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
@@ -39,4 +39,8 @@ Requires-Dist: obsws-python~=1.7.2
39
39
  Requires-Dist: matplotlib
40
40
  Requires-Dist: sounddevice
41
41
  Requires-Dist: google-genai
42
+ Requires-Dist: owocr
43
+ Requires-Dist: betterproto==2.0.0b7
44
+ Requires-Dist: oneocr
45
+ Requires-Dist: openai
42
46
  Dynamic: license-file
@@ -1,12 +1,12 @@
1
1
  GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- GameSentenceMiner/anki.py,sha256=zBvKvu-LBaUmG1bUNLq9QXmVoPidqZCxJn4UPw6pUug,21041
3
- GameSentenceMiner/config_gui.py,sha256=nrOGhYuvHxvJ0mmlXsK5ANtaie_ztMNOWf-iwxLEkeM,125738
4
- GameSentenceMiner/gametext.py,sha256=h6IOGesK79X8IlvqqMmSzRkSVtkPAXDMHrkQsBwEV1E,10879
5
- GameSentenceMiner/gsm.py,sha256=daUAD_kr5nEG0WjPxMDq2PVSLXfx4dQ73OvTUWX9AlQ,28298
2
+ GameSentenceMiner/anki.py,sha256=mNUU0AYwsDezVug-ZXDpR71R9GGUBsmc9Gu8ftfnpf4,23078
3
+ GameSentenceMiner/config_gui.py,sha256=dFN-2mD9JTcRhjFIjaj4bFcEmMAdjudYk_A5x9rp3_U,134126
4
+ GameSentenceMiner/gametext.py,sha256=2MHOLuuXAxrhs0tkwh9JW_BYHQ7YMf-lHfO2Kl3ACDs,10244
5
+ GameSentenceMiner/gsm.py,sha256=GdqegZnKrTMVRvp43bK7oNlWj5OxLx2PNdVWyHL9Gc4,28282
6
6
  GameSentenceMiner/obs.py,sha256=smlP_BFuuMkASVDEPG3DjxJ6p617kZXuNTeQ0edtH64,18703
7
7
  GameSentenceMiner/vad.py,sha256=zFReBMvNEEaQ_YEozCTCaMdV-o40FwtlxYRb17cYZio,19125
8
8
  GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- GameSentenceMiner/ai/ai_prompting.py,sha256=iHkEx2pQJ-tEyejOgYy4G0DcZc8qvBugVL6-CQpPSME,26089
9
+ GameSentenceMiner/ai/ai_prompting.py,sha256=gNNDkoXVjWU8-is1CVI08ir2Jo_DpGvCWkV8TLl74Gg,23375
10
10
  GameSentenceMiner/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  GameSentenceMiner/assets/icon.png,sha256=9GRL8uXUAgkUSlvbm9Pv9o2poFVRGdW6s2ub_DeUD9M,937624
12
12
  GameSentenceMiner/assets/icon128.png,sha256=l90j7biwdz5ahwOd5wZ-406ryEV9Pan93dquJQ3e1CI,18395
@@ -15,9 +15,9 @@ GameSentenceMiner/assets/icon32.png,sha256=Kww0hU_qke9_22wBuO_Nq0Dv2SfnOLwMhCyGg
15
15
  GameSentenceMiner/assets/icon512.png,sha256=HxUj2GHjyQsk8NV433256UxU9phPhtjCY-YB_7W4sqs,192487
16
16
  GameSentenceMiner/assets/icon64.png,sha256=N8xgdZXvhqVQP9QUK3wX5iqxX9LxHljD7c-Bmgim6tM,9301
17
17
  GameSentenceMiner/assets/pickaxe.png,sha256=VfIGyXyIZdzEnVcc4PmG3wszPMO1W4KCT7Q_nFK6eSE,1403829
18
- GameSentenceMiner/locales/en_us.json,sha256=9aFJw4vls269_ufnVx03uRLXG8-E-hdG9hJ9RVzM01I,25653
19
- GameSentenceMiner/locales/ja_jp.json,sha256=zx5R_DLsdUw9Szg7RrOkRprMcnumM5Bnl4QhsfOj-qM,27190
20
- GameSentenceMiner/locales/zh_cn.json,sha256=fwP5GQh0WkftdpXuyyt9IiaEIPK_ZXtEa_3kmoeQMAk,24198
18
+ GameSentenceMiner/locales/en_us.json,sha256=MYcOLgjOIpzWwC0WCPvsbIvGQ9qRIhLCZ1DlHcGQfko,25942
19
+ GameSentenceMiner/locales/ja_jp.json,sha256=2I-aSs6KIbjJ7uNRA-dx_5oRP9JLTWWUIrMMZOkenlE,27520
20
+ GameSentenceMiner/locales/zh_cn.json,sha256=tx9Szm6KEMn_YaYDWS1ph4pGALYR3iguVru2Dfj7S3o,24047
21
21
  GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=1cuFYT2LN_A2WjCqm2FsiHqM93EG2zIbB4ZWxps829Y,5791
23
23
  GameSentenceMiner/ocr/ocrconfig.py,sha256=_tY8mjnzHMJrLS8E5pHqYXZjMuLoGKYgJwdhYgN-ny4,6466
@@ -28,21 +28,23 @@ GameSentenceMiner/owocr/owocr/__init__.py,sha256=87hfN5u_PbL_onLfMACbc0F5j4KyIK9
28
28
  GameSentenceMiner/owocr/owocr/__main__.py,sha256=XQaqZY99EKoCpU-gWQjNbTs7Kg17HvBVE7JY8LqIE0o,157
29
29
  GameSentenceMiner/owocr/owocr/config.py,sha256=qM7kISHdUhuygGXOxmgU6Ef2nwBShrZtdqu4InDCViE,8103
30
30
  GameSentenceMiner/owocr/owocr/lens_betterproto.py,sha256=oNoISsPilVVRBBPVDtb4-roJtAhp8ZAuFTci3TGXtMc,39141
31
- GameSentenceMiner/owocr/owocr/ocr.py,sha256=beArOJgVPhK7aeEwC5qAdbXzGRkNIpGNiM8rAnQ6cbg,68508
31
+ GameSentenceMiner/owocr/owocr/ocr.py,sha256=OeGPD6NgwKXkmgVTQfo3gulNLXwnBP7zltyKPZKPB1I,68365
32
32
  GameSentenceMiner/owocr/owocr/run.py,sha256=TSSZnHO_sPoTtUCsDol-v4TWPPzz_Nbf24TeBUea5I4,68498
33
33
  GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=Na6XStbQBtpQUSdbN3QhEswtKuU1JjReFk_K8t5ezQE,3395
34
+ GameSentenceMiner/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
+ GameSentenceMiner/tools/audio_offset_selector.py,sha256=8Stk3BP-XVIuzRv9nl9Eqd2D-1yD3JrgU-CamBywJmY,8542
36
+ GameSentenceMiner/tools/ss_selector.py,sha256=cbjMxiKOCuOfbRvLR_PCRlykBrGtm1LXd6u5czPqkmc,4793
37
+ GameSentenceMiner/tools/window_transparency.py,sha256=GtbxbmZg0-UYPXhfHff-7IKZyY2DKe4B9GdyovfmpeM,8166
34
38
  GameSentenceMiner/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
- GameSentenceMiner/util/audio_offset_selector.py,sha256=8Stk3BP-XVIuzRv9nl9Eqd2D-1yD3JrgU-CamBywJmY,8542
36
- GameSentenceMiner/util/configuration.py,sha256=p0ZnrPa0UTFzj59y__pNy0aWOxjlHz1fURZI33qWu74,37184
39
+ GameSentenceMiner/util/configuration.py,sha256=SODstU9nrRRAmbPrubYHJuGzzuKXURNUEQQPrfa9_1o,39739
40
+ GameSentenceMiner/util/db.py,sha256=2bO0rD4i8A1hhsRBER-wgZy9IK17ibRbI8DHxdKvYsI,16598
37
41
  GameSentenceMiner/util/electron_config.py,sha256=9CA27nzEFlxezzDqOPHxeD4BdJ093AnSJ9DJTcwWPsM,8762
38
- GameSentenceMiner/util/ffmpeg.py,sha256=HI2H_prbiZtLtLQgYOj1QNu0Ik_8Du_7gtoHftaz1g4,23520
42
+ GameSentenceMiner/util/ffmpeg.py,sha256=iqsdp3TbBv6KMACJxkUF3e5VWak3jHPZdIEMrUdKFtE,23073
43
+ GameSentenceMiner/util/get_overlay_coords.py,sha256=zBDFSWVdBLYZQcwdICQe9stg1mG0UHnJHupkuZGlJY8,12042
39
44
  GameSentenceMiner/util/gsm_utils.py,sha256=Piwv88Q9av2LBeN7M6QDi0Mp0_R2lNbkcI6ekK5hd2o,11851
40
- GameSentenceMiner/util/model.py,sha256=hmA_seopP2bK40v9T4ulua9TrAeWtbkdCv-sTBPBQDk,6660
45
+ GameSentenceMiner/util/model.py,sha256=R-_RYTYLSDNgBoVTPuPBcIHeOznIqi_vBzQ7VQ20WYk,6727
41
46
  GameSentenceMiner/util/notification.py,sha256=-qk3kTKEERzmMxx5XMh084HCyFmbfqz0XjY1hTKhCeQ,4202
42
- GameSentenceMiner/util/package.py,sha256=u1ym5z869lw5EHvIviC9h9uH97bzUXSXXA8KIn8rUvk,1157
43
- GameSentenceMiner/util/ss_selector.py,sha256=cbjMxiKOCuOfbRvLR_PCRlykBrGtm1LXd6u5czPqkmc,4793
44
- GameSentenceMiner/util/text_log.py,sha256=TnrwoB830wzLEnur57uZoWg-agi5vxPYFnd2GipWaFE,6729
45
- GameSentenceMiner/util/window_transparency.py,sha256=GtbxbmZg0-UYPXhfHff-7IKZyY2DKe4B9GdyovfmpeM,8166
47
+ GameSentenceMiner/util/text_log.py,sha256=zB9--7J_Wwck74IOEI4aWhmCYouqjSE6Sm0sCznF63Q,6731
46
48
  GameSentenceMiner/util/communication/__init__.py,sha256=xh__yn2MhzXi9eLi89PeZWlJPn-cbBSjskhi1BRraXg,643
47
49
  GameSentenceMiner/util/communication/send.py,sha256=Wki9qIY2CgYnuHbmnyKVIYkcKAN_oYS4up93XMikBaI,222
48
50
  GameSentenceMiner/util/communication/websocket.py,sha256=TbphRGmxVrgEupS7tNdifsmQfWDfIp0Hio2cSiUKgsk,3317
@@ -52,7 +54,7 @@ GameSentenceMiner/util/downloader/download_tools.py,sha256=zR-aEHiFVkyo-9oPoSx6n
52
54
  GameSentenceMiner/util/downloader/oneocr_dl.py,sha256=l3s9Z-x1b57GX048o5h-MVv0UTZo4H-Q-zb-JREkMLI,10439
53
55
  GameSentenceMiner/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
56
  GameSentenceMiner/web/service.py,sha256=YZchmScTn7AX_GkwV1ULEK6qjdOnJcpc3qfMwDf7cUE,5363
55
- GameSentenceMiner/web/texthooking_page.py,sha256=qCNtWtWZAiC91uPrHD2LUWdHOufVuzDhTyTH8Z4oXXk,17927
57
+ GameSentenceMiner/web/texthooking_page.py,sha256=EBdP6v_CCEr-7MCRAkBSBf5Y59E4JBkneifQ1Pgq76I,17923
56
58
  GameSentenceMiner/web/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
59
  GameSentenceMiner/web/static/apple-touch-icon.png,sha256=OcMI8af_68DA_tweOsQ5LytTyMwm7-hPW07IfrOVgEs,46132
58
60
  GameSentenceMiner/web/static/favicon-96x96.png,sha256=lOePzjiKl1JY2J1kT_PMdyEnrlJmi5GWbmXJunM12B4,16502
@@ -66,10 +68,10 @@ GameSentenceMiner/web/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
66
68
  GameSentenceMiner/web/templates/index.html,sha256=Gv3CJvNnhAzIVV_QxhNq4OD-pXDt1vKCu9k6WdHSXuA,215343
67
69
  GameSentenceMiner/web/templates/text_replacements.html,sha256=tV5c8mCaWSt_vKuUpbdbLAzXZ3ATZeDvQ9PnnAfqY0M,8598
68
70
  GameSentenceMiner/web/templates/utility.html,sha256=3flZinKNqUJ7pvrZk6xu__v67z44rXnaK7UTZ303R-8,16946
69
- GameSentenceMiner/wip/get_overlay_coords.py,sha256=IK3ZLKOygHmk02bUGl7GDQF7LV_FEDmSMWRniWGPvBk,20435
70
- gamesentenceminer-2.13.15.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
71
- gamesentenceminer-2.13.15.dist-info/METADATA,sha256=ovhoy-qvPB2qkIRV9pqrj3DkRg9nZbAPikRNBABW9os,1464
72
- gamesentenceminer-2.13.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
73
- gamesentenceminer-2.13.15.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
74
- gamesentenceminer-2.13.15.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
75
- gamesentenceminer-2.13.15.dist-info/RECORD,,
71
+ GameSentenceMiner/wip/__init___.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
+ gamesentenceminer-2.14.0rc1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
73
+ gamesentenceminer-2.14.0rc1.dist-info/METADATA,sha256=Dtctsp_KYbHfrXer_6_pAzok5waimQ_OKsBPzCzQKZ8,1567
74
+ gamesentenceminer-2.14.0rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
75
+ gamesentenceminer-2.14.0rc1.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
76
+ gamesentenceminer-2.14.0rc1.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
77
+ gamesentenceminer-2.14.0rc1.dist-info/RECORD,,
@@ -1,37 +0,0 @@
1
- from importlib import metadata
2
-
3
- import requests
4
-
5
- from GameSentenceMiner.util.configuration import logger
6
-
7
- PACKAGE_NAME = "GameSentenceMiner"
8
-
9
- def get_current_version():
10
- try:
11
- version = metadata.version(PACKAGE_NAME)
12
- return version
13
- except metadata.PackageNotFoundError:
14
- return None
15
-
16
- def get_latest_version():
17
- try:
18
- response = requests.get(f"https://pypi.org/pypi/{PACKAGE_NAME}/json")
19
- latest_version = response.json()["info"]["version"]
20
- return latest_version
21
- except Exception as e:
22
- logger.error(f"Error fetching latest version: {e}")
23
- return None
24
-
25
- def check_for_updates(force=False):
26
- try:
27
- installed_version = get_current_version()
28
- latest_version = get_latest_version()
29
-
30
- if installed_version != latest_version or force:
31
- logger.info(f"Update available: {installed_version} -> {latest_version}")
32
- return True, latest_version
33
- else:
34
- logger.info("You are already using the latest version.")
35
- return False, latest_version
36
- except Exception as e:
37
- logger.error(f"Error checking for updates: {e}")