GameSentenceMiner 2.8.6__py3-none-any.whl → 2.8.7__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.
@@ -1,19 +1,25 @@
1
+ import asyncio
1
2
  import datetime
2
3
  import json
3
4
  import os
5
+ import queue
6
+ import sqlite3
7
+ import threading
4
8
  from dataclasses import dataclass
5
9
 
6
10
  import flask
11
+ import websockets
7
12
 
8
- from GameSentenceMiner.text_log import GameLine, get_line_by_id
13
+ from GameSentenceMiner.text_log import GameLine, get_line_by_id, initial_time
9
14
  from flask import request, jsonify, send_from_directory
10
15
  import webbrowser
11
16
  from GameSentenceMiner import obs
12
- from GameSentenceMiner.configuration import logger, get_config
17
+ from GameSentenceMiner.configuration import logger, get_config, DB_PATH
13
18
  from GameSentenceMiner.util import TEXT_REPLACEMENTS_FILE
14
19
 
15
20
  port = get_config().general.texthooker_port
16
21
  url = f"http://localhost:{port}"
22
+ websocket_port = 55001
17
23
 
18
24
 
19
25
  @dataclass
@@ -22,16 +28,25 @@ class EventItem:
22
28
  id: str
23
29
  text: str
24
30
  time: datetime.datetime
25
- timestamp: float
26
31
  checked: bool = False
32
+ history: bool = False
27
33
 
28
34
  def to_dict(self):
29
35
  return {
30
36
  'id': self.id,
31
37
  'text': self.text,
32
38
  'time': self.time,
33
- 'timestamp': self.timestamp,
34
- 'checked': self.checked
39
+ 'checked': self.checked,
40
+ 'history': self.history,
41
+ }
42
+
43
+ def to_serializable(self):
44
+ return {
45
+ 'id': self.id,
46
+ 'text': self.text,
47
+ 'time': self.time.isoformat(),
48
+ 'checked': self.checked,
49
+ 'history': self.history,
35
50
  }
36
51
 
37
52
  class EventManager:
@@ -43,6 +58,36 @@ class EventManager:
43
58
  def __init__(self):
44
59
  self.events = []
45
60
  self.events_dict = {}
61
+ self._connect()
62
+ self._create_table()
63
+ self._load_events_from_db()
64
+ # self.close_connection()
65
+
66
+ def _connect(self):
67
+ self.conn = sqlite3.connect(DB_PATH)
68
+ self.cursor = self.conn.cursor()
69
+
70
+ def _create_table(self):
71
+ self.cursor.execute("""
72
+ CREATE TABLE IF NOT EXISTS events (
73
+ event_id TEXT PRIMARY KEY,
74
+ line_id TEXT,
75
+ text TEXT,
76
+ time TEXT
77
+ )
78
+ """)
79
+ self.conn.commit()
80
+
81
+ def _load_events_from_db(self):
82
+ self.cursor.execute("SELECT * FROM events")
83
+ rows = self.cursor.fetchall()
84
+ for row in rows:
85
+ event_id, line_id, text, timestamp = row
86
+ timestamp = datetime.datetime.fromisoformat(timestamp)
87
+ line = GameLine(line_id, text, timestamp, None, None, 0)
88
+ event = EventItem(line, event_id, text, timestamp, False, timestamp < initial_time)
89
+ self.events.append(event)
90
+ self.events_dict[event_id] = event
46
91
 
47
92
  def __iter__(self):
48
93
  return iter(self.events)
@@ -51,20 +96,69 @@ class EventManager:
51
96
  self.events = new_events
52
97
 
53
98
  def add_gameline(self, line: GameLine):
54
- new_event = EventItem(line, line.id, line.text, line.time, line.time.timestamp(), False)
99
+ new_event = EventItem(line, line.id, line.text, line.time, False, False)
55
100
  self.events_dict[line.id] = new_event
56
101
  self.events.append(new_event)
102
+ # self.store_to_db(new_event)
103
+ event_queue.put(new_event)
104
+ return new_event
57
105
 
58
106
  def get_events(self):
59
107
  return self.events
60
108
 
61
109
  def add_event(self, event):
62
110
  self.events.append(event)
111
+ event_queue.put(event)
63
112
 
64
113
  def get(self, event_id):
65
114
  return self.events_dict.get(event_id)
66
115
 
116
+ def close_connection(self):
117
+ if self.conn:
118
+ self.conn.close()
119
+
120
+ class EventProcessor(threading.Thread):
121
+ def __init__(self, event_queue, db_path):
122
+ super().__init__()
123
+ self.event_queue = event_queue
124
+ self.db_path = db_path
125
+ self.conn = None
126
+ self.cursor = None
127
+ self.daemon = True
128
+
129
+ def _connect(self):
130
+ self.conn = sqlite3.connect(self.db_path)
131
+ self.cursor = self.conn.cursor()
132
+
133
+ def run(self):
134
+ self._connect()
135
+ while True:
136
+ try:
137
+ event = self.event_queue.get()
138
+ if event is None: # Exit signal
139
+ break
140
+ self._store_to_db(event)
141
+ except Exception as e:
142
+ logger.error(f"Error processing event: {e}")
143
+ self._close_connection()
144
+
145
+ def _store_to_db(self, event):
146
+ self.cursor.execute("""
147
+ INSERT INTO events (event_id, line_id, text, time)
148
+ VALUES (?, ?, ?, ?)
149
+ """, (event.id, event.line.id, event.text, event.time.isoformat()))
150
+ self.conn.commit()
151
+
152
+ def _close_connection(self):
153
+ if self.conn:
154
+ self.conn.close()
155
+
67
156
  event_manager = EventManager()
157
+ event_queue = queue.Queue()
158
+
159
+ # Initialize the EventProcessor with the queue and event manager
160
+ event_processor = EventProcessor(event_queue, DB_PATH)
161
+ event_processor.start()
68
162
 
69
163
  server_start_time = datetime.datetime.now().timestamp()
70
164
 
@@ -119,27 +213,28 @@ def serve_static(filename):
119
213
 
120
214
  @app.route('/')
121
215
  def index():
122
- with open(os.path.join(app.root_path, 'static', 'utility.html'), encoding='utf-8') as file:
123
- return file.read()
216
+ return flask.render_template('utility.html', websocket_port=websocket_port)
124
217
 
125
218
  @app.route('/texthooker')
126
219
  def texthooker():
127
- with open(os.path.join(app.root_path, 'static', 'utility.html'), encoding='utf-8') as file:
128
- return file.read()
220
+ return flask.render_template('utility.html', websocket_port=websocket_port)
129
221
 
130
222
  @app.route('/textreplacements')
131
223
  def textreplacements():
132
- with open(os.path.join(app.root_path, 'static', 'text_replacements.html'), encoding='utf-8') as file:
133
- return file.read()
224
+ return flask.render_template('text_replacements.html')
134
225
 
135
226
  @app.route('/data', methods=['GET'])
136
227
  def get_data():
137
228
  return jsonify([event.to_dict() for event in event_manager])
138
229
 
139
230
 
140
- def add_event_to_texthooker(line: GameLine):
231
+ async def add_event_to_texthooker(line: GameLine):
141
232
  logger.info("Adding event to web server: %s", line.text)
142
- event_manager.add_gameline(line)
233
+ new_event = event_manager.add_gameline(line)
234
+ await broadcast_message({
235
+ 'event': 'text_received',
236
+ 'data': new_event.to_serializable()
237
+ })
143
238
 
144
239
 
145
240
  @app.route('/update', methods=['POST'])
@@ -178,6 +273,42 @@ def play_audio():
178
273
  return jsonify({}), 200
179
274
 
180
275
 
276
+ connected_clients = set()
277
+
278
+ async def websocket_handler(websocket):
279
+ logger.debug(f"Client connected: {websocket.remote_address}")
280
+ connected_clients.add(websocket)
281
+ try:
282
+ async for message in websocket:
283
+ try:
284
+ data = json.loads(message)
285
+ if 'type' in data and data['type'] == 'get_events':
286
+ initial_events = [{'id': 1, 'text': 'Initial event from WebSocket'}, {'id': 2, 'text': 'Another initial event'}]
287
+ await websocket.send(json.dumps({'event': 'initial_events', 'payload': initial_events}))
288
+ elif 'update_checkbox' in data:
289
+ print(f"Received checkbox update: {data}")
290
+ # Handle checkbox update logic
291
+ pass
292
+ await websocket.send(json.dumps({'response': f'Server received: {message}'}))
293
+ except json.JSONDecodeError:
294
+ await websocket.send(json.dumps({'error': 'Invalid JSON format'}))
295
+ except websockets.exceptions.ConnectionClosedError:
296
+ print(f"Client disconnected abruptly: {websocket.remote_address}")
297
+ except websockets.exceptions.ConnectionClosedOK:
298
+ print(f"Client disconnected gracefully: {websocket.remote_address}")
299
+ finally:
300
+ connected_clients.discard(websocket)
301
+
302
+ async def broadcast_message(message):
303
+ if connected_clients:
304
+ tasks = [client.send(json.dumps(message)) for client in connected_clients]
305
+ await asyncio.gather(*tasks)
306
+
307
+ # async def main():
308
+ # async with websockets.serve(websocket_handler, "localhost", 8765): # Choose a port for WebSocket
309
+ # print("WebSocket server started on ws://localhost:8765/ws (adjust as needed)")
310
+ # await asyncio.Future() # Keep the server running
311
+
181
312
  # @app.route('/store-events', methods=['POST'])
182
313
  # def store_events():
183
314
  # data = request.get_json()
@@ -225,5 +356,31 @@ def start_web_server():
225
356
 
226
357
  app.run(port=port, debug=False) # debug=True provides helpful error messages during development
227
358
 
359
+ async def run_websocket_server(host="0.0.0.0", port=55001):
360
+ global websocket_port
361
+ while True:
362
+ websocket_port = port
363
+ try:
364
+ async with websockets.serve(websocket_handler, host, port):
365
+ logger.debug(f"WebSocket server started at ws://{host}:{port}/")
366
+ await asyncio.Future() # Keep the WebSocket server running
367
+ except OSError as e:
368
+ logger.debug(f"Port {port} is in use. Trying the next port...")
369
+ port += 1
370
+
371
+
372
+
373
+ async def texthooker_page_coro():
374
+ # Run the WebSocket server in the asyncio event loop
375
+ flask_thread = threading.Thread(target=start_web_server)
376
+ flask_thread.daemon = True
377
+ flask_thread.start()
378
+
379
+ # Keep the main asyncio event loop running (for the WebSocket server)
380
+ await run_websocket_server()
381
+
382
+ def run_text_hooker_page():
383
+ asyncio.run(texthooker_page_coro())
384
+
228
385
  if __name__ == '__main__':
229
- start_web_server()
386
+ asyncio.run(run_text_hooker_page())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.8.6
3
+ Version: 2.8.7
4
4
  Summary: A tool for mining sentences from games. Update: Multi-Line Mining! Fixed!
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
@@ -36,6 +36,7 @@ Requires-Dist: pywin32; sys_platform == "win32"
36
36
  Requires-Dist: google-generativeai
37
37
  Requires-Dist: pygetwindow; sys_platform == "win32"
38
38
  Requires-Dist: flask
39
+ Requires-Dist: groq
39
40
  Dynamic: license-file
40
41
 
41
42
  # Game Sentence Miner
@@ -1,19 +1,19 @@
1
1
  GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- GameSentenceMiner/anki.py,sha256=2Oy3a8Od8DC3vs1-ktS6KF1iUj74Ptd0SCe-U0cZcfc,14206
3
- GameSentenceMiner/config_gui.py,sha256=8Wc5YdAyaYT09T4pvHiL5Q4NiKRaL19CS3A0JhnSu6k,68822
4
- GameSentenceMiner/configuration.py,sha256=NQMsM23p4_keTFXEaKMTMacIx0TCdk4-TIl4usZkJy4,20801
2
+ GameSentenceMiner/anki.py,sha256=pSkf-il17vKZIBt1ZHHDMfvfO9M-GdF1zox-c--KkAY,14208
3
+ GameSentenceMiner/config_gui.py,sha256=mOsk_9rqQ09VUdUNuSMx-gHTAS2o41T3kD7qpYq3TJA,70928
4
+ GameSentenceMiner/configuration.py,sha256=nV2FLrT-XbedPakHDKo8TddtnWj9Q2RCPFGcVs_S4RY,21673
5
5
  GameSentenceMiner/electron_config.py,sha256=dGcPYCISPehXubYSzsDuI2Gl092MYK0u3bTnkL9Jh1Y,9787
6
6
  GameSentenceMiner/ffmpeg.py,sha256=mcEcJnYl06oJGbLaymFUfqClFiHf6Hhf2SXo3UV9tvM,13378
7
- GameSentenceMiner/gametext.py,sha256=pPJO-NYW15SqAG-IQ0ZHpPt8py4g0yL1Lx0UlR-en-4,5431
8
- GameSentenceMiner/gsm.py,sha256=6BcCYNV9tGQ8FgDh6Y8bPbZ66KWP57oBUqIoVF_nnXg,24667
7
+ GameSentenceMiner/gametext.py,sha256=ClSpOeohBWG17MRVIbhXfNDnkUdxU9mTspGv9975uEc,5422
8
+ GameSentenceMiner/gsm.py,sha256=vCEPg7rE68AUD2u6DesjLxuZSy1gJzbiA0fig673WRM,25254
9
9
  GameSentenceMiner/model.py,sha256=JdnkT4VoPOXmOpRgFdvERZ09c9wLN6tUJxdrKlGZcqo,5305
10
10
  GameSentenceMiner/notification.py,sha256=FY39ChSRK0Y8TQ6lBGsLnpZUFPtFpSy2tweeXVoV7kc,2809
11
- GameSentenceMiner/obs.py,sha256=DLTcK48Dqcg-5BYC2Fp-tPCO4dVI9FuVYbwIawECkwQ,8977
11
+ GameSentenceMiner/obs.py,sha256=GPlsFrcv1eYelXyJfpspGK0iZK5AXPkoFsIGdB7eJrk,10002
12
12
  GameSentenceMiner/package.py,sha256=YlS6QRMuVlm6mdXx0rlXv9_3erTGS21jaP3PNNWfAH0,1250
13
13
  GameSentenceMiner/text_log.py,sha256=tBuZ8ElUgPtiQV8U6U90kmRxposwIkL3fjOYejdzikc,5153
14
14
  GameSentenceMiner/util.py,sha256=bb75EQdk4Nf0i9t3XMznjd6NxTWfkOVeE5bZFstbCbU,8910
15
15
  GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- GameSentenceMiner/ai/gemini.py,sha256=6kTQPuRH16D-1srhrWa5uPGIy-jBqNm9eRdKBSbpiXA,5423
16
+ GameSentenceMiner/ai/ai_prompting.py,sha256=tu7MxItzuJ5iSyieWIt1YHrMDEUKYAAlur6WWLZ61D4,9137
17
17
  GameSentenceMiner/communication/__init__.py,sha256=_jGn9PJxtOAOPtJ2rI-Qu9hEHVZVpIvWlxKvqk91_zI,638
18
18
  GameSentenceMiner/communication/send.py,sha256=oOJdCS6-LNX90amkRn5FL2xqx6THGm56zHR2ntVIFTE,229
19
19
  GameSentenceMiner/communication/websocket.py,sha256=pTcUe_ZZRp9REdSU4qalhPmbT_1DKa7w18j6RfFLELA,3074
@@ -24,21 +24,21 @@ GameSentenceMiner/downloader/oneocr_dl.py,sha256=o3ANp5IodEQoQ8GPcJdg9Y8JzA_lict
24
24
  GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=zagsB4UD9mmZX_r6dFBCXZqdDa0XGk-RvIqbKoPB9lQ,1932
26
26
  GameSentenceMiner/ocr/ocrconfig.py,sha256=_tY8mjnzHMJrLS8E5pHqYXZjMuLoGKYgJwdhYgN-ny4,6466
27
- GameSentenceMiner/ocr/owocr_area_selector.py,sha256=a2i4kW9g9_DJKt6OJ5dlSiqJGiXkKyqAQaAiXBdrq8U,46680
28
- GameSentenceMiner/ocr/owocr_helper.py,sha256=J6FrcxikTbfoHnnvQfp5uvGIragASrB4I3oepBCcziE,16076
27
+ GameSentenceMiner/ocr/owocr_area_selector.py,sha256=gwYOz-fA5qoL63wh77eyGJtBtO7YVvWyO5cHb3D0Oz4,46738
28
+ GameSentenceMiner/ocr/owocr_helper.py,sha256=PCsPbqrECSbi4u8NIq3PJotStaYVBBbfccHu-DrqpwU,17269
29
29
  GameSentenceMiner/owocr/owocr/__init__.py,sha256=opjBOyGGyEqZCE6YdZPnyt7nVfiwyELHsXA0jAsjm14,25
30
30
  GameSentenceMiner/owocr/owocr/__main__.py,sha256=r8MI6RAmbkTWqOJ59uvXoDS7CSw5jX5war9ULGWELrA,128
31
- GameSentenceMiner/owocr/owocr/config.py,sha256=738QCJHEWpFhMh966plOcXYWwcshSiRsxjjIwldeTtI,7461
31
+ GameSentenceMiner/owocr/owocr/config.py,sha256=n-xtVylb2Q_H84jb1ZsIGxPQjTNnyvnRny1RhtaLJM8,7550
32
32
  GameSentenceMiner/owocr/owocr/lens_betterproto.py,sha256=oNoISsPilVVRBBPVDtb4-roJtAhp8ZAuFTci3TGXtMc,39141
33
33
  GameSentenceMiner/owocr/owocr/ocr.py,sha256=n24Xg8Z8dbcgLpq1u4d22z3tLV1evmf0dK3-Xocv3vs,39878
34
- GameSentenceMiner/owocr/owocr/run.py,sha256=WP3YTocRnZh4AHysIgLokemcaowatMjwfxS0IcxIHps,47859
34
+ GameSentenceMiner/owocr/owocr/run.py,sha256=fzRUo1iYDZffolYHffqCaIYpvBLO2r_FbXgoQ35Eiqk,51151
35
35
  GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=fjJ3CSXLti3WboGPpmsa7MWOwIXsfpHC8N4zKahGGY0,3346
36
36
  GameSentenceMiner/vad/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  GameSentenceMiner/vad/silero_trim.py,sha256=ULf3zwS-JMsY82cKF7gZxREHw8L6lgpWF2U1YqgE9Oc,1681
38
38
  GameSentenceMiner/vad/vosk_helper.py,sha256=125X8C9NxFPlWWpoNsbOnEqKx8RCjXN109zNx_QXhyg,6070
39
39
  GameSentenceMiner/vad/whisper_helper.py,sha256=JJ-iltCh813XdjyEw0Wn5DaErf6PDqfH0Efu1Md8cIY,3543
40
40
  GameSentenceMiner/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
- GameSentenceMiner/web/texthooking_page.py,sha256=INCBzEUk0I_452OpDnfDMeGMaMCAJWSmq14S4V1ToHA,7227
41
+ GameSentenceMiner/web/texthooking_page.py,sha256=bI1itAIY4iPjGlFTmtrLbcjo6pvvp_dFlSOYrEDWPJ0,12760
42
42
  GameSentenceMiner/web/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
43
  GameSentenceMiner/web/static/apple-touch-icon.png,sha256=OcMI8af_68DA_tweOsQ5LytTyMwm7-hPW07IfrOVgEs,46132
44
44
  GameSentenceMiner/web/static/favicon-96x96.png,sha256=lOePzjiKl1JY2J1kT_PMdyEnrlJmi5GWbmXJunM12B4,16502
@@ -46,13 +46,11 @@ GameSentenceMiner/web/static/favicon.ico,sha256=7d25r_FBqRSNsAoEHpSzNoT7zyVt2DJR
46
46
  GameSentenceMiner/web/static/favicon.svg,sha256=x305AP6WlXGtrXIZlaQspdLmwteoFYUoe5FyJ9MYlJ8,11517
47
47
  GameSentenceMiner/web/static/site.webmanifest,sha256=kaeNT-FjFt-T7JGzOhXH7YSqsrDeiplZ2kDxCN_CFU4,436
48
48
  GameSentenceMiner/web/static/style.css,sha256=bPZK0NVMuyRl5NNDuT7ZTzVLKlvSsdmeVHmAW4y5FM0,7001
49
- GameSentenceMiner/web/static/text_replacements.html,sha256=tV5c8mCaWSt_vKuUpbdbLAzXZ3ATZeDvQ9PnnAfqY0M,8598
50
- GameSentenceMiner/web/static/utility.html,sha256=58br1whazdfrevWCHEzYNa1_VdBvebDDX2tWlX2QrFs,9964
51
49
  GameSentenceMiner/web/static/web-app-manifest-192x192.png,sha256=EfSNnBmsSaLfESbkGfYwbKzcjKOdzuWo18ABADfN974,51117
52
50
  GameSentenceMiner/web/static/web-app-manifest-512x512.png,sha256=wyqgCWCrLEUxSRXmaA3iJEESd-vM-ZmlTtZFBY4V8Pk,230819
53
- gamesentenceminer-2.8.6.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
54
- gamesentenceminer-2.8.6.dist-info/METADATA,sha256=-y0KqwQGu8y7OVhrpmzL0JSPusDvd4YtcxSip82DND4,5912
55
- gamesentenceminer-2.8.6.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
56
- gamesentenceminer-2.8.6.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
57
- gamesentenceminer-2.8.6.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
58
- gamesentenceminer-2.8.6.dist-info/RECORD,,
51
+ gamesentenceminer-2.8.7.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
52
+ gamesentenceminer-2.8.7.dist-info/METADATA,sha256=D_BRnBo7BO5pHItwjMraVgSCcZAfuJdjhhId2ouPwaA,5932
53
+ gamesentenceminer-2.8.7.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
54
+ gamesentenceminer-2.8.7.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
55
+ gamesentenceminer-2.8.7.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
56
+ gamesentenceminer-2.8.7.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.1)
2
+ Generator: setuptools (80.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,143 +0,0 @@
1
- import google.generativeai as genai
2
-
3
- from GameSentenceMiner.configuration import get_config, logger
4
-
5
- MODEL = "gemini-2.0-flash" # or "gemini-pro-vision" if you need image support
6
-
7
- genai.configure(api_key=get_config().ai.api_key)
8
- model = genai.GenerativeModel(MODEL)
9
-
10
- def translate_with_context(lines, sentence, current_line_index, game_title=""):
11
- """
12
- Translates a line of dialogue with context from surrounding lines.
13
-
14
- Args:
15
- lines: A list of strings representing the dialogue lines.
16
- sentence: Sentence to get translation for
17
- current_line_index: The index of the line to translate.
18
- game_title: Optional title of the game for added context.
19
-
20
- Returns:
21
- A string containing the translated sentence with context.
22
- """
23
-
24
- if not lines or current_line_index < 0 or current_line_index >= len(lines):
25
- return "Invalid input."
26
-
27
- context_lines = []
28
-
29
- # Get the previous 10 lines (or fewer if at the beginning)
30
- for i in range(max(0, current_line_index - 10), current_line_index):
31
- context_lines.append(lines[i].text)
32
-
33
- # Get the current line
34
- current_line = lines[current_line_index]
35
- context_lines.append(current_line.text)
36
-
37
- #Get the next 10 lines (or fewer if at the end)
38
- for i in range(current_line_index + 1, min(current_line_index + 11, len(lines))):
39
- context_lines.append(lines[i].text)
40
-
41
- ai_config = get_config().ai
42
-
43
- #this is ugly, but prettier in the output... so idk
44
- if ai_config.use_canned_translation_prompt:
45
- prompt_to_use = \
46
- f"""
47
- Translate the following Japanese dialogue from the game {game_title} into natural, context-aware English. Focus on preserving the tone, intent, and emotional nuance of the original text, paying close attention to the context provided by surrounding lines. The dialogue may include slang, idioms, implied meanings, or game-specific terminology that should be adapted naturally for English-speaking players. Ensure the translation feels immersive and aligns with the game's narrative style and character voices.
48
- Translate only the specified line below, providing a single result. Do not include additional text, explanations, or other lines unless explicitly requested. Allow expletives if more natural. Allow HTML tags for emphasis, italics, and other formatting as needed. Please also try to preserve existing HTML tags from the specified sentence if appropriate.
49
-
50
- Line to Translate:
51
- """
52
- elif ai_config.use_canned_context_prompt:
53
- prompt_to_use = \
54
- f"""
55
- Provide a very brief summary of the scene in English based on the provided Japanese dialogue and context. Focus on the characters' actions and the immediate situation being described.
56
-
57
- Current Sentence:
58
- """
59
- else:
60
- prompt_to_use = ai_config.custom_prompt
61
-
62
-
63
- prompt = \
64
- f"""
65
- Dialogue Context:
66
-
67
- {chr(10).join(context_lines)}
68
-
69
- I am playing the game {game_title}. With that, and the above dialogue context in mind, answer the following prompt.
70
-
71
- {prompt_to_use}
72
-
73
- {sentence}
74
- """
75
-
76
- logger.debug(prompt)
77
- try:
78
- response = model.generate_content(prompt)
79
- return response.text.strip()
80
- except Exception as e:
81
- return f"Translation failed: {e}"
82
-
83
- # Example Usage: Zero Escape: 999 examples
84
-
85
- # zero_escape_dialogue1 = [
86
- # "扉は開いた…?",
87
- # "まさか、こんな仕掛けが…",
88
- # "一体、何が起こっているんだ?",
89
- # "この数字の意味は…?",
90
- # "落ち着いて、考えるんだ。",
91
- # "でも、時間が…",
92
- # "まだ、諦めるな!",
93
- # "一体、誰が…?",
94
- # "まさか、あの人が…?",
95
- # "もう、ダメだ…",
96
- # "まだ、希望はある!",
97
- # "この部屋から、脱出するんだ!",
98
- # "でも、どうやって…?",
99
- # "何か、手がかりがあるはずだ!",
100
- # "早く、見つけないと…",
101
- # ]
102
- #
103
- #
104
- # current_line_index = 3
105
- # translation = translate_with_context(zero_escape_dialogue1, current_line_index, "Zero Escape: 999")
106
- # print(f"Original: {zero_escape_dialogue1[current_line_index]}")
107
- # print(f"Translation: {translation}")
108
- #
109
- # # Example with fewer context lines at the beginning.
110
- # zero_escape_dialogue2 = [
111
- # "このアミュレット…",
112
- # "何かを感じる…",
113
- # "この数字は…",
114
- # "9…?",
115
- # "まさか、これは…",
116
- # "何かの手がかり…?",
117
- # "急がないと…",
118
- # "時間がない…",
119
- # "早く、脱出を…",
120
- # ]
121
- #
122
- # current_line_index = 3
123
- # translation = translate_with_context(zero_escape_dialogue2, current_line_index, "Zero Escape: 999")
124
- # print(f"Original: {zero_escape_dialogue2[current_line_index]}")
125
- # print(f"Translation: {translation}")
126
- #
127
- # #example with fewer context lines at the end.
128
- # zero_escape_dialogue3 = [
129
- # "この状況、理解できない。",
130
- # "誰かが、私たちを閉じ込めたのか?",
131
- # "なぜ、こんなことを…?",
132
- # "このゲームの目的は…?",
133
- # "一体、何が真実なんだ?",
134
- # "信じられるのは、誰…?",
135
- # "疑心暗鬼になるな。",
136
- # "でも、どうすれば…?",
137
- # "とにかく、進むしかない。"
138
- # ]
139
- #
140
- # current_line_index = 4
141
- # translation = translate_with_context(zero_escape_dialogue3, current_line_index, "Zero Escape: 999")
142
- # print(f"Original: {zero_escape_dialogue3[current_line_index]}")
143
- # print(f"Translation: {translation}")