GameSentenceMiner 2.14.8__py3-none-any.whl → 2.14.9__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/config_gui.py +19 -10
- GameSentenceMiner/gsm.py +68 -8
- GameSentenceMiner/locales/en_us.json +4 -0
- GameSentenceMiner/locales/ja_jp.json +4 -0
- GameSentenceMiner/locales/zh_cn.json +4 -0
- GameSentenceMiner/obs.py +12 -8
- {gamesentenceminer-2.14.8.dist-info → gamesentenceminer-2.14.9.dist-info}/METADATA +1 -2
- gamesentenceminer-2.14.9.dist-info/RECORD +24 -0
- GameSentenceMiner/ai/__init__.py +0 -0
- GameSentenceMiner/ai/ai_prompting.py +0 -473
- GameSentenceMiner/ocr/__init__.py +0 -0
- GameSentenceMiner/ocr/gsm_ocr_config.py +0 -174
- GameSentenceMiner/ocr/ocrconfig.py +0 -129
- GameSentenceMiner/ocr/owocr_area_selector.py +0 -629
- GameSentenceMiner/ocr/owocr_helper.py +0 -638
- GameSentenceMiner/ocr/ss_picker.py +0 -140
- GameSentenceMiner/owocr/owocr/__init__.py +0 -1
- GameSentenceMiner/owocr/owocr/__main__.py +0 -9
- GameSentenceMiner/owocr/owocr/config.py +0 -148
- GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -1238
- GameSentenceMiner/owocr/owocr/ocr.py +0 -1690
- GameSentenceMiner/owocr/owocr/run.py +0 -1817
- GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -109
- GameSentenceMiner/tools/__init__.py +0 -0
- GameSentenceMiner/tools/audio_offset_selector.py +0 -215
- GameSentenceMiner/tools/ss_selector.py +0 -135
- GameSentenceMiner/tools/window_transparency.py +0 -214
- GameSentenceMiner/util/__init__.py +0 -0
- GameSentenceMiner/util/communication/__init__.py +0 -22
- GameSentenceMiner/util/communication/send.py +0 -7
- GameSentenceMiner/util/communication/websocket.py +0 -94
- GameSentenceMiner/util/configuration.py +0 -1198
- GameSentenceMiner/util/db.py +0 -408
- GameSentenceMiner/util/downloader/Untitled_json.py +0 -472
- GameSentenceMiner/util/downloader/__init__.py +0 -0
- GameSentenceMiner/util/downloader/download_tools.py +0 -194
- GameSentenceMiner/util/downloader/oneocr_dl.py +0 -250
- GameSentenceMiner/util/electron_config.py +0 -259
- GameSentenceMiner/util/ffmpeg.py +0 -571
- GameSentenceMiner/util/get_overlay_coords.py +0 -366
- GameSentenceMiner/util/gsm_utils.py +0 -323
- GameSentenceMiner/util/model.py +0 -206
- GameSentenceMiner/util/notification.py +0 -147
- GameSentenceMiner/util/text_log.py +0 -214
- GameSentenceMiner/web/__init__.py +0 -0
- GameSentenceMiner/web/service.py +0 -132
- GameSentenceMiner/web/static/__init__.py +0 -0
- GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
- GameSentenceMiner/web/static/favicon-96x96.png +0 -0
- GameSentenceMiner/web/static/favicon.ico +0 -0
- GameSentenceMiner/web/static/favicon.svg +0 -3
- GameSentenceMiner/web/static/site.webmanifest +0 -21
- GameSentenceMiner/web/static/style.css +0 -292
- GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
- GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
- GameSentenceMiner/web/templates/__init__.py +0 -0
- GameSentenceMiner/web/templates/index.html +0 -50
- GameSentenceMiner/web/templates/text_replacements.html +0 -238
- GameSentenceMiner/web/templates/utility.html +0 -483
- GameSentenceMiner/web/texthooking_page.py +0 -584
- GameSentenceMiner/wip/__init___.py +0 -0
- gamesentenceminer-2.14.8.dist-info/RECORD +0 -77
- {gamesentenceminer-2.14.8.dist-info → gamesentenceminer-2.14.9.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.14.8.dist-info → gamesentenceminer-2.14.9.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.14.8.dist-info → gamesentenceminer-2.14.9.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.14.8.dist-info → gamesentenceminer-2.14.9.dist-info}/top_level.txt +0 -0
@@ -1,584 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
from ctypes.util import test
|
3
|
-
import datetime
|
4
|
-
import json
|
5
|
-
import os
|
6
|
-
import queue
|
7
|
-
import sqlite3
|
8
|
-
import threading
|
9
|
-
from dataclasses import dataclass
|
10
|
-
|
11
|
-
import flask
|
12
|
-
import websockets
|
13
|
-
|
14
|
-
from GameSentenceMiner.ai.ai_prompting import get_ai_prompt_result
|
15
|
-
from GameSentenceMiner.obs import get_current_game
|
16
|
-
from GameSentenceMiner.util.gsm_utils import TEXT_REPLACEMENTS_FILE
|
17
|
-
from GameSentenceMiner.util.text_log import GameLine, get_line_by_id, initial_time, get_all_lines
|
18
|
-
from flask import request, jsonify, send_from_directory
|
19
|
-
import webbrowser
|
20
|
-
from GameSentenceMiner import obs
|
21
|
-
from GameSentenceMiner.util.configuration import logger, get_config, DB_PATH, gsm_state, gsm_status
|
22
|
-
from GameSentenceMiner.web.service import handle_texthooker_button
|
23
|
-
|
24
|
-
port = get_config().general.texthooker_port
|
25
|
-
url = f"http://localhost:{port}"
|
26
|
-
websocket_port = 55001
|
27
|
-
|
28
|
-
|
29
|
-
@dataclass
|
30
|
-
class EventItem:
|
31
|
-
line: 'GameLine'
|
32
|
-
id: str
|
33
|
-
text: str
|
34
|
-
time: datetime.datetime
|
35
|
-
checked: bool = False
|
36
|
-
history: bool = False
|
37
|
-
|
38
|
-
def to_dict(self):
|
39
|
-
return {
|
40
|
-
'id': self.id,
|
41
|
-
'text': self.text,
|
42
|
-
'time': self.time,
|
43
|
-
'checked': self.checked,
|
44
|
-
'history': self.history,
|
45
|
-
}
|
46
|
-
|
47
|
-
def to_serializable(self):
|
48
|
-
return {
|
49
|
-
'id': self.id,
|
50
|
-
'text': self.text,
|
51
|
-
'time': self.time.isoformat(),
|
52
|
-
'checked': self.checked,
|
53
|
-
'history': self.history,
|
54
|
-
}
|
55
|
-
|
56
|
-
|
57
|
-
class EventManager:
|
58
|
-
events: list[EventItem]
|
59
|
-
events_dict: dict[str, EventItem] = {}
|
60
|
-
|
61
|
-
def __init__(self):
|
62
|
-
self.events = []
|
63
|
-
self.ids = []
|
64
|
-
self.events_dict = {}
|
65
|
-
self._connect()
|
66
|
-
self._create_table()
|
67
|
-
self._load_events_from_db()
|
68
|
-
# self.close_connection()
|
69
|
-
|
70
|
-
def _connect(self):
|
71
|
-
self.conn = sqlite3.connect(DB_PATH)
|
72
|
-
self.cursor = self.conn.cursor()
|
73
|
-
|
74
|
-
def _create_table(self):
|
75
|
-
self.cursor.execute("""
|
76
|
-
CREATE TABLE IF NOT EXISTS events (
|
77
|
-
event_id TEXT PRIMARY KEY,
|
78
|
-
line_id TEXT,
|
79
|
-
text TEXT,
|
80
|
-
time TEXT
|
81
|
-
)
|
82
|
-
""")
|
83
|
-
self.conn.commit()
|
84
|
-
|
85
|
-
def _load_events_from_db(self):
|
86
|
-
self.cursor.execute("SELECT * FROM events")
|
87
|
-
rows = self.cursor.fetchall()
|
88
|
-
for row in rows:
|
89
|
-
event_id, line_id, text, timestamp = row
|
90
|
-
timestamp = datetime.datetime.fromisoformat(timestamp)
|
91
|
-
line = GameLine(line_id, text, timestamp, None, None, 0)
|
92
|
-
event = EventItem(line, event_id, text, timestamp,
|
93
|
-
False, timestamp < initial_time)
|
94
|
-
self.events.append(event)
|
95
|
-
self.ids.append(event_id)
|
96
|
-
self.events_dict[event_id] = event
|
97
|
-
|
98
|
-
def __iter__(self):
|
99
|
-
return iter(self.events)
|
100
|
-
|
101
|
-
def replace_events(self, new_events: list[EventItem]):
|
102
|
-
self.events = new_events
|
103
|
-
|
104
|
-
def add_gameline(self, line: GameLine):
|
105
|
-
new_event = EventItem(line, line.id, line.text,
|
106
|
-
line.time, False, False)
|
107
|
-
self.events_dict[line.id] = new_event
|
108
|
-
self.ids.append(line.id)
|
109
|
-
self.events.append(new_event)
|
110
|
-
# self.store_to_db(new_event)
|
111
|
-
# event_queue.put(new_event)
|
112
|
-
return new_event
|
113
|
-
|
114
|
-
def reset_checked_lines(self):
|
115
|
-
for event in self.events:
|
116
|
-
event.checked = False
|
117
|
-
|
118
|
-
def get_events(self):
|
119
|
-
return self.events
|
120
|
-
|
121
|
-
def add_event(self, event):
|
122
|
-
self.events.append(event)
|
123
|
-
self.ids.append(event.id)
|
124
|
-
event_queue.put(event)
|
125
|
-
|
126
|
-
def get(self, event_id):
|
127
|
-
return self.events_dict.get(event_id)
|
128
|
-
|
129
|
-
def get_ids(self):
|
130
|
-
return self.ids
|
131
|
-
|
132
|
-
def close_connection(self):
|
133
|
-
if self.conn:
|
134
|
-
self.conn.close()
|
135
|
-
|
136
|
-
def clear_history(self):
|
137
|
-
self.cursor.execute("DELETE FROM events WHERE time < ?",
|
138
|
-
(initial_time.isoformat(),))
|
139
|
-
logger.info(f"Cleared history before {initial_time.isoformat()}")
|
140
|
-
self.conn.commit()
|
141
|
-
# Clear the in-memory events as well
|
142
|
-
event_manager.events = [
|
143
|
-
event for event in event_manager if not event.history]
|
144
|
-
event_manager.events_dict = {
|
145
|
-
event.id: event for event in event_manager.events}
|
146
|
-
|
147
|
-
|
148
|
-
class EventProcessor(threading.Thread):
|
149
|
-
def __init__(self, event_queue, db_path):
|
150
|
-
super().__init__()
|
151
|
-
self.event_queue = event_queue
|
152
|
-
self.db_path = db_path
|
153
|
-
self.conn = None
|
154
|
-
self.cursor = None
|
155
|
-
self.daemon = True
|
156
|
-
|
157
|
-
def _connect(self):
|
158
|
-
self.conn = sqlite3.connect(self.db_path)
|
159
|
-
self.cursor = self.conn.cursor()
|
160
|
-
|
161
|
-
def run(self):
|
162
|
-
self._connect()
|
163
|
-
while True:
|
164
|
-
try:
|
165
|
-
event = self.event_queue.get()
|
166
|
-
if event is None: # Exit signal
|
167
|
-
break
|
168
|
-
self._store_to_db(event)
|
169
|
-
except Exception as e:
|
170
|
-
logger.error(f"Error processing event: {e}")
|
171
|
-
self._close_connection()
|
172
|
-
|
173
|
-
def _store_to_db(self, event):
|
174
|
-
self.cursor.execute("""
|
175
|
-
INSERT INTO events (event_id, line_id, text, time)
|
176
|
-
VALUES (?, ?, ?, ?)
|
177
|
-
""", (event.id, event.line.id, event.text, event.time.isoformat()))
|
178
|
-
self.conn.commit()
|
179
|
-
|
180
|
-
def _close_connection(self):
|
181
|
-
if self.conn:
|
182
|
-
self.conn.close()
|
183
|
-
|
184
|
-
|
185
|
-
event_manager = EventManager()
|
186
|
-
event_queue = queue.Queue()
|
187
|
-
|
188
|
-
# Initialize the EventProcessor with the queue and event manager
|
189
|
-
event_processor = EventProcessor(event_queue, DB_PATH)
|
190
|
-
event_processor.start()
|
191
|
-
|
192
|
-
server_start_time = datetime.datetime.now().timestamp()
|
193
|
-
|
194
|
-
app = flask.Flask(__name__)
|
195
|
-
|
196
|
-
# Load data from the JSON file
|
197
|
-
|
198
|
-
|
199
|
-
def load_data_from_file():
|
200
|
-
if os.path.exists(TEXT_REPLACEMENTS_FILE):
|
201
|
-
with open(TEXT_REPLACEMENTS_FILE, 'r', encoding='utf-8') as file:
|
202
|
-
return json.load(file)
|
203
|
-
return {"enabled": True, "args": {"replacements": {}}}
|
204
|
-
|
205
|
-
# Save data to the JSON file
|
206
|
-
|
207
|
-
|
208
|
-
def save_data_to_file(data):
|
209
|
-
with open(TEXT_REPLACEMENTS_FILE, 'w', encoding='utf-8') as file:
|
210
|
-
json.dump(data, file, indent=4, ensure_ascii=False)
|
211
|
-
|
212
|
-
|
213
|
-
@app.route('/load-data', methods=['GET'])
|
214
|
-
def load_data():
|
215
|
-
try:
|
216
|
-
data = load_data_from_file()
|
217
|
-
return jsonify(data), 200
|
218
|
-
except Exception as e:
|
219
|
-
return jsonify({"error": f"Failed to load data: {str(e)}"}), 500
|
220
|
-
|
221
|
-
|
222
|
-
@app.route('/save-data', methods=['POST'])
|
223
|
-
def save_data():
|
224
|
-
try:
|
225
|
-
data = request.get_json()
|
226
|
-
if not isinstance(data, dict):
|
227
|
-
return jsonify({"error": "Invalid data format"}), 400
|
228
|
-
|
229
|
-
# Save updated data
|
230
|
-
save_data_to_file(data)
|
231
|
-
return jsonify({"message": "Data saved successfully"}), 200
|
232
|
-
except Exception as e:
|
233
|
-
return jsonify({"error": f"Failed to save data: {str(e)}"}), 500
|
234
|
-
|
235
|
-
|
236
|
-
def inject_server_start_time(html_content, timestamp):
|
237
|
-
placeholder = '<script>'
|
238
|
-
replacement = f'<script>const serverStartTime = {timestamp};'
|
239
|
-
return html_content.replace(placeholder, replacement)
|
240
|
-
|
241
|
-
|
242
|
-
@app.route('/favicon.ico')
|
243
|
-
def favicon():
|
244
|
-
return send_from_directory(os.path.join(app.root_path, 'static'),
|
245
|
-
'favicon.ico', mimetype='image/vnd.microsoft.icon')
|
246
|
-
|
247
|
-
|
248
|
-
@app.route('/<path:filename>')
|
249
|
-
def serve_static(filename):
|
250
|
-
return send_from_directory('pages', filename)
|
251
|
-
|
252
|
-
|
253
|
-
@app.route('/')
|
254
|
-
def index():
|
255
|
-
return send_from_directory('templates', 'index.html')
|
256
|
-
|
257
|
-
|
258
|
-
@app.route('/texthooker')
|
259
|
-
def texthooker():
|
260
|
-
return send_from_directory('templates', 'index.html')
|
261
|
-
|
262
|
-
|
263
|
-
@app.route('/textreplacements')
|
264
|
-
def textreplacements():
|
265
|
-
return flask.render_template('text_replacements.html')
|
266
|
-
|
267
|
-
|
268
|
-
@app.route('/data', methods=['GET'])
|
269
|
-
def get_data():
|
270
|
-
return jsonify([event.to_dict() for event in event_manager])
|
271
|
-
|
272
|
-
|
273
|
-
@app.route('/get_ids', methods=['GET'])
|
274
|
-
def get_ids():
|
275
|
-
return jsonify(event_manager.get_ids())
|
276
|
-
|
277
|
-
|
278
|
-
@app.route('/clear_history', methods=['POST'])
|
279
|
-
def clear_history():
|
280
|
-
temp_em = EventManager()
|
281
|
-
temp_em.clear_history()
|
282
|
-
temp_em.close_connection()
|
283
|
-
return jsonify({'message': 'History cleared successfully'}), 200
|
284
|
-
|
285
|
-
|
286
|
-
async def add_event_to_texthooker(line: GameLine):
|
287
|
-
new_event = event_manager.add_gameline(line)
|
288
|
-
await websocket_server_thread.send_text({
|
289
|
-
'event': 'text_received',
|
290
|
-
'sentence': line.text,
|
291
|
-
'data': new_event.to_serializable()
|
292
|
-
})
|
293
|
-
if get_config().advanced.plaintext_websocket_port:
|
294
|
-
await plaintext_websocket_server_thread.send_text(line.text)
|
295
|
-
|
296
|
-
|
297
|
-
async def send_word_coordinates_to_overlay(boxes):
|
298
|
-
if boxes and len(boxes) > 0 and overlay_server_thread:
|
299
|
-
await overlay_server_thread.send_text(boxes)
|
300
|
-
|
301
|
-
|
302
|
-
@app.route('/update_checkbox', methods=['POST'])
|
303
|
-
def update_event():
|
304
|
-
data = request.get_json()
|
305
|
-
event_id = data.get('id')
|
306
|
-
|
307
|
-
if event_id is None:
|
308
|
-
return jsonify({'error': 'Missing id'}), 400
|
309
|
-
event = event_manager.get(event_id)
|
310
|
-
event_manager.get(event_id).checked = not event.checked
|
311
|
-
return jsonify({'message': 'Event updated successfully'}), 200
|
312
|
-
|
313
|
-
|
314
|
-
@app.route('/get-screenshot', methods=['Post'])
|
315
|
-
def get_screenshot():
|
316
|
-
"""Endpoint to get a screenshot of the current game screen."""
|
317
|
-
data = request.get_json()
|
318
|
-
event_id = data.get('id')
|
319
|
-
if event_id is None:
|
320
|
-
return jsonify({'error': 'Missing id'}), 400
|
321
|
-
gsm_state.line_for_screenshot = get_line_by_id(event_id)
|
322
|
-
if gsm_state.previous_line_for_screenshot and gsm_state.line_for_screenshot == gsm_state.previous_line_for_screenshot or gsm_state.previous_line_for_audio and gsm_state.line_for_screenshot == gsm_state.previous_line_for_audio:
|
323
|
-
handle_texthooker_button(gsm_state.previous_replay)
|
324
|
-
else:
|
325
|
-
obs.save_replay_buffer()
|
326
|
-
return jsonify({}), 200
|
327
|
-
|
328
|
-
|
329
|
-
@app.route('/play-audio', methods=['POST'])
|
330
|
-
def play_audio():
|
331
|
-
"""Endpoint to play audio for a specific event."""
|
332
|
-
data = request.get_json()
|
333
|
-
event_id = data.get('id')
|
334
|
-
if event_id is None:
|
335
|
-
return jsonify({'error': 'Missing id'}), 400
|
336
|
-
print(f"Playing audio for event ID: {event_id}")
|
337
|
-
gsm_state.line_for_audio = get_line_by_id(event_id)
|
338
|
-
print(f"gsm_state.line_for_audio: {gsm_state.line_for_audio}")
|
339
|
-
if gsm_state.previous_line_for_audio and gsm_state.line_for_audio == gsm_state.previous_line_for_audio or gsm_state.previous_line_for_screenshot and gsm_state.line_for_audio == gsm_state.previous_line_for_screenshot:
|
340
|
-
handle_texthooker_button(gsm_state.previous_replay)
|
341
|
-
else:
|
342
|
-
obs.save_replay_buffer()
|
343
|
-
return jsonify({}), 200
|
344
|
-
|
345
|
-
|
346
|
-
@app.route("/translate-line", methods=['POST'])
|
347
|
-
def translate_line():
|
348
|
-
data = request.get_json()
|
349
|
-
event_id = data.get('id')
|
350
|
-
if event_id is None:
|
351
|
-
return jsonify({'error': 'Missing id'}), 400
|
352
|
-
line_to_translate = get_line_by_id(event_id)
|
353
|
-
translation = get_ai_prompt_result(get_all_lines(), line_to_translate.text,
|
354
|
-
line_to_translate, get_current_game())
|
355
|
-
line_to_translate.set_TL(translation)
|
356
|
-
return jsonify({'TL': translation}), 200
|
357
|
-
|
358
|
-
@app.route('/translate-multiple', methods=['POST'])
|
359
|
-
def translate_multiple():
|
360
|
-
data = request.get_json()
|
361
|
-
event_ids = data.get('ids', [])
|
362
|
-
if not event_ids:
|
363
|
-
return jsonify({'error': 'Missing ids'}), 400
|
364
|
-
|
365
|
-
lines = [get_line_by_id(event_id) for event_id in event_ids if get_line_by_id(event_id) is not None]
|
366
|
-
|
367
|
-
text = "\n".join(line.text for line in lines)
|
368
|
-
|
369
|
-
translate_multiple_lines_prompt = f"""
|
370
|
-
**Professional Game Localization Task**
|
371
|
-
Translate the following lines of game dialogue into natural-sounding, context-aware {get_config().general.get_native_language_name()}:
|
372
|
-
|
373
|
-
**Output Requirements**
|
374
|
-
- Maintain the original tone and style of the dialogue.
|
375
|
-
- Ensure that the translation is contextually appropriate for the game.
|
376
|
-
- Pay attention to character names and any specific terminology used in the game.
|
377
|
-
- Maintain Formatting and newline structure of the given lines. It should be very human readable as a dialogue.
|
378
|
-
- Do not include any notes, alternatives, explanations, or any other surrounding text. Absolutely nothing but the translated lines.
|
379
|
-
|
380
|
-
**Lines to Translate:**
|
381
|
-
"""
|
382
|
-
|
383
|
-
translation = get_ai_prompt_result(get_all_lines(), text,
|
384
|
-
lines[0], get_current_game(), custom_prompt=translate_multiple_lines_prompt)
|
385
|
-
|
386
|
-
return translation, 200
|
387
|
-
|
388
|
-
@app.route('/get_status', methods=['GET'])
|
389
|
-
def get_status():
|
390
|
-
return jsonify(gsm_status.to_dict()), 200
|
391
|
-
|
392
|
-
|
393
|
-
# async def main():
|
394
|
-
# async with websockets.serve(websocket_handler, "localhost", 8765): # Choose a port for WebSocket
|
395
|
-
# print("WebSocket server started on ws://localhost:8765/ws (adjust as needed)")
|
396
|
-
# await asyncio.Future() # Keep the server running
|
397
|
-
|
398
|
-
# @app.route('/store-events', methods=['POST'])
|
399
|
-
# def store_events():
|
400
|
-
# data = request.get_json()
|
401
|
-
# events_data = data.get('events', [])
|
402
|
-
#
|
403
|
-
# if not isinstance(events_data, list):
|
404
|
-
# return jsonify({'error': 'Invalid data format. Expected an array of events.'}), 400
|
405
|
-
#
|
406
|
-
# for event_data in events_data:
|
407
|
-
# if not all(k in event_data for k in ('id', 'text', 'time', 'checked')):
|
408
|
-
# return jsonify({'error': 'Invalid event structure. Missing keys.'}), 400
|
409
|
-
# if not (isinstance(event_data['id'], (int, float)) and
|
410
|
-
# isinstance(event_data['text'], str) and
|
411
|
-
# isinstance(event_data['time'], str) and
|
412
|
-
# isinstance(event_data['checked'], bool)):
|
413
|
-
# return jsonify({'error': 'Invalid event structure. Incorrect data types.'}), 400
|
414
|
-
#
|
415
|
-
# event_manager.replace_events([EventItem(item['id'], item['text'], item['time'], item.get(['timestamp'], 0), item['checked']) for item in data])
|
416
|
-
#
|
417
|
-
# return jsonify({'message': 'Events successfully stored on server.', 'receivedEvents': data}), 200
|
418
|
-
|
419
|
-
|
420
|
-
def get_selected_lines():
|
421
|
-
return [item.line for item in event_manager if item.checked]
|
422
|
-
|
423
|
-
|
424
|
-
def are_lines_selected():
|
425
|
-
return any(item.checked for item in event_manager)
|
426
|
-
|
427
|
-
|
428
|
-
def reset_checked_lines():
|
429
|
-
async def send_reset_message():
|
430
|
-
await websocket_server_thread.send_text({
|
431
|
-
'event': 'reset_checkboxes',
|
432
|
-
})
|
433
|
-
event_manager.reset_checked_lines()
|
434
|
-
asyncio.run(send_reset_message())
|
435
|
-
|
436
|
-
|
437
|
-
def open_texthooker():
|
438
|
-
webbrowser.open(url + '/texthooker')
|
439
|
-
|
440
|
-
|
441
|
-
def start_web_server():
|
442
|
-
logger.debug("Starting web server...")
|
443
|
-
import logging
|
444
|
-
log = logging.getLogger('werkzeug')
|
445
|
-
log.setLevel(logging.ERROR) # Set to ERROR to suppress most logs
|
446
|
-
|
447
|
-
# Open the default browser
|
448
|
-
if get_config().general.open_multimine_on_startup:
|
449
|
-
open_texthooker()
|
450
|
-
|
451
|
-
# debug=True provides helpful error messages during development
|
452
|
-
app.run(host='0.0.0.0', port=port, debug=False)
|
453
|
-
|
454
|
-
|
455
|
-
websocket_queue = queue.Queue()
|
456
|
-
paused = False
|
457
|
-
|
458
|
-
|
459
|
-
class WebsocketServerThread(threading.Thread):
|
460
|
-
def __init__(self, read, get_ws_port_func):
|
461
|
-
super().__init__(daemon=True)
|
462
|
-
self._loop = None
|
463
|
-
self.read = read
|
464
|
-
self.clients = set()
|
465
|
-
self._event = threading.Event()
|
466
|
-
self.get_ws_port_func = get_ws_port_func
|
467
|
-
self.backedup_text = []
|
468
|
-
|
469
|
-
@property
|
470
|
-
def loop(self):
|
471
|
-
self._event.wait()
|
472
|
-
return self._loop
|
473
|
-
|
474
|
-
async def send_text_coroutine(self, message):
|
475
|
-
if not self.clients:
|
476
|
-
self.backedup_text.append(message)
|
477
|
-
return
|
478
|
-
for client in self.clients:
|
479
|
-
await client.send(message)
|
480
|
-
|
481
|
-
async def server_handler(self, websocket):
|
482
|
-
self.clients.add(websocket)
|
483
|
-
try:
|
484
|
-
if self.backedup_text:
|
485
|
-
for message in self.backedup_text:
|
486
|
-
await websocket.send(message)
|
487
|
-
self.backedup_text.clear()
|
488
|
-
async for message in websocket:
|
489
|
-
if self.read and not paused:
|
490
|
-
websocket_queue.put(message)
|
491
|
-
try:
|
492
|
-
await websocket.send('True')
|
493
|
-
except websockets.exceptions.ConnectionClosedOK:
|
494
|
-
pass
|
495
|
-
else:
|
496
|
-
try:
|
497
|
-
await websocket.send('False')
|
498
|
-
except websockets.exceptions.ConnectionClosedOK:
|
499
|
-
pass
|
500
|
-
except websockets.exceptions.ConnectionClosedError:
|
501
|
-
pass
|
502
|
-
finally:
|
503
|
-
self.clients.remove(websocket)
|
504
|
-
|
505
|
-
async def send_text(self, text):
|
506
|
-
if text:
|
507
|
-
if isinstance(text, dict) or isinstance(text, list):
|
508
|
-
text = json.dumps(text)
|
509
|
-
return asyncio.run_coroutine_threadsafe(
|
510
|
-
self.send_text_coroutine(text), self.loop)
|
511
|
-
|
512
|
-
def has_clients(self):
|
513
|
-
return len(self.clients) > 0
|
514
|
-
|
515
|
-
def stop_server(self):
|
516
|
-
self.loop.call_soon_threadsafe(self._stop_event.set)
|
517
|
-
|
518
|
-
def run(self):
|
519
|
-
async def main():
|
520
|
-
self._loop = asyncio.get_running_loop()
|
521
|
-
self._stop_event = stop_event = asyncio.Event()
|
522
|
-
self._event.set()
|
523
|
-
while True:
|
524
|
-
try:
|
525
|
-
self.server = start_server = websockets.serve(self.server_handler,
|
526
|
-
"0.0.0.0",
|
527
|
-
self.get_ws_port_func(),
|
528
|
-
max_size=1000000000)
|
529
|
-
async with start_server:
|
530
|
-
await stop_event.wait()
|
531
|
-
return
|
532
|
-
except Exception as e:
|
533
|
-
logger.warning(
|
534
|
-
f"WebSocket server encountered an error: {e}. Retrying...")
|
535
|
-
await asyncio.sleep(1)
|
536
|
-
|
537
|
-
asyncio.run(main())
|
538
|
-
|
539
|
-
|
540
|
-
def handle_exit_signal(loop):
|
541
|
-
logger.info("Received exit signal. Shutting down...")
|
542
|
-
for task in asyncio.all_tasks(loop):
|
543
|
-
task.cancel()
|
544
|
-
|
545
|
-
|
546
|
-
websocket_server_thread = WebsocketServerThread(read=True, get_ws_port_func=lambda: get_config(
|
547
|
-
).get_field_value('advanced', 'texthooker_communication_websocket_port'))
|
548
|
-
websocket_server_thread.start()
|
549
|
-
|
550
|
-
if get_config().advanced.plaintext_websocket_port:
|
551
|
-
plaintext_websocket_server_thread = WebsocketServerThread(
|
552
|
-
read=False, get_ws_port_func=lambda: get_config().get_field_value('advanced', 'plaintext_websocket_port'))
|
553
|
-
plaintext_websocket_server_thread.start()
|
554
|
-
|
555
|
-
overlay_server_thread = WebsocketServerThread(
|
556
|
-
read=False, get_ws_port_func=lambda: get_config().get_field_value('overlay', 'websocket_port'))
|
557
|
-
overlay_server_thread.start()
|
558
|
-
|
559
|
-
websocket_server_threads = [
|
560
|
-
websocket_server_thread,
|
561
|
-
plaintext_websocket_server_thread,
|
562
|
-
overlay_server_thread
|
563
|
-
]
|
564
|
-
|
565
|
-
|
566
|
-
async def texthooker_page_coro():
|
567
|
-
global websocket_server_thread, plaintext_websocket_server_thread, overlay_server_thread
|
568
|
-
# Run the WebSocket server in the asyncio event loop
|
569
|
-
flask_thread = threading.Thread(target=start_web_server)
|
570
|
-
flask_thread.daemon = True
|
571
|
-
flask_thread.start()
|
572
|
-
|
573
|
-
# Keep the main asyncio event loop running (for the WebSocket server)
|
574
|
-
|
575
|
-
|
576
|
-
def run_text_hooker_page():
|
577
|
-
try:
|
578
|
-
asyncio.run(texthooker_page_coro())
|
579
|
-
except KeyboardInterrupt:
|
580
|
-
logger.info("Shutting down due to KeyboardInterrupt.")
|
581
|
-
|
582
|
-
|
583
|
-
if __name__ == '__main__':
|
584
|
-
asyncio.run(texthooker_page_coro())
|
File without changes
|
@@ -1,77 +0,0 @@
|
|
1
|
-
GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
GameSentenceMiner/anki.py,sha256=xTXCACDM_u9natCFnbqdxEX24F9bTsdoI7PoskWbdkk,23012
|
3
|
-
GameSentenceMiner/config_gui.py,sha256=dTYBe3ZI9tsOiOtT2WEMnodj3q5-U4Kj5l1l1qutlsw,137549
|
4
|
-
GameSentenceMiner/gametext.py,sha256=fgBgLchezpauWELE9Y5G3kVCLfAneD0X4lJFoI3FYbs,10351
|
5
|
-
GameSentenceMiner/gsm.py,sha256=GdqegZnKrTMVRvp43bK7oNlWj5OxLx2PNdVWyHL9Gc4,28282
|
6
|
-
GameSentenceMiner/obs.py,sha256=JmtO7Ojcm14Q8P_bz8TdS24rwDdwQasg2gnO2J0KcuQ,20598
|
7
|
-
GameSentenceMiner/vad.py,sha256=zFReBMvNEEaQ_YEozCTCaMdV-o40FwtlxYRb17cYZio,19125
|
8
|
-
GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
GameSentenceMiner/ai/ai_prompting.py,sha256=D1KyHER_sj4jYvOKGRbp8FkKZSKWzAaXHbpmZLLFctg,23998
|
10
|
-
GameSentenceMiner/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
-
GameSentenceMiner/assets/icon.png,sha256=9GRL8uXUAgkUSlvbm9Pv9o2poFVRGdW6s2ub_DeUD9M,937624
|
12
|
-
GameSentenceMiner/assets/icon128.png,sha256=l90j7biwdz5ahwOd5wZ-406ryEV9Pan93dquJQ3e1CI,18395
|
13
|
-
GameSentenceMiner/assets/icon256.png,sha256=JEW46wOrG1KR-907rvFaEdNbPtj5gu0HJmG7qUnIHxQ,51874
|
14
|
-
GameSentenceMiner/assets/icon32.png,sha256=Kww0hU_qke9_22wBuO_Nq0Dv2SfnOLwMhCyGgbgXdg8,6089
|
15
|
-
GameSentenceMiner/assets/icon512.png,sha256=HxUj2GHjyQsk8NV433256UxU9phPhtjCY-YB_7W4sqs,192487
|
16
|
-
GameSentenceMiner/assets/icon64.png,sha256=N8xgdZXvhqVQP9QUK3wX5iqxX9LxHljD7c-Bmgim6tM,9301
|
17
|
-
GameSentenceMiner/assets/pickaxe.png,sha256=VfIGyXyIZdzEnVcc4PmG3wszPMO1W4KCT7Q_nFK6eSE,1403829
|
18
|
-
GameSentenceMiner/locales/en_us.json,sha256=B3CF3VvJ1PVLP_2Xv34ZFNUVAF8SQ8qDlOD20EOOJmE,25948
|
19
|
-
GameSentenceMiner/locales/ja_jp.json,sha256=2I-aSs6KIbjJ7uNRA-dx_5oRP9JLTWWUIrMMZOkenlE,27520
|
20
|
-
GameSentenceMiner/locales/zh_cn.json,sha256=tx9Szm6KEMn_YaYDWS1ph4pGALYR3iguVru2Dfj7S3o,24047
|
21
|
-
GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
|
-
GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=QyBarjb6Wjt-oyor1sIT9hG-KrGdi6wODLyCrHXun6E,6008
|
23
|
-
GameSentenceMiner/ocr/ocrconfig.py,sha256=_tY8mjnzHMJrLS8E5pHqYXZjMuLoGKYgJwdhYgN-ny4,6466
|
24
|
-
GameSentenceMiner/ocr/owocr_area_selector.py,sha256=Rm1_nuZotJhfOfoJ_3mesh9udtOBjYqKhnAvSief6fo,29181
|
25
|
-
GameSentenceMiner/ocr/owocr_helper.py,sha256=6R6IbdwpjWmIrXnUvyQ_0kXRYhJe2tphqyapoxc_PsQ,28857
|
26
|
-
GameSentenceMiner/ocr/ss_picker.py,sha256=0IhxUdaKruFpZyBL-8SpxWg7bPrlGpy3lhTcMMZ5rwo,5224
|
27
|
-
GameSentenceMiner/owocr/owocr/__init__.py,sha256=87hfN5u_PbL_onLfMACbc0F5j4KyIK9lKnRCj6oZgR0,49
|
28
|
-
GameSentenceMiner/owocr/owocr/__main__.py,sha256=XQaqZY99EKoCpU-gWQjNbTs7Kg17HvBVE7JY8LqIE0o,157
|
29
|
-
GameSentenceMiner/owocr/owocr/config.py,sha256=qM7kISHdUhuygGXOxmgU6Ef2nwBShrZtdqu4InDCViE,8103
|
30
|
-
GameSentenceMiner/owocr/owocr/lens_betterproto.py,sha256=oNoISsPilVVRBBPVDtb4-roJtAhp8ZAuFTci3TGXtMc,39141
|
31
|
-
GameSentenceMiner/owocr/owocr/ocr.py,sha256=xU1PkkDkOZ36phzmaXdOrfw08zy0YkSNbKTgIDAqBYs,70556
|
32
|
-
GameSentenceMiner/owocr/owocr/run.py,sha256=-aRSIcwc-qIn2s-JVaAMGvzJHjvqw4LsLQKLtswEfcE,75570
|
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
|
38
|
-
GameSentenceMiner/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
39
|
-
GameSentenceMiner/util/configuration.py,sha256=NnKcMyFV4y6gB6AwFUP-B-tdfk0_Awh5bJ6FHtXr6Vk,40017
|
40
|
-
GameSentenceMiner/util/db.py,sha256=2bO0rD4i8A1hhsRBER-wgZy9IK17ibRbI8DHxdKvYsI,16598
|
41
|
-
GameSentenceMiner/util/electron_config.py,sha256=KfeJToeFFVw0IR5MKa-gBzpzaGrU-lyJbR9z-sDEHYU,8767
|
42
|
-
GameSentenceMiner/util/ffmpeg.py,sha256=iqsdp3TbBv6KMACJxkUF3e5VWak3jHPZdIEMrUdKFtE,23073
|
43
|
-
GameSentenceMiner/util/get_overlay_coords.py,sha256=VtrT25DOjqMgo-eRFRMovuq6LUnoY8GrpbkfDsvgSjM,13855
|
44
|
-
GameSentenceMiner/util/gsm_utils.py,sha256=Piwv88Q9av2LBeN7M6QDi0Mp0_R2lNbkcI6ekK5hd2o,11851
|
45
|
-
GameSentenceMiner/util/model.py,sha256=R-_RYTYLSDNgBoVTPuPBcIHeOznIqi_vBzQ7VQ20WYk,6727
|
46
|
-
GameSentenceMiner/util/notification.py,sha256=-qk3kTKEERzmMxx5XMh084HCyFmbfqz0XjY1hTKhCeQ,4202
|
47
|
-
GameSentenceMiner/util/text_log.py,sha256=zB9--7J_Wwck74IOEI4aWhmCYouqjSE6Sm0sCznF63Q,6731
|
48
|
-
GameSentenceMiner/util/communication/__init__.py,sha256=xh__yn2MhzXi9eLi89PeZWlJPn-cbBSjskhi1BRraXg,643
|
49
|
-
GameSentenceMiner/util/communication/send.py,sha256=Wki9qIY2CgYnuHbmnyKVIYkcKAN_oYS4up93XMikBaI,222
|
50
|
-
GameSentenceMiner/util/communication/websocket.py,sha256=TbphRGmxVrgEupS7tNdifsmQfWDfIp0Hio2cSiUKgsk,3317
|
51
|
-
GameSentenceMiner/util/downloader/Untitled_json.py,sha256=RUUl2bbbCpUDUUS0fP0tdvf5FngZ7ILdA_J5TFYAXUQ,15272
|
52
|
-
GameSentenceMiner/util/downloader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
53
|
-
GameSentenceMiner/util/downloader/download_tools.py,sha256=zR-aEHiFVkyo-9oPoSx6nQ2K-_J8WBHLZyLoOhypsW4,8458
|
54
|
-
GameSentenceMiner/util/downloader/oneocr_dl.py,sha256=l3s9Z-x1b57GX048o5h-MVv0UTZo4H-Q-zb-JREkMLI,10439
|
55
|
-
GameSentenceMiner/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
56
|
-
GameSentenceMiner/web/service.py,sha256=YZchmScTn7AX_GkwV1ULEK6qjdOnJcpc3qfMwDf7cUE,5363
|
57
|
-
GameSentenceMiner/web/texthooking_page.py,sha256=JhuApKUeQGwqGUO_92Z6yMY0_-kqHQceT4FGzhbGlGE,19269
|
58
|
-
GameSentenceMiner/web/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
59
|
-
GameSentenceMiner/web/static/apple-touch-icon.png,sha256=OcMI8af_68DA_tweOsQ5LytTyMwm7-hPW07IfrOVgEs,46132
|
60
|
-
GameSentenceMiner/web/static/favicon-96x96.png,sha256=lOePzjiKl1JY2J1kT_PMdyEnrlJmi5GWbmXJunM12B4,16502
|
61
|
-
GameSentenceMiner/web/static/favicon.ico,sha256=7d25r_FBqRSNsAoEHpSzNoT7zyVt2DJRLNDNq_HYoX8,15086
|
62
|
-
GameSentenceMiner/web/static/favicon.svg,sha256=x305AP6WlXGtrXIZlaQspdLmwteoFYUoe5FyJ9MYlJ8,11517
|
63
|
-
GameSentenceMiner/web/static/site.webmanifest,sha256=kaeNT-FjFt-T7JGzOhXH7YSqsrDeiplZ2kDxCN_CFU4,436
|
64
|
-
GameSentenceMiner/web/static/style.css,sha256=bPZK0NVMuyRl5NNDuT7ZTzVLKlvSsdmeVHmAW4y5FM0,7001
|
65
|
-
GameSentenceMiner/web/static/web-app-manifest-192x192.png,sha256=EfSNnBmsSaLfESbkGfYwbKzcjKOdzuWo18ABADfN974,51117
|
66
|
-
GameSentenceMiner/web/static/web-app-manifest-512x512.png,sha256=wyqgCWCrLEUxSRXmaA3iJEESd-vM-ZmlTtZFBY4V8Pk,230819
|
67
|
-
GameSentenceMiner/web/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
68
|
-
GameSentenceMiner/web/templates/index.html,sha256=VrhrkQKcOAPuoWRcilFLDXjvNf6WTOY8s_Cjj3vYdWY,216505
|
69
|
-
GameSentenceMiner/web/templates/text_replacements.html,sha256=tV5c8mCaWSt_vKuUpbdbLAzXZ3ATZeDvQ9PnnAfqY0M,8598
|
70
|
-
GameSentenceMiner/web/templates/utility.html,sha256=3flZinKNqUJ7pvrZk6xu__v67z44rXnaK7UTZ303R-8,16946
|
71
|
-
GameSentenceMiner/wip/__init___.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
72
|
-
gamesentenceminer-2.14.8.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
73
|
-
gamesentenceminer-2.14.8.dist-info/METADATA,sha256=vvvPFxb_AjD9_1NkCTKCj5E9feTflY5TgINHIyEOd-0,7330
|
74
|
-
gamesentenceminer-2.14.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
75
|
-
gamesentenceminer-2.14.8.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
76
|
-
gamesentenceminer-2.14.8.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
77
|
-
gamesentenceminer-2.14.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|