GameSentenceMiner 2.3.1__py3-none-any.whl → 2.3.3__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/anki.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import base64
2
+ import queue
2
3
  import subprocess
3
4
  import threading
4
5
  import time
@@ -12,7 +13,7 @@ from GameSentenceMiner.configuration import *
12
13
  from GameSentenceMiner.configuration import get_config
13
14
  from GameSentenceMiner.gametext import get_last_two_sentences
14
15
  from GameSentenceMiner.obs import get_current_game
15
- from util import remove_html_tags
16
+ from GameSentenceMiner.util import remove_html_tags
16
17
 
17
18
  audio_in_anki = None
18
19
  screenshot_in_anki = None
@@ -20,6 +21,7 @@ screenshot_in_anki = None
20
21
  # Global variables to track state
21
22
  previous_note_ids = set()
22
23
  first_run = True
24
+ card_queue = []
23
25
 
24
26
 
25
27
  def update_anki_card(last_note, note=None, audio_path='', video_path='', tango='', reuse_audio=False,
@@ -40,6 +42,7 @@ def update_anki_card(last_note, note=None, audio_path='', video_path='', tango='
40
42
  screenshot_in_anki = store_media_file(screenshot)
41
43
  if get_config().paths.remove_screenshot:
42
44
  os.remove(screenshot)
45
+ util.set_last_mined_line(get_sentence(last_note))
43
46
  audio_html = f"[sound:{audio_in_anki}]"
44
47
  image_html = f"<img src=\"{screenshot_in_anki}\">"
45
48
 
@@ -70,8 +73,6 @@ def update_anki_card(last_note, note=None, audio_path='', video_path='', tango='
70
73
  if get_config().features.open_anki_edit:
71
74
  notification.open_anki_card(last_note['noteId'])
72
75
 
73
- util.set_last_mined_line(get_sentence(last_note))
74
-
75
76
  if get_config().audio.external_tool:
76
77
  open_audio_in_external(f"{get_config().audio.anki_media_collection}/{audio_in_anki}")
77
78
 
@@ -211,20 +212,18 @@ def update_new_card():
211
212
  last_card = get_last_anki_card()
212
213
  if not check_tags_for_should_update(last_card):
213
214
  return
215
+ use_prev_audio = sentence_is_same_as_previous(last_card)
216
+ logger.info(f"last mined line: {util.get_last_mined_line()}, current sentence: {get_sentence(last_card)}")
217
+ logger.info(f"use previous audio: {use_prev_audio}")
218
+ if get_config().obs.get_game_from_scene:
219
+ obs.update_current_game()
220
+ if use_prev_audio:
221
+ update_anki_card(last_card, note=get_initial_card_info(last_card, []), reuse_audio=True)
222
+ else:
223
+ logger.info("New card(s) detected! Added to Processing Queue!")
224
+ card_queue.append(last_card)
225
+ obs.save_replay_buffer()
214
226
 
215
- if util.lock.locked():
216
- logger.info("Audio still being Trimmed, Card Queued!")
217
- with util.lock:
218
- use_prev_audio = sentence_is_same_as_previous(last_card)
219
- logger.info(f"last mined line: {util.get_last_mined_line()}, current sentence: {get_sentence(last_card)}")
220
- logger.info(f"use previous audio: {use_prev_audio}")
221
- if get_config().obs.get_game_from_scene:
222
- obs.update_current_game()
223
- if use_prev_audio:
224
- update_anki_card(last_card, note=get_initial_card_info(last_card, []), reuse_audio=True)
225
- else:
226
- logger.info("New card(s) detected!")
227
- obs.save_replay_buffer()
228
227
 
229
228
  def sentence_is_same_as_previous(last_card):
230
229
  if not util.get_last_mined_line():
@@ -276,3 +275,4 @@ def start_monitoring_anki():
276
275
  obs_thread = threading.Thread(target=monitor_anki)
277
276
  obs_thread.daemon = True # Ensures the thread will exit when the main program exits
278
277
  obs_thread.start()
278
+
@@ -171,7 +171,8 @@ class ConfigApp:
171
171
  extension=self.screenshot_extension.get(),
172
172
  custom_ffmpeg_settings=self.screenshot_custom_ffmpeg_settings.get(),
173
173
  screenshot_hotkey_updates_anki=self.screenshot_hotkey_update_anki.get(),
174
- seconds_after_line = self.seconds_after_line.get()
174
+ seconds_after_line = self.seconds_after_line.get(),
175
+ use_beginning_of_line_as_screenshot=self.use_beginning_of_line_as_screenshot.get()
175
176
  ),
176
177
  audio=Audio(
177
178
  extension=self.audio_extension.get(),
@@ -700,6 +701,11 @@ class ConfigApp:
700
701
  self.add_label_and_increment_row(screenshot_frame, "This is only used for mining from lines from history (not current line)", row=self.current_row,
701
702
  column=2)
702
703
 
704
+ ttk.Label(screenshot_frame, text="Use Beginning of Line as Screenshot:").grid(row=self.current_row, column=0, sticky='W')
705
+ self.use_beginning_of_line_as_screenshot = tk.BooleanVar(value=self.settings.screenshot.use_beginning_of_line_as_screenshot)
706
+ ttk.Checkbutton(screenshot_frame, variable=self.use_beginning_of_line_as_screenshot).grid(row=self.current_row, column=1, sticky='W')
707
+ self.add_label_and_increment_row(screenshot_frame, "Enable to use the beginning of the line as the screenshot point. Adjust the above setting to fine-tine timing.", row=self.current_row, column=2)
708
+
703
709
  @new_tab
704
710
  def create_audio_tab(self):
705
711
  audio_frame = ttk.Frame(self.notebook)
@@ -103,6 +103,7 @@ class Screenshot:
103
103
  custom_ffmpeg_settings: str = ''
104
104
  screenshot_hotkey_updates_anki: bool = False
105
105
  seconds_after_line: int = 1
106
+ use_beginning_of_line_as_screenshot: bool = True
106
107
 
107
108
 
108
109
  @dataclass_json
@@ -9,6 +9,7 @@ from typing import Callable
9
9
  import pyperclip
10
10
  import websockets
11
11
 
12
+ import util
12
13
  from GameSentenceMiner.configuration import *
13
14
  from GameSentenceMiner.configuration import get_config, logger
14
15
  from GameSentenceMiner.util import remove_html_tags
@@ -88,6 +89,7 @@ def reset_line_hotkey_pressed():
88
89
  logger.info("LINE RESET HOTKEY PRESSED")
89
90
  current_line_time = datetime.now()
90
91
  line_history[current_line_after_regex] = current_line_time
92
+ util.set_last_mined_line("")
91
93
 
92
94
 
93
95
  def run_websocket_listener():
@@ -139,8 +141,8 @@ def get_last_two_sentences(last_note):
139
141
  if not last_note:
140
142
  return lines[-1][0] if lines else '', lines[-2][0] if len(lines) > 1 else ''
141
143
 
142
- current_line = ""
143
- prev_line = ""
144
+ current = ""
145
+ previous = ""
144
146
 
145
147
  sentence = last_note['fields'][get_config().anki.sentence_field]['value']
146
148
  if sentence:
@@ -149,20 +151,20 @@ def get_last_two_sentences(last_note):
149
151
  similarity = similar(remove_html_tags(sentence), line)
150
152
  logger.debug(f"Comparing: {remove_html_tags(sentence)} with {line} - Similarity: {similarity}")
151
153
  if found:
152
- prev_line = line
154
+ previous = line
153
155
  break
154
156
  if similarity >= 0.60 or line in remove_html_tags(sentence): # 80% similarity threshold
155
157
  found = True
156
- current_line = line
158
+ current = line
157
159
 
158
- logger.debug(f"Current Line: {current_line}")
159
- logger.debug(f"Previous Line: {prev_line}")
160
+ logger.debug(f"Current Line: {current}")
161
+ logger.debug(f"Previous Line: {previous}")
160
162
 
161
- if not current_line or not prev_line:
163
+ if not current or not previous:
162
164
  logger.debug("Couldn't find lines in history, using last two lines")
163
165
  return lines[-1][0] if lines else '', lines[-2][0] if len(lines) > 1 else ''
164
166
 
165
- return current_line, prev_line
167
+ return current, previous
166
168
 
167
169
 
168
170
  def get_line_and_future_lines(last_note):
GameSentenceMiner/gsm.py CHANGED
@@ -31,7 +31,7 @@ from GameSentenceMiner.util import *
31
31
  if is_windows():
32
32
  import win32api
33
33
 
34
- obs_process: Popen = None
34
+ obs_process = None
35
35
  procs_to_close = []
36
36
  settings_window: config_gui.ConfigApp = None
37
37
  utility_window: utility_gui.UtilityApp = None
@@ -54,6 +54,8 @@ class VideoToAudioHandler(FileSystemEventHandler):
54
54
  def convert_to_audio(video_path):
55
55
  try:
56
56
  with util.lock:
57
+ if anki.card_queue and len(anki.card_queue) > 0:
58
+ last_note = anki.card_queue.pop(0)
57
59
  if os.path.exists(video_path) and os.access(video_path, os.R_OK):
58
60
  logger.debug(f"Video found and is readable: {video_path}")
59
61
 
@@ -64,17 +66,17 @@ class VideoToAudioHandler(FileSystemEventHandler):
64
66
  logger.error(
65
67
  f"Video was unusually small, potentially empty! Check OBS for Correct Scene Settings! Path: {video_path}")
66
68
  return
67
- last_note = None
68
- logger.debug("Attempting to get last anki card")
69
- if get_config().anki.update_anki:
70
- last_note = anki.get_last_anki_card()
71
- if get_config().features.backfill_audio:
72
- last_note = anki.get_cards_by_sentence(gametext.current_line)
69
+ if not last_note:
70
+ logger.debug("Attempting to get last anki card")
71
+ if get_config().anki.update_anki:
72
+ last_note = anki.get_last_anki_card()
73
+ if get_config().features.backfill_audio:
74
+ last_note = anki.get_cards_by_sentence(gametext.current_line)
73
75
  line_time, next_line_time = get_line_timing(last_note)
74
76
  if utility_window.lines_selected():
75
77
  line_time, next_line_time = utility_window.get_selected_times()
76
78
  ss_timing = 0
77
- if line_time and next_line_time:
79
+ if line_time and next_line_time or line_time and get_config().screenshot.use_beginning_of_line_as_screenshot:
78
80
  ss_timing = ffmpeg.get_screenshot_time(video_path, line_time)
79
81
  if last_note:
80
82
  logger.debug(json.dumps(last_note))
@@ -322,11 +324,33 @@ def run_tray():
322
324
  icon = Icon("TrayApp", create_image(), "Game Sentence Miner", menu)
323
325
  icon.run()
324
326
 
327
+ # def close_obs():
328
+ # if obs_process:
329
+ # logger.info("Closing OBS")
330
+ # proc = None
331
+ # if obs_process:
332
+ # try:
333
+ # logger.info("Closing OBS")
334
+ # proc = psutil.Process(obs_process)
335
+ # proc.send_signal(signal.CTRL_BREAK_EVENT)
336
+ # proc.wait(timeout=5)
337
+ # logger.info("Process closed gracefully.")
338
+ # except psutil.NoSuchProcess:
339
+ # logger.info("PID already closed.")
340
+ # except psutil.TimeoutExpired:
341
+ # logger.info("Process did not close gracefully, terminating.")
342
+ # proc.terminate()
343
+ # proc.wait()
344
+
325
345
  def close_obs():
326
346
  if obs_process:
327
- logger.info("Closing OBS")
328
- obs_process.terminate()
329
- obs_process.wait()
347
+ try:
348
+ subprocess.run(["taskkill", "/PID", str(obs_process), "/F"], check=True, capture_output=True, text=True)
349
+ print(f"OBS (PID {obs_process}) has been terminated.")
350
+ except subprocess.CalledProcessError as e:
351
+ print(f"Error terminating OBS: {e.stderr}")
352
+ else:
353
+ print("OBS is not running.")
330
354
 
331
355
  def restart_obs():
332
356
  global obs_process
GameSentenceMiner/obs.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import subprocess
2
2
  import time
3
3
 
4
+ import psutil
4
5
  from obswebsocket import obsws, requests
5
6
 
6
7
  from GameSentenceMiner import util, configuration
@@ -22,15 +23,25 @@ def start_obs():
22
23
  return None
23
24
 
24
25
  try:
25
- # process = subprocess.Popen([obs_path], cwd=os.path.dirname(obs_path))
26
- # process = subprocess.Popen([obs_path, '--minimize-to-tray'], cwd=os.path.dirname(obs_path))
27
- process = subprocess.Popen([obs_path, '--disable-shutdown-check'], cwd=os.path.dirname(obs_path))
26
+ obs_pid = is_obs_running(obs_path)
27
+ if obs_pid:
28
+ return obs_pid
29
+ process = subprocess.Popen([obs_path, '--disable-shutdown-check', '--portable'], cwd=os.path.dirname(obs_path))
28
30
  logger.info("OBS launched")
29
- return process
31
+ return process.pid
30
32
  except Exception as e:
31
33
  logger.error(f"Error launching OBS: {e}")
32
34
  return None
33
35
 
36
+ def is_obs_running(obs_path):
37
+ obs_path = os.path.abspath(obs_path) # Normalize path
38
+ for process in psutil.process_iter(['exe']):
39
+ try:
40
+ if process.info['exe'] and os.path.abspath(process.info['exe']) == obs_path:
41
+ return process.pid
42
+ except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
43
+ continue
44
+ return False
34
45
 
35
46
  def get_obs_websocket_config_values():
36
47
  config_path = os.path.join(get_app_directory(), 'obs-studio', 'config', 'obs-studio', 'plugin_config', 'obs-websocket', 'config.json')
@@ -46,10 +46,10 @@ class UtilityApp:
46
46
  var = tk.BooleanVar()
47
47
  self.items.append((text, var, time))
48
48
 
49
- # Remove the first checkbox if there are more than 10
50
49
  if len(self.items) > 10:
51
- self.checkboxes[0].destroy()
52
- self.checkboxes.pop(0)
50
+ if self.checkboxes:
51
+ self.checkboxes[0].destroy()
52
+ self.checkboxes.pop(0)
53
53
  self.items.pop(0)
54
54
 
55
55
  if self.multi_mine_window and tk.Toplevel.winfo_exists(self.multi_mine_window):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: GameSentenceMiner
3
- Version: 2.3.1
3
+ Version: 2.3.3
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
@@ -1,16 +1,16 @@
1
1
  GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- GameSentenceMiner/anki.py,sha256=gpd_af7zrwuGyIfMAPvunZW1I0mVG2MBemVnikTVp5c,10151
3
- GameSentenceMiner/config_gui.py,sha256=B37GPeGtGNTJgFTor3M01Tw2iZYRRtHLYck6Q83-JRg,53460
4
- GameSentenceMiner/configuration.py,sha256=x50-InaGl70dHpWhdOAZJTIKj84EtVfTqEmIFCnDcvw,14348
2
+ GameSentenceMiner/anki.py,sha256=OHtzFSwfO3GNPFNfxOyPtOy9ATuM6IpeqPPIJX1x7j8,10116
3
+ GameSentenceMiner/config_gui.py,sha256=10VaSYc2OGdRT219HLrNkBpUONMUtQG5DKAQf1phano,54171
4
+ GameSentenceMiner/configuration.py,sha256=xL3EoYXoKB9amo6K37OY1eFlI9owTmdT3UB-tJJJfx4,14401
5
5
  GameSentenceMiner/ffmpeg.py,sha256=VExJYWSFhYuWukIXgOiHufsoSROEDA8LnVQFG8srRGc,10924
6
- GameSentenceMiner/gametext.py,sha256=ckZGOHpGuvFE2zDNdaASSaMYi4JCFsyG2kYKCbZIQPo,6463
7
- GameSentenceMiner/gsm.py,sha256=l92vu9P06v5crBfka-TGjlSyqM7WG2ND4ZzWQVTY7ws,17103
6
+ GameSentenceMiner/gametext.py,sha256=peP_1psoOF1SkcPqhYJkz8MLM-8XyEj8Yd3wU0sCwSs,6478
7
+ GameSentenceMiner/gsm.py,sha256=X93WBDGn6Tj1PeHjUm1QNrb6E91MdylbCYsrUe1posU,18259
8
8
  GameSentenceMiner/model.py,sha256=oh8VVT8T1UKekbmP6MGNgQ8jIuQ_7Rg4GPzDCn2kJo8,1999
9
9
  GameSentenceMiner/notification.py,sha256=WBaQWoPNhW4XqdPBUmxPBgjk0ngzH_4v9zMQ-XQAKC8,2010
10
- GameSentenceMiner/obs.py,sha256=3Flcjxy812VpF78EPI7sxlGx6yyM3GfqzlinW17SK20,6231
10
+ GameSentenceMiner/obs.py,sha256=8ImXAVUWa4JdzwcBOEFShlZRZzh1dCvdpD1aEGhQfbU,6566
11
11
  GameSentenceMiner/package_updater.py,sha256=0uaLAp0WrWqostNTBWRS0laITjI9aN9Yt_6GXosS4NQ,1278
12
12
  GameSentenceMiner/util.py,sha256=MITweiFYaefWQF5nR8tZ9yE6vd_b-fLuP0MP1Y1U4K0,4720
13
- GameSentenceMiner/utility_gui.py,sha256=-T5b14Nx6KvNnBEmdVz0mWkXoYi-bZzHIce6ADwfVEQ,4701
13
+ GameSentenceMiner/utility_gui.py,sha256=Wa1YLitHzjbzkvvOtt3HbmFnplfajKR3N4aB2fTOlnM,4679
14
14
  GameSentenceMiner/downloader/Untitled_json.py,sha256=RUUl2bbbCpUDUUS0fP0tdvf5FngZ7ILdA_J5TFYAXUQ,15272
15
15
  GameSentenceMiner/downloader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  GameSentenceMiner/downloader/download_tools.py,sha256=M7vLo6_0QMuk1Ji4CsZqk1C2g7Bq6PyM29r7XNoP6Rw,6406
@@ -18,8 +18,8 @@ GameSentenceMiner/vad/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
18
18
  GameSentenceMiner/vad/silero_trim.py,sha256=syDJX_KbFmdyFFtnQqYTD0tICsUCJizYhs-atPgXtxA,1549
19
19
  GameSentenceMiner/vad/vosk_helper.py,sha256=-AAwK0cgOC5rK3_gL0sQgrPJ75E49g_PxZR4d5ckwc4,5826
20
20
  GameSentenceMiner/vad/whisper_helper.py,sha256=bpR1HVnJRn9H5u8XaHBqBJ6JwIjzqn-Fajps8QmQ4zc,3411
21
- GameSentenceMiner-2.3.1.dist-info/METADATA,sha256=UN9Pn6JrUpTuIeMRBT3CAizAUNmVM9aTKw6DCBwxTOg,10120
22
- GameSentenceMiner-2.3.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
23
- GameSentenceMiner-2.3.1.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
24
- GameSentenceMiner-2.3.1.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
25
- GameSentenceMiner-2.3.1.dist-info/RECORD,,
21
+ GameSentenceMiner-2.3.3.dist-info/METADATA,sha256=vcd2xsyVtLoyBMFV0raMrTsaLaawGxSxknXCnmEaqM4,10120
22
+ GameSentenceMiner-2.3.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
23
+ GameSentenceMiner-2.3.3.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
24
+ GameSentenceMiner-2.3.3.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
25
+ GameSentenceMiner-2.3.3.dist-info/RECORD,,