GameSentenceMiner 2.18.19__py3-none-any.whl → 2.18.20__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.
@@ -17,7 +17,7 @@ from GameSentenceMiner.util.gsm_utils import add_srt_line
17
17
  from GameSentenceMiner.util.text_log import add_line, get_text_log
18
18
  from GameSentenceMiner.web.texthooking_page import add_event_to_texthooker, overlay_server_thread
19
19
 
20
- from GameSentenceMiner.util.get_overlay_coords import OverlayProcessor
20
+ from GameSentenceMiner.util.get_overlay_coords import overlay_processor
21
21
 
22
22
 
23
23
  current_line = ''
@@ -33,7 +33,6 @@ last_clipboard = ''
33
33
 
34
34
  reconnecting = False
35
35
  websocket_connected = {}
36
- overlay_processor = None
37
36
 
38
37
  async def monitor_clipboard():
39
38
  global current_line, last_clipboard
@@ -67,6 +66,13 @@ async def listen_websockets():
67
66
  global current_line, current_line_time, reconnecting, websocket_connected
68
67
  try_other = False
69
68
  websocket_connected[uri] = False
69
+ websocket_names = {
70
+ "9002": "GSM OCR",
71
+ "9001": "Agent or TextractorSender",
72
+ "6677": "textractor_websocket",
73
+ "2333": "LunaTranslator"
74
+ }
75
+ likely_websocket_name = next((f" ({name})" for port, name in websocket_names.items() if port in uri), "")
70
76
  while True:
71
77
  if not get_config().general.use_websocket:
72
78
  await asyncio.sleep(1)
@@ -76,10 +82,9 @@ async def listen_websockets():
76
82
  websocket_url = f'ws://{uri}/api/ws/text/origin'
77
83
  try:
78
84
  async with websockets.connect(websocket_url, ping_interval=None) as websocket:
79
- logger.info(f"TextHooker Websocket {uri} Connected!")
80
85
  gsm_status.websockets_connected.append(websocket_url)
81
86
  if reconnecting:
82
- logger.info(f"Texthooker WebSocket {uri} connected Successfully!" + " Disabling Clipboard Monitor." if (get_config().general.use_clipboard and not get_config().general.use_both_clipboard_and_websocket) else "")
87
+ logger.info(f"Texthooker WebSocket {uri}{likely_websocket_name} connected Successfully!" + " Disabling Clipboard Monitor." if (get_config().general.use_clipboard and not get_config().general.use_both_clipboard_and_websocket) else "")
83
88
  reconnecting = False
84
89
  websocket_connected[uri] = True
85
90
  line_time = None
@@ -110,13 +115,13 @@ async def listen_websockets():
110
115
  if isinstance(e, InvalidStatus):
111
116
  e: InvalidStatus
112
117
  if e.response.status_code == 404:
113
- logger.info(f"Texthooker WebSocket: {uri} connection failed. Attempting some fixes...")
118
+ logger.info(f"Texthooker WebSocket: {uri}{likely_websocket_name} connection failed. Attempting some fixes...")
114
119
  try_other = True
115
120
  elif websocket_connected[uri]:
116
121
  if not (isinstance(e, ConnectionResetError) or isinstance(e, ConnectionError) or isinstance(e, InvalidStatus) or isinstance(e, websockets.ConnectionClosed)):
117
- logger.debug(f"Unexpected error in Texthooker WebSocket {uri} connection: {e}, Can be ignored")
122
+ logger.debug(f"Unexpected error in Texthooker WebSocket {uri}{likely_websocket_name} connection: {e}, Can be ignored")
118
123
  else:
119
- logger.warning(f"Texthooker WebSocket {uri} disconnected. Attempting to reconnect...")
124
+ logger.warning(f"Texthooker WebSocket {uri}{likely_websocket_name} disconnected. Attempting to reconnect...")
120
125
  websocket_connected[uri] = False
121
126
  await asyncio.sleep(1)
122
127
 
@@ -184,8 +189,6 @@ async def handle_new_text_event(current_clipboard, line_time=None):
184
189
 
185
190
 
186
191
  async def add_line_to_text_log(line, line_time=None):
187
- global overlay_processor
188
-
189
192
  if get_config().general.texthook_replacement_regex:
190
193
  current_line_after_regex = re.sub(get_config().general.texthook_replacement_regex, '', line)
191
194
  else:
@@ -198,12 +201,11 @@ async def add_line_to_text_log(line, line_time=None):
198
201
  if len(get_text_log().values) > 0:
199
202
  await add_event_to_texthooker(get_text_log()[-1])
200
203
  if get_config().overlay.websocket_port and overlay_server_thread.has_clients():
201
- if not overlay_processor:
202
- overlay_processor = OverlayProcessor()
203
204
  if overlay_processor.ready:
204
205
  await overlay_processor.find_box_and_send_to_overlay(current_line_after_regex)
205
206
  add_srt_line(line_time, new_line)
206
- GameLinesTable.add_line(get_text_log()[-1])
207
+ if 'nostatspls' not in new_line.scene.lower():
208
+ GameLinesTable.add_line(new_line)
207
209
 
208
210
  def reset_line_hotkey_pressed():
209
211
  global current_line_time
GameSentenceMiner/obs.py CHANGED
@@ -35,6 +35,8 @@ class OBSConnectionPool:
35
35
  self._locks = [threading.Lock() for _ in range(self.size)]
36
36
  self._next_idx = 0
37
37
  self._idx_lock = threading.Lock()
38
+ self.connected_once = False
39
+ self.last_error_shown = [None] * self.size
38
40
  logger.info(f"Initialized OBSConnectionPool with size {self.size}")
39
41
 
40
42
  def connect_all(self):
@@ -42,8 +44,12 @@ class OBSConnectionPool:
42
44
  for i in range(self.size):
43
45
  try:
44
46
  self._clients[i] = obs.ReqClient(**self.connection_kwargs)
47
+ self.connected_once = True
45
48
  except Exception as e:
49
+ if str(e) == self.last_error_shown[i]:
50
+ continue
46
51
  logger.error(f"Failed to create client {i} in pool: {e}")
52
+ self.last_error_shown[i] = str(e)
47
53
  return True
48
54
 
49
55
  def disconnect_all(self):
@@ -154,6 +160,8 @@ class OBSConnectionManager(threading.Thread):
154
160
  return errors
155
161
 
156
162
  buffer_seconds, error_message = self.check_replay_buffer_enabled()
163
+
164
+ gsm_state.replay_buffer_length = buffer_seconds or 300
157
165
 
158
166
  if not buffer_seconds:
159
167
  errors.append(error_message)
@@ -905,6 +905,7 @@ class Config:
905
905
  if profile.vad.selected_vad_model == WHISPER and profile.vad.backup_vad_model == SILERO:
906
906
  profile.vad.backup_vad_model = OFF
907
907
 
908
+ self.version = current_version
908
909
  self.save()
909
910
 
910
911
  def save(self):
@@ -1315,6 +1316,7 @@ class GsmAppState:
1315
1316
  self.current_srt = None
1316
1317
  self.srt_index = 1
1317
1318
  self.current_audio_stream = None
1319
+ self.replay_buffer_length = 0
1318
1320
 
1319
1321
 
1320
1322
  @dataclass_json
@@ -568,6 +568,8 @@ class OverlayProcessor:
568
568
  }
569
569
  # logger.info(f"Converted OneOCR results to percentages: {converted_results}")
570
570
  return converted_results
571
+
572
+ overlay_processor = OverlayProcessor()
571
573
 
572
574
  async def main_test_screenshot():
573
575
  """
@@ -596,7 +598,7 @@ async def main_test_screenshot():
596
598
 
597
599
  new_img.paste(img, (left, top))
598
600
  new_img.show()
599
-
601
+
600
602
  async def main_run_ocr():
601
603
  """
602
604
  Main function to demonstrate running the full OCR process.
@@ -1,6 +1,6 @@
1
1
  import uuid
2
2
  from dataclasses import dataclass
3
- from datetime import datetime
3
+ from datetime import datetime, timedelta
4
4
  from difflib import SequenceMatcher
5
5
  from typing import Optional
6
6
 
@@ -38,9 +38,6 @@ class GameLine:
38
38
  def set_TL(self, tl: str):
39
39
  self.TL = tl
40
40
 
41
- def get_stripped_text(self):
42
- return self.text.replace('\n', '').strip()
43
-
44
41
  def __str__(self):
45
42
  return str({"text": self.text, "time": self.time})
46
43
 
@@ -119,7 +116,7 @@ def strip_whitespace_and_punctuation(text: str) -> str:
119
116
  return re.sub(r'[\s 、。「」【】《》., ]', '', text).strip()
120
117
 
121
118
 
122
- # TODO See if partial_ratio is better than ratio
119
+ # Do not use partial_ratio here, ever
123
120
  def lines_match(texthooker_sentence, anki_sentence, similarity_threshold=80) -> bool:
124
121
  # Replace newlines, spaces, other whitespace characters, AND japanese punctuation
125
122
  texthooker_sentence = strip_whitespace_and_punctuation(texthooker_sentence)
@@ -133,9 +130,20 @@ def lines_match(texthooker_sentence, anki_sentence, similarity_threshold=80) ->
133
130
  return (anki_sentence in texthooker_sentence) or (texthooker_sentence in anki_sentence) or (similarity >= similarity_threshold)
134
131
 
135
132
 
136
- def get_text_event(last_note) -> GameLine:
137
- lines = game_log.values
138
-
133
+ def get_matching_line(last_note: AnkiCard, lines=None) -> GameLine:
134
+ """
135
+ Find a matching GameLine for the given AnkiCard.
136
+
137
+ Args:
138
+ last_note: The AnkiCard to match against
139
+ lines: Optional list of GameLines to search in. If None, uses all game log lines.
140
+
141
+ Returns:
142
+ GameLine: The matching line or the latest line if no match found
143
+ """
144
+ if not lines:
145
+ lines = get_all_lines()
146
+
139
147
  if not lines:
140
148
  raise Exception("No voicelines in GSM. GSM can only do work on text that has been sent to it since it started. If you are not getting any text into GSM, please check your setup/config.")
141
149
 
@@ -146,8 +154,12 @@ def get_text_event(last_note) -> GameLine:
146
154
  if not sentence:
147
155
  return lines[-1]
148
156
 
149
- # Check the last 50 lines for a match
150
- for line in reversed(lines[-50:]):
157
+ logger.info(f"Replay buffer length: {gsm_state.replay_buffer_length}")
158
+ time_window = datetime.now() - timedelta(seconds=gsm_state.replay_buffer_length) - timedelta(seconds=5)
159
+ for line in reversed(lines):
160
+ if line.time < time_window:
161
+ logger.info("Could not find matching sentence from GSM's history within the replay buffer time window. Using the latest line.")
162
+ return lines[-1]
151
163
  if lines_match(line.text, remove_html_and_cloze_tags(sentence)):
152
164
  return line
153
165
 
@@ -155,38 +167,20 @@ def get_text_event(last_note) -> GameLine:
155
167
  return lines[-1]
156
168
 
157
169
 
158
- def get_line_and_future_lines(last_note):
159
- if not last_note:
160
- return []
170
+ def get_text_event(last_note) -> GameLine:
171
+ """
172
+ Legacy wrapper for get_matching_line with original behavior.
173
+ Uses raw text comparison for backward compatibility.
174
+ """
175
+ return get_matching_line(last_note, lines=None)
161
176
 
162
- sentence = last_note.get_field(get_config().anki.sentence_field)
163
- found_lines = []
164
- if sentence:
165
- found = False
166
- for line in game_log.values:
167
- if found:
168
- found_lines.append(line)
169
- if lines_match(line.text, remove_html_and_cloze_tags(sentence)): # 80% similarity threshold
170
- found = True
171
- found_lines.append(line)
172
- return found_lines
173
-
174
-
175
- def get_mined_line(last_note: AnkiCard, lines=None):
176
- if lines is None:
177
- lines = []
178
- if not last_note:
179
- return lines[-1]
180
- if not lines:
181
- lines = get_all_lines()
182
- if not lines:
183
- raise Exception("No voicelines in GSM. GSM can only do work on text that has been sent to it since it started. If you are not getting any text into GSM, please check your setup/config.")
184
177
 
185
- sentence = last_note.get_field(get_config().anki.sentence_field)
186
- for line in reversed(lines[-50:]):
187
- if lines_match(line.get_stripped_text(), remove_html_and_cloze_tags(sentence)):
188
- return line
189
- return lines[-1]
178
+ def get_mined_line(last_note: AnkiCard, lines=None) -> GameLine:
179
+ """
180
+ Legacy wrapper for get_matching_line with original behavior.
181
+ Uses stripped text comparison and accepts custom lines.
182
+ """
183
+ return get_matching_line(last_note, lines=lines)
190
184
 
191
185
 
192
186
  def get_time_of_line(line):
@@ -200,9 +194,11 @@ def get_all_lines():
200
194
  def get_text_log() -> GameText:
201
195
  return game_log
202
196
 
197
+
203
198
  def add_line(current_line_after_regex, line_time):
204
199
  return game_log.add_line(current_line_after_regex, line_time)
205
200
 
201
+
206
202
  def get_line_by_id(line_id: str) -> Optional[GameLine]:
207
203
  """
208
204
  Retrieve a GameLine by its unique ID.
GameSentenceMiner/vad.py CHANGED
@@ -151,7 +151,7 @@ class SileroVADProcessor(VADProcessor):
151
151
  temp_wav = tempfile.NamedTemporaryFile(dir=configuration.get_temporary_directory(), suffix='.wav').name
152
152
  ffmpeg.convert_audio_to_wav(input_audio, temp_wav)
153
153
  wav = read_audio(temp_wav)
154
- speech_timestamps = get_speech_timestamps(wav, self.vad_model, return_seconds=True, threshold=0.2)
154
+ speech_timestamps = get_speech_timestamps(wav, self.vad_model, return_seconds=True)
155
155
  logger.debug(speech_timestamps)
156
156
  return speech_timestamps
157
157
 
@@ -166,9 +166,11 @@ class WhisperVADProcessor(VADProcessor):
166
166
  import torch
167
167
  if not self.vad_model:
168
168
  self.device = "cpu" if get_config().vad.use_cpu_for_inference else "cuda" if torch.cuda.is_available() else "cpu"
169
+ compute_type = "float32" if torch.cuda.is_available() else "int8"
169
170
  with warnings.catch_warnings():
170
171
  warnings.simplefilter("ignore")
171
- self.vad_model = whisper.load_faster_whisper(get_config().vad.whisper_model, device=self.device)
172
+ logger.info(f"Loading Whisper model '{get_config().vad.whisper_model}' on device '{self.device}'...")
173
+ self.vad_model = whisper.load_faster_whisper(get_config().vad.whisper_model, device=self.device, compute_type=compute_type)
172
174
  logger.info(f"Whisper model '{get_config().vad.whisper_model}' loaded.")
173
175
  return self.vad_model
174
176
 
@@ -429,13 +431,13 @@ def test_vad_processors():
429
431
  out_path = os.path.join(output_dir, out_name.replace("after_splice_", "after_trim_"))
430
432
  if os.path.exists(out_path):
431
433
  os.remove(out_path)
432
- result = processor.process_audio(test_audio, out_path, None)
434
+ result = processor.process_audio(test_audio, out_path, None, "")
433
435
  print(result)
434
436
 
435
437
  vad_system = VADSystem()
436
438
  vad_system.init()
437
439
 
438
- result = vad_system.trim_audio_with_vad(test_audio, os.path.join(output_dir, "after_vad.opus"), None)
440
+ result = vad_system.trim_audio_with_vad(test_audio, os.path.join(output_dir, "after_vad.opus"), None, full_text="")
439
441
  print(result)
440
442
 
441
443
 
@@ -227,6 +227,8 @@ def register_database_api_routes(app):
227
227
  data = request.get_json()
228
228
  line_ids = data.get('line_ids', [])
229
229
 
230
+ logger.debug(f"Request to delete line IDs: {line_ids}")
231
+
230
232
  if not line_ids:
231
233
  return jsonify({'error': 'No line IDs provided'}), 400
232
234
 
@@ -1214,7 +1216,8 @@ def register_database_api_routes(app):
1214
1216
  all_lines_data.append({
1215
1217
  'timestamp': float(line.timestamp),
1216
1218
  'game_name': line.game_name or 'Unknown Game',
1217
- 'characters': len(line.line_text) if line.line_text else 0
1219
+ 'characters': len(line.line_text) if line.line_text else 0,
1220
+ 'id': line.id
1218
1221
  })
1219
1222
  except Exception as e:
1220
1223
  logger.error(f"Error preparing all lines data: {e}")
@@ -578,6 +578,7 @@ document.addEventListener('DOMContentLoaded', function () {
578
578
  // Session navigation button handlers
579
579
  const prevSessionBtn = document.querySelector('.prev-session-btn');
580
580
  const nextSessionBtn = document.querySelector('.next-session-btn');
581
+ const deleteSessionBtn = document.querySelector('.delete-session-btn');
581
582
 
582
583
  function updateSessionNavigationButtons() {
583
584
  if (!window.todaySessionDetails || window.todaySessionDetails.length === 0) {
@@ -597,6 +598,32 @@ document.addEventListener('DOMContentLoaded', function () {
597
598
  updateSessionNavigationButtons();
598
599
  }
599
600
 
601
+ function deleteSession(session) {
602
+ const line_ids = session.lines.map(line => line.id);
603
+ fetch('/api/delete-sentence-lines', {
604
+ method: 'POST',
605
+ headers: {
606
+ 'Content-Type': 'application/json'
607
+ },
608
+ body: JSON.stringify({ line_ids })
609
+ })
610
+ .then(response => response.json())
611
+ .then(data => {
612
+ if (data.success) {
613
+ // Remove the session from the list
614
+ window.todaySessionDetails = window.todaySessionDetails.filter(s => s !== session);
615
+ // Update the UI
616
+ updateCurrentSessionOverview(window.todaySessionDetails, window.currentSessionIndex);
617
+ updateSessionNavigationButtons();
618
+ } else {
619
+ console.error('Failed to delete session:', data.error);
620
+ }
621
+ })
622
+ .catch(error => {
623
+ console.error('Error deleting session:', error);
624
+ });
625
+ }
626
+
600
627
  prevSessionBtn.addEventListener('click', () => {
601
628
  if (!window.todaySessionDetails) return;
602
629
  let idx = window.currentSessionIndex || 0;
@@ -613,6 +640,24 @@ document.addEventListener('DOMContentLoaded', function () {
613
640
  }
614
641
  });
615
642
 
643
+ deleteSessionBtn.addEventListener('click', () => {
644
+ if (!window.todaySessionDetails || window.todaySessionDetails.length === 0) return;
645
+ const idx = window.currentSessionIndex || 0;
646
+ const sessionToDelete = window.todaySessionDetails[idx];
647
+ if (!sessionToDelete) return;
648
+
649
+ // Confirm deletion
650
+ const confirm1 = confirm(`Are you sure you want to delete the session starting at ${new Date(sessionToDelete.startTime * 1000).toLocaleString()}? This will delete ${sessionToDelete.lines.length} lines. This action cannot be undone.`);
651
+ if (!confirm1) return;
652
+ const confirm2 = confirm("Are you REALLY sure? This cannot be undone.");
653
+ if (!confirm2) return;
654
+ const confirm3 = confirm("Final warning: Delete this session permanently?");
655
+ if (!confirm3) return;
656
+
657
+ // Call the delete function
658
+ deleteSession(sessionToDelete);
659
+ });
660
+
616
661
  // Update navigation buttons whenever sessions are loaded
617
662
  document.addEventListener('datesSet', () => {
618
663
  setTimeout(updateSessionNavigationButtons, 1200);
@@ -182,6 +182,7 @@
182
182
  <div class="session-navigation">
183
183
  <button class="prev-session-btn" data-action="prevSession">←</button>
184
184
  <button class="next-session-btn" data-action="nextSession">→</button>
185
+ <button class="delete-session-btn" data-action="deleteSession" title="Delete Current Session">🗑️</button>
185
186
  </div>
186
187
  </div>
187
188
  <div class="dashboard-stats-grid" id="currentSessionStats">
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.18.19
4
- Summary: A tool for mining sentences from games. Fix Goals Char Count
3
+ Version: 2.18.20
4
+ Summary: A tool for mining sentences from games. Update: Dependencies, replay buffer based line searching, and bug fixes.
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
7
7
  Project-URL: Homepage, https://github.com/bpwhelan/GameSentenceMiner
@@ -12,43 +12,42 @@ Classifier: Operating System :: OS Independent
12
12
  Requires-Python: >=3.10
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
- Requires-Dist: requests~=2.32.3
16
- Requires-Dist: watchdog~=5.0.2
17
- Requires-Dist: DateTime~=5.5
18
- Requires-Dist: pyperclip~=1.9.0
19
- Requires-Dist: soundfile~=0.12.1
20
- Requires-Dist: toml~=0.10.2
21
- Requires-Dist: psutil~=7.1.0
22
- Requires-Dist: rapidfuzz~=3.9.7
23
- Requires-Dist: plyer~=2.1.0
24
- Requires-Dist: keyboard~=0.13.5
25
- Requires-Dist: websockets~=15.0.1
26
- Requires-Dist: openai-whisper==20240930; sys_platform == "win32"
27
- Requires-Dist: openai-whisper; sys_platform != "win32"
28
- Requires-Dist: stable-ts-whisperless
29
- Requires-Dist: silero-vad~=5.1.2
30
- Requires-Dist: ttkbootstrap~=1.10.1
31
- Requires-Dist: dataclasses_json~=0.6.7
32
- Requires-Dist: win10toast; sys_platform == "win32"
33
- Requires-Dist: pystray
34
- Requires-Dist: pywin32; sys_platform == "win32"
35
- Requires-Dist: pygetwindow; sys_platform == "win32"
36
- Requires-Dist: flask
37
- Requires-Dist: groq
38
- Requires-Dist: matplotlib
39
- Requires-Dist: sounddevice
40
- Requires-Dist: google-genai
41
- Requires-Dist: owocr
42
- Requires-Dist: oneocr
43
- Requires-Dist: openai
44
- Requires-Dist: scikit-image
45
- Requires-Dist: opencv-python
15
+ Requires-Dist: requests>=2.32.3
16
+ Requires-Dist: watchdog>=5.0.2
17
+ Requires-Dist: DateTime>=5.5
18
+ Requires-Dist: pyperclip>=1.9.0
19
+ Requires-Dist: soundfile>=0.12.1
20
+ Requires-Dist: toml>=0.10.2
21
+ Requires-Dist: psutil>=7.1.0
22
+ Requires-Dist: rapidfuzz>=3.9.7
23
+ Requires-Dist: plyer>=2.1.0
24
+ Requires-Dist: keyboard>=0.13.5
25
+ Requires-Dist: websockets>=15.0.1
26
+ Requires-Dist: ttkbootstrap>=1.10.1
27
+ Requires-Dist: dataclasses_json>=0.6.7
46
28
  Requires-Dist: betterproto==2.0.0b7
47
- Requires-Dist: obsws-python~=1.7.2
29
+ Requires-Dist: obsws-python>=1.7.2
48
30
  Requires-Dist: numpy==2.2.6
49
- Requires-Dist: regex
50
- Requires-Dist: faster-whisper~=1.2.0
51
- Requires-Dist: torchaudio==2.8.0
31
+ Requires-Dist: faster-whisper>=1.2.0
32
+ Requires-Dist: silero-vad>=6.0.0
33
+ Requires-Dist: regex>=2025.9.18
34
+ Requires-Dist: opencv-python>=4.12.0.88
35
+ Requires-Dist: scikit-image>=0.25.2
36
+ Requires-Dist: openai>=1.108.0
37
+ Requires-Dist: owocr>=1.9.1
38
+ Requires-Dist: oneocr>=1.0.10
39
+ Requires-Dist: google-genai>=1.38.0
40
+ Requires-Dist: sounddevice>=0.5.2
41
+ Requires-Dist: matplotlib>=3.10.6
42
+ Requires-Dist: groq>=0.31.1
43
+ Requires-Dist: flask>=3.1.2
44
+ Requires-Dist: pystray>=0.19.5
45
+ Requires-Dist: pygetwindow>=0.0.9; sys_platform == "win32"
46
+ Requires-Dist: pywin32>=311; sys_platform == "win32"
47
+ Requires-Dist: win10toast>=0.9; sys_platform == "win32"
48
+ Requires-Dist: stable-ts>=2.19.1
49
+ Requires-Dist: torchcodec>=0.7.0
50
+ Requires-Dist: torchaudio>=2.8.0
52
51
  Dynamic: license-file
53
52
 
54
53
  # GSM - An Immersion toolkit for Games.
@@ -1,9 +1,9 @@
1
1
  GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  GameSentenceMiner/anki.py,sha256=9pjjJrQS7IbA6iTtOaJ0Hs2nBddYXTCZBF5lZ5I4dOs,29593
3
- GameSentenceMiner/gametext.py,sha256=FBL3kgJ71hCg5Nczuo9dAEi_sLGdVIGgvc62bT5KhCc,10691
3
+ GameSentenceMiner/gametext.py,sha256=mXQCArkt_2gKPFGrJdigqEJwf1AYCnuzZJ4Unf_clcg,10924
4
4
  GameSentenceMiner/gsm.py,sha256=0hEpEBDbI9FtiKtHeyrSLKV1nys-mKTKfxLY0Dk7mOQ,36387
5
- GameSentenceMiner/obs.py,sha256=PtO_zB8zhaBxm_Vp8ggf2JmYAJ72cclBLo0kPuUBIoc,36766
6
- GameSentenceMiner/vad.py,sha256=jT0pNfoKeoqSAveK9Cl1j9RVKkakeHvoohAge_D3tnU,21275
5
+ GameSentenceMiner/obs.py,sha256=GJqIj1Q0zI5WwylJcDbwtj2FZjzqrwubgV2ZXWHLR_Q,37102
6
+ GameSentenceMiner/vad.py,sha256=iMSsoUZ7-aNoWKzDKfOHdB3Zk5U2hV7x5hqTny6rj08,21501
7
7
  GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  GameSentenceMiner/ai/ai_prompting.py,sha256=mq9Odv_FpohXagU-OoSZbLWttdrEl1M1NiqnodeUpD8,29126
9
9
  GameSentenceMiner/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -42,16 +42,16 @@ GameSentenceMiner/ui/furigana_filter_preview.py,sha256=DAT2-j6vSDHr9ufk6PiaLikEs
42
42
  GameSentenceMiner/ui/screenshot_selector.py,sha256=7QvDhOMpA0ej8x_lYtu6fhmrWbM1GCg-dps3XVWwk1Q,8234
43
43
  GameSentenceMiner/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
44
  GameSentenceMiner/util/audio_player.py,sha256=-yFsf0qoTSS1ga5rCmEJZJGUSJzXCvfZHY3t0NxycDk,7896
45
- GameSentenceMiner/util/configuration.py,sha256=qndhFAN4oC1dawklllS3UBhK2DCVSTloGdZxDoTUGr4,48137
45
+ GameSentenceMiner/util/configuration.py,sha256=E-pS5EqWbagJi-wozBKcSsAqDGypAsYSrQPoGOXgTYc,48222
46
46
  GameSentenceMiner/util/db.py,sha256=FQUvMHcQv_bRNE9LfrsFIxXXXog8BhOA1t4mr_UXoiI,33019
47
47
  GameSentenceMiner/util/electron_config.py,sha256=KfeJToeFFVw0IR5MKa-gBzpzaGrU-lyJbR9z-sDEHYU,8767
48
48
  GameSentenceMiner/util/ffmpeg.py,sha256=cAzztfY36Xf2WvsJDjavoiMOvA9ac2GVdCrSB4LzHk4,29007
49
49
  GameSentenceMiner/util/games_table.py,sha256=VM68MAsdyE6tpdwM4bDSk67qioBOvsEO8-TpnRmUnSo,12003
50
- GameSentenceMiner/util/get_overlay_coords.py,sha256=TJz3iVimiwhRLwkYNB1uOZnDR4BEWWUWYjoJEe7uSQs,25160
50
+ GameSentenceMiner/util/get_overlay_coords.py,sha256=R3ovcosfLf9UtysN6RSu1s2r6WcQQLhZDgf0slW6yHQ,25208
51
51
  GameSentenceMiner/util/gsm_utils.py,sha256=mASECTmN10c2yPL4NEfLg0Y0YWwFso1i6r_hhJPR3MY,10974
52
52
  GameSentenceMiner/util/model.py,sha256=R-_RYTYLSDNgBoVTPuPBcIHeOznIqi_vBzQ7VQ20WYk,6727
53
53
  GameSentenceMiner/util/notification.py,sha256=YBhf_mSo_i3cjBz-pmeTPx3wchKiG9BK2VBdZSa2prQ,4597
54
- GameSentenceMiner/util/text_log.py,sha256=ZR98XFiOx9OWbZz5DzviHLjbg-j01OnPtIj6MnSfFU4,6877
54
+ GameSentenceMiner/util/text_log.py,sha256=nb4N6bId1hwVWSnKjbxl0kvTqBhJW2UL-xmwtyKmKrk,6835
55
55
  GameSentenceMiner/util/communication/__init__.py,sha256=xh__yn2MhzXi9eLi89PeZWlJPn-cbBSjskhi1BRraXg,643
56
56
  GameSentenceMiner/util/communication/send.py,sha256=Wki9qIY2CgYnuHbmnyKVIYkcKAN_oYS4up93XMikBaI,222
57
57
  GameSentenceMiner/util/communication/websocket.py,sha256=Zpnqsy8RUeYxMFNGVUaPrWrlbAHjuNxCsn908iWL_kU,3344
@@ -63,7 +63,7 @@ GameSentenceMiner/util/win10toast/__init__.py,sha256=6TL2w6rzNmpJEp6_v2cAJP_7ExA
63
63
  GameSentenceMiner/util/win10toast/__main__.py,sha256=5MYnBcFj8y_6Dyc1kiPd0_FsUuh4yl1cv5wsleU6V4w,668
64
64
  GameSentenceMiner/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
65
  GameSentenceMiner/web/anki_api_endpoints.py,sha256=r30OTT3YVfgbF6aJ-EGWZLF-j2D9L63jLkRXMycU0p8,23681
66
- GameSentenceMiner/web/database_api.py,sha256=fLpVMZLn-LNwm-M0jNZLDSGH9dV6ml4zn28hNPHcOKY,89783
66
+ GameSentenceMiner/web/database_api.py,sha256=wJGFwrPbB7qQMIqwg0w6hn_henFhjAUCIpwjhdUNMGU,89903
67
67
  GameSentenceMiner/web/events.py,sha256=6Vyz5c9MdpMIa7Zqljqhap2XFQnAVYJ0CdQV64TSZsA,5119
68
68
  GameSentenceMiner/web/gsm_websocket.py,sha256=B0VKpxmsRu0WRh5nFWlpDPBQ6-K2ed7TEIa0O6YWeoo,4166
69
69
  GameSentenceMiner/web/service.py,sha256=6cgUmDgtp3ZKzuPFszowjPoq-BDtC1bS3ux6sykeaqo,6662
@@ -91,7 +91,7 @@ GameSentenceMiner/web/static/js/database.js,sha256=kM5hDl0merhKjbKQhWklLGi1L_O1_
91
91
  GameSentenceMiner/web/static/js/goals.js,sha256=PaJNS1jR1pIpBxYrI8Gog6XFQNPrnbaNWZ1QX7JVlAo,30938
92
92
  GameSentenceMiner/web/static/js/heatmap.js,sha256=9E4SPGxjWX5-EWbXtiKFzNLGY3LtpbUmfmeATcrYRDU,14704
93
93
  GameSentenceMiner/web/static/js/kanji-grid.js,sha256=k9y25X4TvFYi2CEO7cusrcFVSQRBFWAT1LQ3zJhewYc,16365
94
- GameSentenceMiner/web/static/js/overview.js,sha256=-bENoz8GenHkEKdOFaO8WzsXBHOFS7-VaLapW1qRnUY,52400
94
+ GameSentenceMiner/web/static/js/overview.js,sha256=9aqIozJ_s8MQxTmdYii7y50LXm3N_RidUymxrvG_V_c,54376
95
95
  GameSentenceMiner/web/static/js/search.js,sha256=QYbIpmBhFNaQ2O7mPN8k9ChSCikIGJvEo-3jOEU2WPM,16158
96
96
  GameSentenceMiner/web/static/js/shared.js,sha256=ZNibQkJxdypg93uMhmr_NEgoT5hJfxikzF6RKGz5Wy0,20416
97
97
  GameSentenceMiner/web/static/js/stats.js,sha256=zPhkdY3RXiQrhPmZyFyh6xwOn3DxQI8Jm6fqbJbjeDk,51322
@@ -99,7 +99,7 @@ GameSentenceMiner/web/templates/anki_stats.html,sha256=ixMg8nT5gXfTWVFG-xa7pppJu
99
99
  GameSentenceMiner/web/templates/database.html,sha256=5SE7cSYa-ZDVLm0ykb1T11Yd4Bm_E600EWaQi2UWip8,18577
100
100
  GameSentenceMiner/web/templates/goals.html,sha256=X5ViEeUT3YnCVM_kofCJ6A0_Wn2TVQQdmBiblZN5Gpo,20909
101
101
  GameSentenceMiner/web/templates/index.html,sha256=y1mrlcKWRdwmfBP8B06p1CBk1avAJOr32388nX8YX4A,229074
102
- GameSentenceMiner/web/templates/overview.html,sha256=HkEelQ60aJkt2OwNouqY2TxUF3fzRt85VHOe32C2-TM,13117
102
+ GameSentenceMiner/web/templates/overview.html,sha256=7hX1rrFhwCWHo9mH-Ia_39FSD3ACCoWTXtVUl7gc1kM,13248
103
103
  GameSentenceMiner/web/templates/search.html,sha256=34mv69GQXBGq-TdagyZ82QpXH9JYWGOXMDbCfoGUoGI,6150
104
104
  GameSentenceMiner/web/templates/stats.html,sha256=RJhWQ1lyOGkYWrqxJNutnJs703IVdCQhBG9Cud0CMPs,5605
105
105
  GameSentenceMiner/web/templates/utility.html,sha256=KtqnZUMAYs5XsEdC9Tlsd40NKAVic0mu6sh-ReMDJpU,16940
@@ -135,9 +135,9 @@ GameSentenceMiner/web/templates/components/kanji_grid/thousand_character_classic
135
135
  GameSentenceMiner/web/templates/components/kanji_grid/wanikani_levels.json,sha256=8wjnnaYQqmho6t5tMxrIAc03512A2tYhQh5dfsQnfAM,11372
136
136
  GameSentenceMiner/web/templates/components/kanji_grid/words_hk_frequency_list.json,sha256=wRkqZNPzz6DT9OTPHpXwfqW96Qb96stCQNNgOL-ZdKk,17535
137
137
  GameSentenceMiner/wip/__init___.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
138
- gamesentenceminer-2.18.19.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
139
- gamesentenceminer-2.18.19.dist-info/METADATA,sha256=_cfxOe5CJVlJmfSSqWwFjFEVpkXs3gQiCgiQzNurcrc,7525
140
- gamesentenceminer-2.18.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
141
- gamesentenceminer-2.18.19.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
142
- gamesentenceminer-2.18.19.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
143
- gamesentenceminer-2.18.19.dist-info/RECORD,,
138
+ gamesentenceminer-2.18.20.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
139
+ gamesentenceminer-2.18.20.dist-info/METADATA,sha256=2bFyGqlXBTNqDLYIFyGtxT6nVbzqDqqBci5059JcwqY,7603
140
+ gamesentenceminer-2.18.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
141
+ gamesentenceminer-2.18.20.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
142
+ gamesentenceminer-2.18.20.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
143
+ gamesentenceminer-2.18.20.dist-info/RECORD,,