GameSentenceMiner 2.15.8__py3-none-any.whl → 2.15.10__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/ocr/gsm_ocr_config.py +1 -1
- GameSentenceMiner/ocr/owocr_helper.py +21 -12
- GameSentenceMiner/owocr/owocr/run.py +2 -2
- GameSentenceMiner/util/configuration.py +3 -0
- GameSentenceMiner/util/text_log.py +2 -2
- GameSentenceMiner/vad.py +1 -1
- GameSentenceMiner/web/database_api.py +783 -0
- GameSentenceMiner/web/events.py +178 -0
- GameSentenceMiner/web/stats.py +582 -0
- GameSentenceMiner/web/templates/database.html +277 -0
- GameSentenceMiner/web/templates/search.html +103 -0
- GameSentenceMiner/web/templates/stats.html +330 -0
- GameSentenceMiner/web/templates/text_replacements.html +211 -0
- GameSentenceMiner/web/templates/utility.html +2 -2
- GameSentenceMiner/web/texthooking_page.py +58 -316
- GameSentenceMiner/web/websockets.py +120 -0
- {gamesentenceminer-2.15.8.dist-info → gamesentenceminer-2.15.10.dist-info}/METADATA +1 -1
- {gamesentenceminer-2.15.8.dist-info → gamesentenceminer-2.15.10.dist-info}/RECORD +22 -16
- GameSentenceMiner/web/templates/__init__.py +0 -0
- {gamesentenceminer-2.15.8.dist-info → gamesentenceminer-2.15.10.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.15.8.dist-info → gamesentenceminer-2.15.10.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.15.8.dist-info → gamesentenceminer-2.15.10.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.15.8.dist-info → gamesentenceminer-2.15.10.dist-info}/top_level.txt +0 -0
@@ -1,201 +1,52 @@
|
|
1
1
|
import asyncio
|
2
|
-
from ctypes.util import test
|
3
2
|
import datetime
|
4
3
|
import json
|
5
4
|
import os
|
6
|
-
import queue
|
7
|
-
import sqlite3
|
8
5
|
import threading
|
9
|
-
from dataclasses import dataclass
|
10
6
|
|
11
7
|
import flask
|
12
|
-
import
|
8
|
+
import webbrowser
|
13
9
|
|
14
10
|
from GameSentenceMiner.ai.ai_prompting import get_ai_prompt_result
|
15
11
|
from GameSentenceMiner.obs import get_current_game
|
16
12
|
from GameSentenceMiner.util.gsm_utils import TEXT_REPLACEMENTS_FILE
|
17
|
-
from GameSentenceMiner.util.text_log import
|
18
|
-
from flask import request, jsonify, send_from_directory
|
19
|
-
import webbrowser
|
13
|
+
from GameSentenceMiner.util.text_log import get_line_by_id, get_all_lines
|
14
|
+
from flask import render_template, request, jsonify, send_from_directory
|
20
15
|
from GameSentenceMiner import obs
|
21
|
-
from GameSentenceMiner.util.configuration import logger, get_config,
|
16
|
+
from GameSentenceMiner.util.configuration import logger, get_config, gsm_state, gsm_status
|
22
17
|
from GameSentenceMiner.web.service import handle_texthooker_button
|
23
18
|
|
19
|
+
# Import from new modules
|
20
|
+
from GameSentenceMiner.web.events import (
|
21
|
+
EventItem, EventManager, EventProcessor, event_manager, event_queue, event_processor
|
22
|
+
)
|
23
|
+
from GameSentenceMiner.web.stats import (
|
24
|
+
is_kanji, interpolate_color, get_gradient_color, calculate_kanji_frequency,
|
25
|
+
calculate_heatmap_data, calculate_total_chars_per_game, calculate_reading_time_per_game,
|
26
|
+
calculate_reading_speed_per_game, generate_game_colors, format_large_number,
|
27
|
+
calculate_actual_reading_time, calculate_daily_reading_time, calculate_time_based_streak,
|
28
|
+
format_time_human_readable, calculate_current_game_stats, calculate_all_games_stats
|
29
|
+
)
|
30
|
+
from GameSentenceMiner.web.websockets import (
|
31
|
+
WebsocketServerThread, websocket_queue, paused, websocket_server_thread,
|
32
|
+
plaintext_websocket_server_thread, overlay_server_thread, websocket_server_threads,
|
33
|
+
handle_exit_signal
|
34
|
+
)
|
35
|
+
from GameSentenceMiner.web.database_api import register_database_api_routes
|
36
|
+
|
37
|
+
# Global configuration
|
24
38
|
port = get_config().general.texthooker_port
|
25
39
|
url = f"http://localhost:{port}"
|
26
40
|
websocket_port = 55001
|
27
41
|
|
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
42
|
server_start_time = datetime.datetime.now().timestamp()
|
193
43
|
|
194
44
|
app = flask.Flask(__name__)
|
195
45
|
|
196
|
-
#
|
197
|
-
|
46
|
+
# Register database API routes
|
47
|
+
register_database_api_routes(app)
|
198
48
|
|
49
|
+
# Load data from the JSON file
|
199
50
|
def load_data_from_file():
|
200
51
|
if os.path.exists(TEXT_REPLACEMENTS_FILE):
|
201
52
|
with open(TEXT_REPLACEMENTS_FILE, 'r', encoding='utf-8') as file:
|
@@ -203,8 +54,6 @@ def load_data_from_file():
|
|
203
54
|
return {"enabled": True, "args": {"replacements": {}}}
|
204
55
|
|
205
56
|
# Save data to the JSON file
|
206
|
-
|
207
|
-
|
208
57
|
def save_data_to_file(data):
|
209
58
|
with open(TEXT_REPLACEMENTS_FILE, 'w', encoding='utf-8') as file:
|
210
59
|
json.dump(data, file, indent=4, ensure_ascii=False)
|
@@ -262,7 +111,19 @@ def texthooker():
|
|
262
111
|
|
263
112
|
@app.route('/textreplacements')
|
264
113
|
def textreplacements():
|
265
|
-
|
114
|
+
# Serve the text replacements data as JSON for compatibility
|
115
|
+
try:
|
116
|
+
if not os.path.exists(TEXT_REPLACEMENTS_FILE):
|
117
|
+
return jsonify({"error": "Text replacements file not found."}), 404
|
118
|
+
with open(TEXT_REPLACEMENTS_FILE, "r", encoding="utf-8") as f:
|
119
|
+
data = json.load(f)
|
120
|
+
return jsonify(data)
|
121
|
+
except Exception as e:
|
122
|
+
return jsonify({"error": f"Failed to load text replacements: {str(e)}"}), 500
|
123
|
+
|
124
|
+
@app.route('/database')
|
125
|
+
def database():
|
126
|
+
return flask.render_template('database.html')
|
266
127
|
|
267
128
|
|
268
129
|
@app.route('/data', methods=['GET'])
|
@@ -283,7 +144,7 @@ def clear_history():
|
|
283
144
|
return jsonify({'message': 'History cleared successfully'}), 200
|
284
145
|
|
285
146
|
|
286
|
-
async def add_event_to_texthooker(line
|
147
|
+
async def add_event_to_texthooker(line):
|
287
148
|
new_event = event_manager.add_gameline(line)
|
288
149
|
await websocket_server_thread.send_text({
|
289
150
|
'event': 'text_received',
|
@@ -389,32 +250,24 @@ Translate the following lines of game dialogue into natural-sounding, context-aw
|
|
389
250
|
def get_status():
|
390
251
|
return jsonify(gsm_status.to_dict()), 200
|
391
252
|
|
253
|
+
@app.template_filter('datetimeformat')
|
254
|
+
def datetimeformat(value, format='%Y-%m-%d %H:%M:%S'):
|
255
|
+
"""Formats a timestamp into a human-readable string."""
|
256
|
+
if value is None:
|
257
|
+
return ""
|
258
|
+
return datetime.datetime.fromtimestamp(float(value)).strftime(format)
|
259
|
+
|
392
260
|
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
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
|
261
|
+
@app.route('/stats')
|
262
|
+
def stats():
|
263
|
+
"""Renders the stats page."""
|
264
|
+
return render_template('stats.html')
|
265
|
+
|
266
|
+
|
267
|
+
@app.route('/search')
|
268
|
+
def search():
|
269
|
+
"""Renders the search page."""
|
270
|
+
return render_template('search.html')
|
418
271
|
|
419
272
|
|
420
273
|
def get_selected_lines():
|
@@ -452,117 +305,6 @@ def start_web_server():
|
|
452
305
|
app.run(host='0.0.0.0', port=port, debug=False)
|
453
306
|
|
454
307
|
|
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
308
|
async def texthooker_page_coro():
|
567
309
|
global websocket_server_thread, plaintext_websocket_server_thread, overlay_server_thread
|
568
310
|
# Run the WebSocket server in the asyncio event loop
|
@@ -581,4 +323,4 @@ def run_text_hooker_page():
|
|
581
323
|
|
582
324
|
|
583
325
|
if __name__ == '__main__':
|
584
|
-
asyncio.run(texthooker_page_coro())
|
326
|
+
asyncio.run(texthooker_page_coro())
|
@@ -0,0 +1,120 @@
|
|
1
|
+
import asyncio
|
2
|
+
import json
|
3
|
+
import queue
|
4
|
+
import threading
|
5
|
+
import websockets
|
6
|
+
|
7
|
+
from GameSentenceMiner.util.configuration import logger, get_config
|
8
|
+
|
9
|
+
|
10
|
+
websocket_queue = queue.Queue()
|
11
|
+
paused = False
|
12
|
+
|
13
|
+
|
14
|
+
class WebsocketServerThread(threading.Thread):
|
15
|
+
def __init__(self, read, get_ws_port_func):
|
16
|
+
super().__init__(daemon=True)
|
17
|
+
self._loop = None
|
18
|
+
self.read = read
|
19
|
+
self.clients = set()
|
20
|
+
self._event = threading.Event()
|
21
|
+
self.get_ws_port_func = get_ws_port_func
|
22
|
+
self.backedup_text = []
|
23
|
+
|
24
|
+
@property
|
25
|
+
def loop(self):
|
26
|
+
self._event.wait()
|
27
|
+
return self._loop
|
28
|
+
|
29
|
+
async def send_text_coroutine(self, message):
|
30
|
+
if not self.clients:
|
31
|
+
self.backedup_text.append(message)
|
32
|
+
return
|
33
|
+
for client in self.clients:
|
34
|
+
await client.send(message)
|
35
|
+
|
36
|
+
async def server_handler(self, websocket):
|
37
|
+
self.clients.add(websocket)
|
38
|
+
try:
|
39
|
+
if self.backedup_text:
|
40
|
+
for message in self.backedup_text:
|
41
|
+
await websocket.send(message)
|
42
|
+
self.backedup_text.clear()
|
43
|
+
async for message in websocket:
|
44
|
+
if self.read and not paused:
|
45
|
+
websocket_queue.put(message)
|
46
|
+
try:
|
47
|
+
await websocket.send('True')
|
48
|
+
except websockets.exceptions.ConnectionClosedOK:
|
49
|
+
pass
|
50
|
+
else:
|
51
|
+
try:
|
52
|
+
await websocket.send('False')
|
53
|
+
except websockets.exceptions.ConnectionClosedOK:
|
54
|
+
pass
|
55
|
+
except websockets.exceptions.ConnectionClosedError:
|
56
|
+
pass
|
57
|
+
finally:
|
58
|
+
self.clients.remove(websocket)
|
59
|
+
|
60
|
+
async def send_text(self, text):
|
61
|
+
if text:
|
62
|
+
if isinstance(text, dict) or isinstance(text, list):
|
63
|
+
text = json.dumps(text)
|
64
|
+
return asyncio.run_coroutine_threadsafe(
|
65
|
+
self.send_text_coroutine(text), self.loop)
|
66
|
+
|
67
|
+
def has_clients(self):
|
68
|
+
return len(self.clients) > 0
|
69
|
+
|
70
|
+
def stop_server(self):
|
71
|
+
self.loop.call_soon_threadsafe(self._stop_event.set)
|
72
|
+
|
73
|
+
def run(self):
|
74
|
+
async def main():
|
75
|
+
self._loop = asyncio.get_running_loop()
|
76
|
+
self._stop_event = stop_event = asyncio.Event()
|
77
|
+
self._event.set()
|
78
|
+
while True:
|
79
|
+
try:
|
80
|
+
self.server = start_server = websockets.serve(self.server_handler,
|
81
|
+
"0.0.0.0",
|
82
|
+
self.get_ws_port_func(),
|
83
|
+
max_size=1000000000)
|
84
|
+
async with start_server:
|
85
|
+
await stop_event.wait()
|
86
|
+
return
|
87
|
+
except Exception as e:
|
88
|
+
logger.warning(
|
89
|
+
f"WebSocket server encountered an error: {e}. Retrying...")
|
90
|
+
await asyncio.sleep(1)
|
91
|
+
|
92
|
+
asyncio.run(main())
|
93
|
+
|
94
|
+
|
95
|
+
def handle_exit_signal(loop):
|
96
|
+
logger.info("Received exit signal. Shutting down...")
|
97
|
+
for task in asyncio.all_tasks(loop):
|
98
|
+
task.cancel()
|
99
|
+
|
100
|
+
|
101
|
+
# Initialize WebSocket server threads
|
102
|
+
websocket_server_thread = WebsocketServerThread(read=True, get_ws_port_func=lambda: get_config(
|
103
|
+
).get_field_value('advanced', 'texthooker_communication_websocket_port'))
|
104
|
+
websocket_server_thread.start()
|
105
|
+
|
106
|
+
plaintext_websocket_server_thread = None
|
107
|
+
if get_config().advanced.plaintext_websocket_port:
|
108
|
+
plaintext_websocket_server_thread = WebsocketServerThread(
|
109
|
+
read=False, get_ws_port_func=lambda: get_config().get_field_value('advanced', 'plaintext_websocket_port'))
|
110
|
+
plaintext_websocket_server_thread.start()
|
111
|
+
|
112
|
+
overlay_server_thread = WebsocketServerThread(
|
113
|
+
read=False, get_ws_port_func=lambda: get_config().get_field_value('overlay', 'websocket_port'))
|
114
|
+
overlay_server_thread.start()
|
115
|
+
|
116
|
+
websocket_server_threads = [
|
117
|
+
websocket_server_thread,
|
118
|
+
plaintext_websocket_server_thread,
|
119
|
+
overlay_server_thread
|
120
|
+
]
|
@@ -4,7 +4,7 @@ GameSentenceMiner/config_gui.py,sha256=i79PrY2pP8_VKvIL7uoDv5cgHvCCQBIe0mS_YnX2A
|
|
4
4
|
GameSentenceMiner/gametext.py,sha256=fgBgLchezpauWELE9Y5G3kVCLfAneD0X4lJFoI3FYbs,10351
|
5
5
|
GameSentenceMiner/gsm.py,sha256=t2GAhMwVEHUzCdqM4tIgAzBUvNmt_Gec515iePacD6k,31945
|
6
6
|
GameSentenceMiner/obs.py,sha256=EyAYhaLvMjoeC-3j7fuvkqZN5logFFanPfb8Wn1C6m0,27296
|
7
|
-
GameSentenceMiner/vad.py,sha256=
|
7
|
+
GameSentenceMiner/vad.py,sha256=N-urlVOT6ayk4BFuIeZhxK1VxLD4Flxu-IStuy8FAVc,20190
|
8
8
|
GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
9
|
GameSentenceMiner/ai/ai_prompting.py,sha256=41xdBzE88Jlt12A0D-T_cMfLO5j6MSxfniOptpwNZm0,24068
|
10
10
|
GameSentenceMiner/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -19,17 +19,17 @@ GameSentenceMiner/locales/en_us.json,sha256=4lCV34FnDOe0c02qHlHnfujQedmqHSL-feN3
|
|
19
19
|
GameSentenceMiner/locales/ja_jp.json,sha256=LNLo2qIugMcDGiPbSo018zVAU8K_HG8Q4zvIcsHUzTA,28517
|
20
20
|
GameSentenceMiner/locales/zh_cn.json,sha256=lZYB3HAcxhVCSVWcnvuepuCvn6_Y2mvd0-SKJEYx_ko,24829
|
21
21
|
GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
|
-
GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=
|
22
|
+
GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=Ov04c-nKzh3sADxO-5JyZWVe4DlrHM9edM9tc7-97Jo,5970
|
23
23
|
GameSentenceMiner/ocr/ocrconfig.py,sha256=_tY8mjnzHMJrLS8E5pHqYXZjMuLoGKYgJwdhYgN-ny4,6466
|
24
24
|
GameSentenceMiner/ocr/owocr_area_selector.py,sha256=Rm1_nuZotJhfOfoJ_3mesh9udtOBjYqKhnAvSief6fo,29181
|
25
|
-
GameSentenceMiner/ocr/owocr_helper.py,sha256=
|
25
|
+
GameSentenceMiner/ocr/owocr_helper.py,sha256=LoGKfVd1PL8R9UgOA_S1TiKT1i_b3Yb8quWl5ls1MEI,31689
|
26
26
|
GameSentenceMiner/ocr/ss_picker.py,sha256=0IhxUdaKruFpZyBL-8SpxWg7bPrlGpy3lhTcMMZ5rwo,5224
|
27
27
|
GameSentenceMiner/owocr/owocr/__init__.py,sha256=87hfN5u_PbL_onLfMACbc0F5j4KyIK9lKnRCj6oZgR0,49
|
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
31
|
GameSentenceMiner/owocr/owocr/ocr.py,sha256=vRTMKLzi6GDBFZWCyf0tYi6es3cP1cvQOBjZqaZmnBg,70482
|
32
|
-
GameSentenceMiner/owocr/owocr/run.py,sha256=
|
32
|
+
GameSentenceMiner/owocr/owocr/run.py,sha256=9Z_gIMCZkP5AGY6Lqz3QamSc6YOa5PeweXz9f4o8Msg,81222
|
33
33
|
GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=Na6XStbQBtpQUSdbN3QhEswtKuU1JjReFk_K8t5ezQE,3395
|
34
34
|
GameSentenceMiner/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
35
35
|
GameSentenceMiner/tools/audio_offset_selector.py,sha256=8Stk3BP-XVIuzRv9nl9Eqd2D-1yD3JrgU-CamBywJmY,8542
|
@@ -37,7 +37,7 @@ GameSentenceMiner/tools/furigana_filter_preview.py,sha256=BXv7FChPEJW_VeG5XYt6su
|
|
37
37
|
GameSentenceMiner/tools/ss_selector.py,sha256=cbjMxiKOCuOfbRvLR_PCRlykBrGtm1LXd6u5czPqkmc,4793
|
38
38
|
GameSentenceMiner/tools/window_transparency.py,sha256=GtbxbmZg0-UYPXhfHff-7IKZyY2DKe4B9GdyovfmpeM,8166
|
39
39
|
GameSentenceMiner/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
40
|
-
GameSentenceMiner/util/configuration.py,sha256=
|
40
|
+
GameSentenceMiner/util/configuration.py,sha256=0S8rf_TTy7qbUZc7PLUXEZVScx27tV0cDZR_GcM1QKk,40412
|
41
41
|
GameSentenceMiner/util/db.py,sha256=2bO0rD4i8A1hhsRBER-wgZy9IK17ibRbI8DHxdKvYsI,16598
|
42
42
|
GameSentenceMiner/util/electron_config.py,sha256=KfeJToeFFVw0IR5MKa-gBzpzaGrU-lyJbR9z-sDEHYU,8767
|
43
43
|
GameSentenceMiner/util/ffmpeg.py,sha256=jA-cFtCmdCWrUSPpdtFSLr-GSoqs4qNUzW20v4HPHf0,28715
|
@@ -45,7 +45,7 @@ GameSentenceMiner/util/get_overlay_coords.py,sha256=P5tI7H0cnveGs33aQdvJGy9DV6aI
|
|
45
45
|
GameSentenceMiner/util/gsm_utils.py,sha256=Piwv88Q9av2LBeN7M6QDi0Mp0_R2lNbkcI6ekK5hd2o,11851
|
46
46
|
GameSentenceMiner/util/model.py,sha256=R-_RYTYLSDNgBoVTPuPBcIHeOznIqi_vBzQ7VQ20WYk,6727
|
47
47
|
GameSentenceMiner/util/notification.py,sha256=YBhf_mSo_i3cjBz-pmeTPx3wchKiG9BK2VBdZSa2prQ,4597
|
48
|
-
GameSentenceMiner/util/text_log.py,sha256=
|
48
|
+
GameSentenceMiner/util/text_log.py,sha256=eqLchRrRsWeuex13f5IoxENLWMhjfZtJgUnh9N5jnJQ,6782
|
49
49
|
GameSentenceMiner/util/communication/__init__.py,sha256=xh__yn2MhzXi9eLi89PeZWlJPn-cbBSjskhi1BRraXg,643
|
50
50
|
GameSentenceMiner/util/communication/send.py,sha256=Wki9qIY2CgYnuHbmnyKVIYkcKAN_oYS4up93XMikBaI,222
|
51
51
|
GameSentenceMiner/util/communication/websocket.py,sha256=Zpnqsy8RUeYxMFNGVUaPrWrlbAHjuNxCsn908iWL_kU,3344
|
@@ -56,8 +56,12 @@ GameSentenceMiner/util/downloader/oneocr_dl.py,sha256=l3s9Z-x1b57GX048o5h-MVv0UT
|
|
56
56
|
GameSentenceMiner/util/win10toast/__init__.py,sha256=6TL2w6rzNmpJEp6_v2cAJP_7ExA3UsKzwdM08pNcVfE,5341
|
57
57
|
GameSentenceMiner/util/win10toast/__main__.py,sha256=5MYnBcFj8y_6Dyc1kiPd0_FsUuh4yl1cv5wsleU6V4w,668
|
58
58
|
GameSentenceMiner/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
59
|
+
GameSentenceMiner/web/database_api.py,sha256=kcyTWPuw_qtrK5qBzCFTIP0tqIvPmL-NEtiRL9BNbe8,35079
|
60
|
+
GameSentenceMiner/web/events.py,sha256=6Vyz5c9MdpMIa7Zqljqhap2XFQnAVYJ0CdQV64TSZsA,5119
|
59
61
|
GameSentenceMiner/web/service.py,sha256=YZchmScTn7AX_GkwV1ULEK6qjdOnJcpc3qfMwDf7cUE,5363
|
60
|
-
GameSentenceMiner/web/
|
62
|
+
GameSentenceMiner/web/stats.py,sha256=daSSxWlumAyqVVtX10qHESF-tZYwCcFMp8qZA5AE0nI,22066
|
63
|
+
GameSentenceMiner/web/texthooking_page.py,sha256=hkKu3SIi0V-wktiKhFhQEnhJe6PUWVZs_FSa1-wOYFQ,11134
|
64
|
+
GameSentenceMiner/web/websockets.py,sha256=IwwQo6VtgPqeOuc-datgfJyLpX3LwB2MISDqA6EkiSA,4131
|
61
65
|
GameSentenceMiner/web/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
62
66
|
GameSentenceMiner/web/static/apple-touch-icon.png,sha256=OcMI8af_68DA_tweOsQ5LytTyMwm7-hPW07IfrOVgEs,46132
|
63
67
|
GameSentenceMiner/web/static/favicon-96x96.png,sha256=lOePzjiKl1JY2J1kT_PMdyEnrlJmi5GWbmXJunM12B4,16502
|
@@ -67,14 +71,16 @@ GameSentenceMiner/web/static/site.webmanifest,sha256=kaeNT-FjFt-T7JGzOhXH7YSqsrD
|
|
67
71
|
GameSentenceMiner/web/static/style.css,sha256=bPZK0NVMuyRl5NNDuT7ZTzVLKlvSsdmeVHmAW4y5FM0,7001
|
68
72
|
GameSentenceMiner/web/static/web-app-manifest-192x192.png,sha256=EfSNnBmsSaLfESbkGfYwbKzcjKOdzuWo18ABADfN974,51117
|
69
73
|
GameSentenceMiner/web/static/web-app-manifest-512x512.png,sha256=wyqgCWCrLEUxSRXmaA3iJEESd-vM-ZmlTtZFBY4V8Pk,230819
|
70
|
-
GameSentenceMiner/web/templates/
|
74
|
+
GameSentenceMiner/web/templates/database.html,sha256=iEJWQvvH_RGWmHuFx0iwNeamBV5FoVxZgFKgfm-4zc4,13582
|
71
75
|
GameSentenceMiner/web/templates/index.html,sha256=LqXZx7-NE42pXSpHNZ3To680rD-vt9wEJoFYBlgp1qU,216923
|
72
|
-
GameSentenceMiner/web/templates/
|
73
|
-
GameSentenceMiner/web/templates/
|
76
|
+
GameSentenceMiner/web/templates/search.html,sha256=Fat3hOjQwkYBbdFhgWzRzZ5iEB78-2_0LpT7uK2aURE,3701
|
77
|
+
GameSentenceMiner/web/templates/stats.html,sha256=I-3eb2521r7fvDHfCktOB79fhWj2_2lcjFJi9qWAbgA,16668
|
78
|
+
GameSentenceMiner/web/templates/text_replacements.html,sha256=rB6mUvzzdbAlNV0dEukZlec0sXgRarBZw8Qh_eWRErE,16694
|
79
|
+
GameSentenceMiner/web/templates/utility.html,sha256=KtqnZUMAYs5XsEdC9Tlsd40NKAVic0mu6sh-ReMDJpU,16940
|
74
80
|
GameSentenceMiner/wip/__init___.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
75
|
-
gamesentenceminer-2.15.
|
76
|
-
gamesentenceminer-2.15.
|
77
|
-
gamesentenceminer-2.15.
|
78
|
-
gamesentenceminer-2.15.
|
79
|
-
gamesentenceminer-2.15.
|
80
|
-
gamesentenceminer-2.15.
|
81
|
+
gamesentenceminer-2.15.10.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
82
|
+
gamesentenceminer-2.15.10.dist-info/METADATA,sha256=T-Ik7ri7Xjt4D_J-Yd3fOVK0yf_rOrIe-oChyHlgOA8,7349
|
83
|
+
gamesentenceminer-2.15.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
84
|
+
gamesentenceminer-2.15.10.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
85
|
+
gamesentenceminer-2.15.10.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
86
|
+
gamesentenceminer-2.15.10.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|