GameSentenceMiner 2.13.10__tar.gz → 2.13.12__tar.gz

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.
Files changed (79) hide show
  1. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/gsm.py +14 -14
  2. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/configuration.py +1 -1
  3. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/ffmpeg.py +12 -12
  4. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/web/service.py +6 -1
  5. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/web/texthooking_page.py +60 -18
  6. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner.egg-info/PKG-INFO +1 -1
  7. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/PKG-INFO +1 -1
  8. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/pyproject.toml +1 -1
  9. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/__init__.py +0 -0
  10. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/ai/__init__.py +0 -0
  11. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/ai/ai_prompting.py +0 -0
  12. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/anki.py +0 -0
  13. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/assets/__init__.py +0 -0
  14. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/assets/icon.png +0 -0
  15. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/assets/icon128.png +0 -0
  16. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/assets/icon256.png +0 -0
  17. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/assets/icon32.png +0 -0
  18. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/assets/icon512.png +0 -0
  19. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/assets/icon64.png +0 -0
  20. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/assets/pickaxe.png +0 -0
  21. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/config_gui.py +0 -0
  22. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/gametext.py +0 -0
  23. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/locales/en_us.json +0 -0
  24. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/locales/ja_jp.json +0 -0
  25. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/locales/zh_cn.json +0 -0
  26. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/obs.py +0 -0
  27. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/ocr/__init__.py +0 -0
  28. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/ocr/gsm_ocr_config.py +0 -0
  29. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/ocr/ocrconfig.py +0 -0
  30. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/ocr/owocr_area_selector.py +0 -0
  31. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/ocr/owocr_helper.py +0 -0
  32. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/ocr/ss_picker.py +0 -0
  33. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/owocr/owocr/__init__.py +0 -0
  34. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/owocr/owocr/__main__.py +0 -0
  35. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/owocr/owocr/config.py +0 -0
  36. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -0
  37. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/owocr/owocr/ocr.py +0 -0
  38. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/owocr/owocr/run.py +0 -0
  39. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -0
  40. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/__init__.py +0 -0
  41. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/audio_offset_selector.py +0 -0
  42. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/communication/__init__.py +0 -0
  43. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/communication/send.py +0 -0
  44. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/communication/websocket.py +0 -0
  45. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/downloader/Untitled_json.py +0 -0
  46. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/downloader/__init__.py +0 -0
  47. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/downloader/download_tools.py +0 -0
  48. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/downloader/oneocr_dl.py +0 -0
  49. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/electron_config.py +0 -0
  50. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/gsm_utils.py +0 -0
  51. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/model.py +0 -0
  52. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/notification.py +0 -0
  53. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/package.py +0 -0
  54. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/ss_selector.py +0 -0
  55. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/text_log.py +0 -0
  56. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/util/window_transparency.py +0 -0
  57. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/vad.py +0 -0
  58. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/web/__init__.py +0 -0
  59. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/web/static/__init__.py +0 -0
  60. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
  61. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/web/static/favicon-96x96.png +0 -0
  62. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/web/static/favicon.ico +0 -0
  63. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/web/static/favicon.svg +0 -0
  64. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/web/static/site.webmanifest +0 -0
  65. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/web/static/style.css +0 -0
  66. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
  67. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
  68. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/web/templates/__init__.py +0 -0
  69. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/web/templates/index.html +0 -0
  70. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/web/templates/text_replacements.html +0 -0
  71. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/web/templates/utility.html +0 -0
  72. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner/wip/get_overlay_coords.py +0 -0
  73. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner.egg-info/SOURCES.txt +0 -0
  74. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner.egg-info/dependency_links.txt +0 -0
  75. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner.egg-info/entry_points.txt +0 -0
  76. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner.egg-info/requires.txt +0 -0
  77. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/GameSentenceMiner.egg-info/top_level.txt +0 -0
  78. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/LICENSE +0 -0
  79. {gamesentenceminer-2.13.10 → gamesentenceminer-2.13.12}/setup.cfg +0 -0
@@ -53,7 +53,7 @@ try:
53
53
  from GameSentenceMiner.util.text_log import GameLine, get_text_event, get_mined_line, get_all_lines, game_log
54
54
  from GameSentenceMiner.util import *
55
55
  from GameSentenceMiner.web import texthooking_page
56
- from GameSentenceMiner.web.service import handle_texthooker_button
56
+ from GameSentenceMiner.web.service import handle_texthooker_button, set_get_audio_from_video_callback
57
57
  from GameSentenceMiner.web.texthooking_page import run_text_hooker_page
58
58
  except Exception as e:
59
59
  from GameSentenceMiner.util.configuration import logger, is_linux, is_windows
@@ -91,10 +91,10 @@ class VideoToAudioHandler(FileSystemEventHandler):
91
91
  selected_lines = []
92
92
  anki_card_creation_time = None
93
93
  mined_line = None
94
- gsm_state.previous_replay = video_path
94
+ start_time = 0
95
+ end_time = 0
95
96
  if gsm_state.line_for_audio or gsm_state.line_for_screenshot:
96
- handle_texthooker_button(
97
- video_path, get_audio_from_video=VideoToAudioHandler.get_audio)
97
+ handle_texthooker_button(video_path)
98
98
  return
99
99
  try:
100
100
  if anki.card_queue and len(anki.card_queue) > 0:
@@ -201,16 +201,14 @@ class VideoToAudioHandler(FileSystemEventHandler):
201
201
  logger.debug(
202
202
  f"Some error was hit catching to allow further work to be done: {e}", exc_info=True)
203
203
  notification.send_error_no_anki_update()
204
- finally:
205
- if get_config().paths.remove_video and video_path and not skip_delete:
206
- try:
207
- if os.path.exists(video_path):
208
- logger.debug(f"Removing video: {video_path}")
209
- os.remove(video_path)
210
- except Exception as e:
211
- logger.error(
212
- f"Error removing video file {video_path}: {e}", exc_info=True)
213
- pass
204
+ if get_config().paths.remove_video and video_path and not skip_delete:
205
+ try:
206
+ if os.path.exists(video_path):
207
+ logger.debug(f"Removing video: {video_path}")
208
+ os.remove(video_path)
209
+ except Exception as e:
210
+ logger.error(
211
+ f"Error removing video file {video_path}: {e}", exc_info=True)
214
212
 
215
213
  @staticmethod
216
214
  def get_audio(game_line, next_line_time, video_path, anki_card_creation_time=None, temporary=False, timing_only=False, mined_line=None):
@@ -525,6 +523,7 @@ def handle_exit():
525
523
  def initialize(reloading=False):
526
524
  global obs_process
527
525
  if not reloading:
526
+ get_temporary_directory(delete=True)
528
527
  if is_windows():
529
528
  download_obs_if_needed()
530
529
  download_ffmpeg_if_needed()
@@ -538,6 +537,7 @@ def initialize(reloading=False):
538
537
  # gametext.start_text_monitor()
539
538
  os.makedirs(get_config().paths.folder_to_watch, exist_ok=True)
540
539
  os.makedirs(get_config().paths.output_folder, exist_ok=True)
540
+ set_get_audio_from_video_callback(VideoToAudioHandler.get_audio)
541
541
  initial_checks()
542
542
  register_websocket_message_handler(handle_websocket_message)
543
543
  # if get_config().vad.do_vad_postprocessing:
@@ -843,7 +843,7 @@ def get_log_path():
843
843
 
844
844
  temp_directory = ''
845
845
 
846
- def get_temporary_directory(delete=True):
846
+ def get_temporary_directory(delete=False):
847
847
  global temp_directory
848
848
  if not temp_directory:
849
849
  temp_directory = os.path.join(get_app_directory(), 'temp')
@@ -117,7 +117,7 @@ def get_screenshot(video_file, screenshot_timing, try_selector=False):
117
117
  get_temporary_directory(),
118
118
  f"{obs.get_current_game(sanitize=True)}.png"))
119
119
  ffmpeg_command = ffmpeg_base_command_list + [
120
- "-ss", f"{screenshot_timing}", # Default to 1 second
120
+ "-ss", f"{screenshot_timing}",
121
121
  "-i", video_file,
122
122
  "-vframes", "1",
123
123
  output_image
@@ -222,7 +222,7 @@ def process_image(image_file):
222
222
  break
223
223
  except Exception as e:
224
224
  logger.error(f"Error re-encoding screenshot: {e}. Defaulting to standard PNG.")
225
- output_image = make_unique_file_name(get_temporary_directory(), f"{obs.get_current_game(sanitize=True)}.png")
225
+ output_image = make_unique_file_name(os.path.join(get_temporary_directory(), f"{obs.get_current_game(sanitize=True)}.png"))
226
226
  shutil.move(image_file, output_image)
227
227
 
228
228
  logger.info(f"Processed image saved to: {output_image}")
@@ -303,21 +303,21 @@ def trim_audio_based_on_last_line(untrimmed_audio, video_path, game_line, next_l
303
303
  suffix=f".{get_config().audio.extension}").name
304
304
  start_trim_time, total_seconds, total_seconds_after_offset, file_length = get_video_timings(video_path, game_line, anki_card_creation_time)
305
305
  end_trim_time = 0
306
+ end_trim_seconds = 0
306
307
 
307
308
  ffmpeg_command = ffmpeg_base_command_list + [
308
309
  "-i", untrimmed_audio,
309
310
  "-ss", str(start_trim_time)]
310
311
  if next_line and next_line > game_line.time:
311
- end_total_seconds = total_seconds + (next_line - game_line.time).total_seconds() + get_config().audio.pre_vad_end_offset
312
- end_trim_time = f"{end_total_seconds:.3f}"
312
+ end_trim_seconds = total_seconds + (next_line - game_line.time).total_seconds() + get_config().audio.pre_vad_end_offset
313
+ end_trim_time = f"{end_trim_seconds:.3f}"
313
314
  ffmpeg_command.extend(['-to', end_trim_time])
314
315
  logger.debug(
315
316
  f"Looks Like this is mining from History, or Multiple Lines were selected Trimming end of audio to {end_trim_time} seconds")
316
317
  elif get_config().audio.pre_vad_end_offset and get_config().audio.pre_vad_end_offset < 0:
317
- end_total_seconds = file_length + get_config().audio.pre_vad_end_offset
318
- end_trim_time = f"{end_total_seconds:.3f}"
319
- ffmpeg_command.extend(['-to', end_trim_time])
320
- logger.debug(f"Trimming end of audio to {end_trim_time} seconds due to pre-vad end offset")
318
+ end_trim_seconds = file_length + get_config().audio.pre_vad_end_offset
319
+ ffmpeg_command.extend(['-to', str(end_trim_seconds)])
320
+ logger.debug(f"Trimming end of audio to {end_trim_seconds} seconds due to pre-vad end offset")
321
321
 
322
322
  ffmpeg_command.extend([
323
323
  "-c", "copy", # Using copy to avoid re-encoding, adjust if needed
@@ -326,18 +326,18 @@ def trim_audio_based_on_last_line(untrimmed_audio, video_path, game_line, next_l
326
326
 
327
327
  logger.debug(" ".join(ffmpeg_command))
328
328
  subprocess.run(ffmpeg_command)
329
- gsm_state.previous_trim_args = (untrimmed_audio, start_trim_time, end_trim_time)
329
+ gsm_state.previous_trim_args = (untrimmed_audio, start_trim_time, end_trim_seconds)
330
330
 
331
331
  logger.debug(f"{total_seconds_after_offset} trimmed off of beginning")
332
332
 
333
- if end_trim_time:
334
- logger.info(f"Audio Extracted and trimmed to {start_trim_time} seconds with end time {end_trim_time}")
333
+ if end_trim_seconds:
334
+ logger.info(f"Audio Extracted and trimmed to {start_trim_time} seconds with end time {end_trim_seconds} seconds")
335
335
  else:
336
336
  logger.info(f"Audio Extracted and trimmed to {start_trim_time} seconds")
337
337
 
338
338
 
339
339
  logger.debug(f"Audio trimmed and saved to {trimmed_audio}")
340
- return trimmed_audio, start_trim_time, end_trim_time
340
+ return trimmed_audio, start_trim_time, end_trim_seconds
341
341
 
342
342
  def get_video_timings(video_path, game_line, anki_card_creation_time=None):
343
343
  if anki_card_creation_time:
@@ -11,7 +11,12 @@ from GameSentenceMiner.util.ffmpeg import get_video_timings
11
11
  from GameSentenceMiner.util.text_log import GameLine
12
12
 
13
13
 
14
- def handle_texthooker_button(video_path='', get_audio_from_video=None):
14
+ def set_get_audio_from_video_callback(func):
15
+ global get_audio_from_video
16
+ get_audio_from_video = func
17
+
18
+
19
+ def handle_texthooker_button(video_path=''):
15
20
  try:
16
21
  if gsm_state.line_for_audio:
17
22
  line: GameLine = gsm_state.line_for_audio
@@ -52,6 +52,7 @@ class EventItem:
52
52
  'history': self.history,
53
53
  }
54
54
 
55
+
55
56
  class EventManager:
56
57
  events: list[EventItem]
57
58
  events_dict: dict[str, EventItem] = {}
@@ -87,7 +88,8 @@ class EventManager:
87
88
  event_id, line_id, text, timestamp = row
88
89
  timestamp = datetime.datetime.fromisoformat(timestamp)
89
90
  line = GameLine(line_id, text, timestamp, None, None, 0)
90
- event = EventItem(line, event_id, text, timestamp, False, timestamp < initial_time)
91
+ event = EventItem(line, event_id, text, timestamp,
92
+ False, timestamp < initial_time)
91
93
  self.events.append(event)
92
94
  self.ids.append(event_id)
93
95
  self.events_dict[event_id] = event
@@ -99,7 +101,8 @@ class EventManager:
99
101
  self.events = new_events
100
102
 
101
103
  def add_gameline(self, line: GameLine):
102
- new_event = EventItem(line, line.id, line.text, line.time, False, False)
104
+ new_event = EventItem(line, line.id, line.text,
105
+ line.time, False, False)
103
106
  self.events_dict[line.id] = new_event
104
107
  self.ids.append(line.id)
105
108
  self.events.append(new_event)
@@ -130,12 +133,16 @@ class EventManager:
130
133
  self.conn.close()
131
134
 
132
135
  def clear_history(self):
133
- self.cursor.execute("DELETE FROM events WHERE time < ?", (initial_time.isoformat(),))
136
+ self.cursor.execute("DELETE FROM events WHERE time < ?",
137
+ (initial_time.isoformat(),))
134
138
  logger.info(f"Cleared history before {initial_time.isoformat()}")
135
139
  self.conn.commit()
136
140
  # Clear the in-memory events as well
137
- event_manager.events = [event for event in event_manager if not event.history]
138
- event_manager.events_dict = {event.id: event for event in event_manager.events}
141
+ event_manager.events = [
142
+ event for event in event_manager if not event.history]
143
+ event_manager.events_dict = {
144
+ event.id: event for event in event_manager.events}
145
+
139
146
 
140
147
  class EventProcessor(threading.Thread):
141
148
  def __init__(self, event_queue, db_path):
@@ -173,6 +180,7 @@ class EventProcessor(threading.Thread):
173
180
  if self.conn:
174
181
  self.conn.close()
175
182
 
183
+
176
184
  event_manager = EventManager()
177
185
  event_queue = queue.Queue()
178
186
 
@@ -185,6 +193,8 @@ server_start_time = datetime.datetime.now().timestamp()
185
193
  app = flask.Flask(__name__)
186
194
 
187
195
  # Load data from the JSON file
196
+
197
+
188
198
  def load_data_from_file():
189
199
  if os.path.exists(TEXT_REPLACEMENTS_FILE):
190
200
  with open(TEXT_REPLACEMENTS_FILE, 'r', encoding='utf-8') as file:
@@ -192,10 +202,13 @@ def load_data_from_file():
192
202
  return {"enabled": True, "args": {"replacements": {}}}
193
203
 
194
204
  # Save data to the JSON file
205
+
206
+
195
207
  def save_data_to_file(data):
196
208
  with open(TEXT_REPLACEMENTS_FILE, 'w', encoding='utf-8') as file:
197
209
  json.dump(data, file, indent=4, ensure_ascii=False)
198
210
 
211
+
199
212
  @app.route('/load-data', methods=['GET'])
200
213
  def load_data():
201
214
  try:
@@ -204,6 +217,7 @@ def load_data():
204
217
  except Exception as e:
205
218
  return jsonify({"error": f"Failed to load data: {str(e)}"}), 500
206
219
 
220
+
207
221
  @app.route('/save-data', methods=['POST'])
208
222
  def save_data():
209
223
  try:
@@ -217,40 +231,49 @@ def save_data():
217
231
  except Exception as e:
218
232
  return jsonify({"error": f"Failed to save data: {str(e)}"}), 500
219
233
 
234
+
220
235
  def inject_server_start_time(html_content, timestamp):
221
236
  placeholder = '<script>'
222
237
  replacement = f'<script>const serverStartTime = {timestamp};'
223
238
  return html_content.replace(placeholder, replacement)
224
239
 
240
+
225
241
  @app.route('/favicon.ico')
226
242
  def favicon():
227
243
  return send_from_directory(os.path.join(app.root_path, 'static'),
228
244
  'favicon.ico', mimetype='image/vnd.microsoft.icon')
229
245
 
246
+
230
247
  @app.route('/<path:filename>')
231
248
  def serve_static(filename):
232
249
  return send_from_directory('pages', filename)
233
250
 
251
+
234
252
  @app.route('/')
235
253
  def index():
236
254
  return send_from_directory('templates', 'index.html')
237
255
 
256
+
238
257
  @app.route('/texthooker')
239
258
  def texthooker():
240
259
  return send_from_directory('templates', 'index.html')
241
260
 
261
+
242
262
  @app.route('/textreplacements')
243
263
  def textreplacements():
244
264
  return flask.render_template('text_replacements.html')
245
265
 
266
+
246
267
  @app.route('/data', methods=['GET'])
247
268
  def get_data():
248
269
  return jsonify([event.to_dict() for event in event_manager])
249
270
 
271
+
250
272
  @app.route('/get_ids', methods=['GET'])
251
273
  def get_ids():
252
274
  return jsonify(event_manager.get_ids())
253
275
 
276
+
254
277
  @app.route('/clear_history', methods=['POST'])
255
278
  def clear_history():
256
279
  temp_em = EventManager()
@@ -268,8 +291,8 @@ async def add_event_to_texthooker(line: GameLine):
268
291
  })
269
292
  if get_config().advanced.plaintext_websocket_port:
270
293
  await plaintext_websocket_server_thread.send_text(line.text)
271
-
272
-
294
+
295
+
273
296
  async def send_word_coordinates_to_overlay(boxes):
274
297
  if boxes and len(boxes) > 0 and overlay_server_thread:
275
298
  await overlay_server_thread.send_text(boxes)
@@ -286,6 +309,7 @@ def update_event():
286
309
  event_manager.get(event_id).checked = not event.checked
287
310
  return jsonify({'message': 'Event updated successfully'}), 200
288
311
 
312
+
289
313
  @app.route('/get-screenshot', methods=['Post'])
290
314
  def get_screenshot():
291
315
  """Endpoint to get a screenshot of the current game screen."""
@@ -294,12 +318,13 @@ def get_screenshot():
294
318
  if event_id is None:
295
319
  return jsonify({'error': 'Missing id'}), 400
296
320
  gsm_state.line_for_screenshot = get_line_by_id(event_id)
297
- if gsm_state.previous_line_for_screenshot and gsm_state.line_for_screenshot.id == gsm_state.previous_line_for_screenshot.id or gsm_state.previous_line_for_audio:
321
+ 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:
298
322
  handle_texthooker_button(gsm_state.previous_replay)
299
323
  else:
300
324
  obs.save_replay_buffer()
301
325
  return jsonify({}), 200
302
326
 
327
+
303
328
  @app.route('/play-audio', methods=['POST'])
304
329
  def play_audio():
305
330
  """Endpoint to play audio for a specific event."""
@@ -307,13 +332,16 @@ def play_audio():
307
332
  event_id = data.get('id')
308
333
  if event_id is None:
309
334
  return jsonify({'error': 'Missing id'}), 400
335
+ print(f"Playing audio for event ID: {event_id}")
310
336
  gsm_state.line_for_audio = get_line_by_id(event_id)
311
- 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:
337
+ print(f"gsm_state.line_for_audio: {gsm_state.line_for_audio}")
338
+ 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:
312
339
  handle_texthooker_button(gsm_state.previous_replay)
313
340
  else:
314
341
  obs.save_replay_buffer()
315
342
  return jsonify({}), 200
316
343
 
344
+
317
345
  @app.route("/translate-line", methods=['POST'])
318
346
  def translate_line():
319
347
  data = request.get_json()
@@ -362,9 +390,11 @@ def get_status():
362
390
  def get_selected_lines():
363
391
  return [item.line for item in event_manager if item.checked]
364
392
 
393
+
365
394
  def are_lines_selected():
366
395
  return any(item.checked for item in event_manager)
367
396
 
397
+
368
398
  def reset_checked_lines():
369
399
  async def send_reset_message():
370
400
  await websocket_server_thread.send_text({
@@ -373,9 +403,11 @@ def reset_checked_lines():
373
403
  event_manager.reset_checked_lines()
374
404
  asyncio.run(send_reset_message())
375
405
 
406
+
376
407
  def open_texthooker():
377
408
  webbrowser.open(url + '/texthooker')
378
409
 
410
+
379
411
  def start_web_server():
380
412
  logger.debug("Starting web server...")
381
413
  import logging
@@ -386,7 +418,8 @@ def start_web_server():
386
418
  if get_config().general.open_multimine_on_startup:
387
419
  open_texthooker()
388
420
 
389
- app.run(host='0.0.0.0', port=port, debug=False) # debug=True provides helpful error messages during development
421
+ # debug=True provides helpful error messages during development
422
+ app.run(host='0.0.0.0', port=port, debug=False)
390
423
 
391
424
 
392
425
  websocket_queue = queue.Queue()
@@ -445,7 +478,7 @@ class WebsocketServerThread(threading.Thread):
445
478
  text = json.dumps(text)
446
479
  return asyncio.run_coroutine_threadsafe(
447
480
  self.send_text_coroutine(text), self.loop)
448
-
481
+
449
482
  def has_clients(self):
450
483
  return len(self.clients) > 0
451
484
 
@@ -467,24 +500,30 @@ class WebsocketServerThread(threading.Thread):
467
500
  await stop_event.wait()
468
501
  return
469
502
  except Exception as e:
470
- logger.warning(f"WebSocket server encountered an error: {e}. Retrying...")
503
+ logger.warning(
504
+ f"WebSocket server encountered an error: {e}. Retrying...")
471
505
  await asyncio.sleep(1)
472
506
 
473
507
  asyncio.run(main())
474
508
 
509
+
475
510
  def handle_exit_signal(loop):
476
511
  logger.info("Received exit signal. Shutting down...")
477
512
  for task in asyncio.all_tasks(loop):
478
513
  task.cancel()
479
-
480
- websocket_server_thread = WebsocketServerThread(read=True, get_ws_port_func=lambda : get_config().get_field_value('advanced', 'texthooker_communication_websocket_port'))
514
+
515
+
516
+ websocket_server_thread = WebsocketServerThread(read=True, get_ws_port_func=lambda: get_config(
517
+ ).get_field_value('advanced', 'texthooker_communication_websocket_port'))
481
518
  websocket_server_thread.start()
482
519
 
483
520
  if get_config().advanced.plaintext_websocket_port:
484
- plaintext_websocket_server_thread = WebsocketServerThread(read=False, get_ws_port_func=lambda : get_config().get_field_value('advanced', 'plaintext_websocket_port'))
521
+ plaintext_websocket_server_thread = WebsocketServerThread(
522
+ read=False, get_ws_port_func=lambda: get_config().get_field_value('advanced', 'plaintext_websocket_port'))
485
523
  plaintext_websocket_server_thread.start()
486
-
487
- overlay_server_thread = WebsocketServerThread(read=False, get_ws_port_func=lambda : get_config().get_field_value('wip', 'overlay_websocket_port'))
524
+
525
+ overlay_server_thread = WebsocketServerThread(
526
+ read=False, get_ws_port_func=lambda: get_config().get_field_value('wip', 'overlay_websocket_port'))
488
527
  overlay_server_thread.start()
489
528
 
490
529
  websocket_server_threads = [
@@ -493,6 +532,7 @@ websocket_server_threads = [
493
532
  overlay_server_thread
494
533
  ]
495
534
 
535
+
496
536
  async def texthooker_page_coro():
497
537
  global websocket_server_thread, plaintext_websocket_server_thread, overlay_server_thread
498
538
  # Run the WebSocket server in the asyncio event loop
@@ -502,11 +542,13 @@ async def texthooker_page_coro():
502
542
 
503
543
  # Keep the main asyncio event loop running (for the WebSocket server)
504
544
 
545
+
505
546
  def run_text_hooker_page():
506
547
  try:
507
548
  asyncio.run(texthooker_page_coro())
508
549
  except KeyboardInterrupt:
509
550
  logger.info("Shutting down due to KeyboardInterrupt.")
510
551
 
552
+
511
553
  if __name__ == '__main__':
512
- asyncio.run(texthooker_page_coro())
554
+ asyncio.run(texthooker_page_coro())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.13.10
3
+ Version: 2.13.12
4
4
  Summary: A tool for mining sentences from games. Update: Overlay?
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.13.10
3
+ Version: 2.13.12
4
4
  Summary: A tool for mining sentences from games. Update: Overlay?
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "GameSentenceMiner"
10
- version = "2.13.10"
10
+ version = "2.13.12"
11
11
  description = "A tool for mining sentences from games. Update: Overlay?"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.10"