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.
- GameSentenceMiner/ai/ai_prompting.py +77 -132
- GameSentenceMiner/anki.py +48 -6
- GameSentenceMiner/config_gui.py +196 -30
- GameSentenceMiner/gametext.py +8 -19
- GameSentenceMiner/gsm.py +5 -4
- GameSentenceMiner/locales/en_us.json +21 -11
- GameSentenceMiner/locales/ja_jp.json +21 -11
- GameSentenceMiner/locales/zh_cn.json +9 -11
- GameSentenceMiner/owocr/owocr/ocr.py +20 -23
- GameSentenceMiner/tools/__init__.py +0 -0
- GameSentenceMiner/util/configuration.py +241 -105
- GameSentenceMiner/util/db.py +408 -0
- GameSentenceMiner/util/ffmpeg.py +2 -10
- GameSentenceMiner/util/get_overlay_coords.py +324 -0
- GameSentenceMiner/util/model.py +8 -2
- GameSentenceMiner/util/text_log.py +1 -1
- GameSentenceMiner/web/texthooking_page.py +1 -1
- GameSentenceMiner/wip/__init___.py +0 -0
- {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/METADATA +5 -1
- {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/RECORD +27 -25
- GameSentenceMiner/util/package.py +0 -37
- GameSentenceMiner/wip/get_overlay_coords.py +0 -535
- /GameSentenceMiner/{util → tools}/audio_offset_selector.py +0 -0
- /GameSentenceMiner/{util → tools}/ss_selector.py +0 -0
- /GameSentenceMiner/{util → tools}/window_transparency.py +0 -0
- {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/licenses/LICENSE +0 -0
- {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)
|
GameSentenceMiner/util/model.py
CHANGED
@@ -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,
|
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]
|
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
|
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('
|
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.
|
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=
|
3
|
-
GameSentenceMiner/config_gui.py,sha256=
|
4
|
-
GameSentenceMiner/gametext.py,sha256=
|
5
|
-
GameSentenceMiner/gsm.py,sha256=
|
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=
|
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=
|
19
|
-
GameSentenceMiner/locales/ja_jp.json,sha256=
|
20
|
-
GameSentenceMiner/locales/zh_cn.json,sha256=
|
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=
|
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/
|
36
|
-
GameSentenceMiner/util/
|
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=
|
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=
|
45
|
+
GameSentenceMiner/util/model.py,sha256=R-_RYTYLSDNgBoVTPuPBcIHeOznIqi_vBzQ7VQ20WYk,6727
|
41
46
|
GameSentenceMiner/util/notification.py,sha256=-qk3kTKEERzmMxx5XMh084HCyFmbfqz0XjY1hTKhCeQ,4202
|
42
|
-
GameSentenceMiner/util/
|
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=
|
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/
|
70
|
-
gamesentenceminer-2.
|
71
|
-
gamesentenceminer-2.
|
72
|
-
gamesentenceminer-2.
|
73
|
-
gamesentenceminer-2.
|
74
|
-
gamesentenceminer-2.
|
75
|
-
gamesentenceminer-2.
|
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}")
|