GameSentenceMiner 2.9.4__py3-none-any.whl → 2.9.5__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/ai/ai_prompting.py +3 -3
- GameSentenceMiner/anki.py +16 -14
- GameSentenceMiner/config_gui.py +22 -7
- GameSentenceMiner/gametext.py +5 -5
- GameSentenceMiner/gsm.py +26 -67
- GameSentenceMiner/obs.py +7 -8
- GameSentenceMiner/ocr/owocr_area_selector.py +1 -1
- GameSentenceMiner/ocr/owocr_helper.py +30 -13
- GameSentenceMiner/owocr/owocr/ocr.py +0 -2
- GameSentenceMiner/owocr/owocr/run.py +1 -1
- GameSentenceMiner/{communication → util/communication}/__init__.py +1 -1
- GameSentenceMiner/{communication → util/communication}/send.py +1 -1
- GameSentenceMiner/{communication → util/communication}/websocket.py +2 -2
- GameSentenceMiner/{downloader → util/downloader}/download_tools.py +3 -3
- GameSentenceMiner/vad.py +344 -0
- GameSentenceMiner/web/texthooking_page.py +15 -10
- {gamesentenceminer-2.9.4.dist-info → gamesentenceminer-2.9.5.dist-info}/METADATA +2 -3
- gamesentenceminer-2.9.5.dist-info/RECORD +57 -0
- GameSentenceMiner/configuration.py +0 -647
- GameSentenceMiner/electron_config.py +0 -315
- GameSentenceMiner/ffmpeg.py +0 -441
- GameSentenceMiner/model.py +0 -177
- GameSentenceMiner/notification.py +0 -126
- GameSentenceMiner/package.py +0 -38
- GameSentenceMiner/ss_selector.py +0 -121
- GameSentenceMiner/text_log.py +0 -186
- GameSentenceMiner/util.py +0 -262
- GameSentenceMiner/vad/__init__.py +0 -0
- GameSentenceMiner/vad/groq_trim.py +0 -82
- GameSentenceMiner/vad/result.py +0 -21
- GameSentenceMiner/vad/silero_trim.py +0 -52
- GameSentenceMiner/vad/vad_utils.py +0 -13
- GameSentenceMiner/vad/vosk_helper.py +0 -158
- GameSentenceMiner/vad/whisper_helper.py +0 -105
- gamesentenceminer-2.9.4.dist-info/RECORD +0 -72
- /GameSentenceMiner/{downloader → util/downloader}/Untitled_json.py +0 -0
- /GameSentenceMiner/{downloader → util/downloader}/__init__.py +0 -0
- /GameSentenceMiner/{downloader → util/downloader}/oneocr_dl.py +0 -0
- {gamesentenceminer-2.9.4.dist-info → gamesentenceminer-2.9.5.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.9.4.dist-info → gamesentenceminer-2.9.5.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.9.4.dist-info → gamesentenceminer-2.9.5.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.9.4.dist-info → gamesentenceminer-2.9.5.dist-info}/top_level.txt +0 -0
@@ -3,14 +3,14 @@ import textwrap
|
|
3
3
|
from abc import ABC, abstractmethod
|
4
4
|
from dataclasses import dataclass
|
5
5
|
from enum import Enum
|
6
|
-
from typing import List,
|
6
|
+
from typing import List, Optional
|
7
7
|
|
8
8
|
import google.generativeai as genai
|
9
9
|
from google.generativeai import GenerationConfig
|
10
10
|
from groq import Groq
|
11
11
|
|
12
|
-
from GameSentenceMiner.configuration import get_config, Ai, logger
|
13
|
-
from GameSentenceMiner.text_log import GameLine
|
12
|
+
from GameSentenceMiner.util.configuration import get_config, Ai, logger
|
13
|
+
from GameSentenceMiner.util.text_log import GameLine
|
14
14
|
|
15
15
|
# Suppress debug logs from httpcore
|
16
16
|
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
GameSentenceMiner/anki.py
CHANGED
@@ -2,19 +2,19 @@ import time
|
|
2
2
|
|
3
3
|
import base64
|
4
4
|
import subprocess
|
5
|
-
import threading
|
6
5
|
import urllib.request
|
7
6
|
from datetime import datetime, timedelta
|
8
7
|
from requests import post
|
9
8
|
|
10
|
-
from GameSentenceMiner import obs
|
9
|
+
from GameSentenceMiner import obs
|
11
10
|
from GameSentenceMiner.ai.ai_prompting import get_ai_prompt_result
|
12
|
-
from GameSentenceMiner.
|
13
|
-
from GameSentenceMiner.
|
14
|
-
from GameSentenceMiner.
|
15
|
-
from GameSentenceMiner.
|
11
|
+
from GameSentenceMiner.util.gsm_utils import wait_for_stable_file, remove_html_and_cloze_tags, combine_dialogue
|
12
|
+
from GameSentenceMiner.util import ffmpeg, notification
|
13
|
+
from GameSentenceMiner.util.configuration import *
|
14
|
+
from GameSentenceMiner.util.configuration import get_config
|
15
|
+
from GameSentenceMiner.util.model import AnkiCard
|
16
|
+
from GameSentenceMiner.util.text_log import get_all_lines, get_text_event, get_mined_line
|
16
17
|
from GameSentenceMiner.obs import get_current_game
|
17
|
-
from GameSentenceMiner.util import remove_html_and_cloze_tags, combine_dialogue, wait_for_stable_file
|
18
18
|
from GameSentenceMiner.web import texthooking_page
|
19
19
|
|
20
20
|
audio_in_anki = None
|
@@ -82,8 +82,9 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
82
82
|
for key, value in get_config().anki.anki_custom_fields.items():
|
83
83
|
note['fields'][key] = str(value)
|
84
84
|
|
85
|
-
|
86
|
-
|
85
|
+
selected_notes = invoke("guiSelectedNotes")
|
86
|
+
if last_note.noteId in selected_notes:
|
87
|
+
notification.open_browser_window(1)
|
87
88
|
invoke("updateNoteFields", note=note)
|
88
89
|
tags = []
|
89
90
|
if get_config().anki.custom_tags:
|
@@ -96,7 +97,8 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
96
97
|
logger.info(f"UPDATED ANKI CARD FOR {last_note.noteId}")
|
97
98
|
if get_config().features.notify_on_update:
|
98
99
|
notification.send_note_updated(tango)
|
99
|
-
|
100
|
+
if last_note.noteId in selected_notes or get_config().features.open_anki_in_browser:
|
101
|
+
notification.open_browser_window(last_note.noteId, get_config().features.browser_query)
|
100
102
|
if get_config().features.open_anki_edit:
|
101
103
|
notification.open_anki_card(last_note.noteId)
|
102
104
|
|
@@ -282,13 +284,13 @@ def update_new_card():
|
|
282
284
|
if not last_card or not check_tags_for_should_update(last_card):
|
283
285
|
return
|
284
286
|
use_prev_audio = sentence_is_same_as_previous(last_card)
|
285
|
-
logger.debug(f"last mined line: {
|
287
|
+
logger.debug(f"last mined line: {gsm_state.last_mined_line}, current sentence: {get_sentence(last_card)}")
|
286
288
|
logger.info(f"New card using previous audio: {use_prev_audio}")
|
287
289
|
if get_config().obs.get_game_from_scene:
|
288
290
|
obs.update_current_game()
|
289
291
|
if use_prev_audio:
|
290
292
|
lines = texthooking_page.get_selected_lines()
|
291
|
-
with
|
293
|
+
with gsm_state.lock:
|
292
294
|
update_anki_card(last_card, note=get_initial_card_info(last_card, lines), game_line=get_mined_line(last_card, lines), reuse_audio=True)
|
293
295
|
texthooking_page.reset_checked_lines()
|
294
296
|
else:
|
@@ -303,9 +305,9 @@ def update_new_card():
|
|
303
305
|
|
304
306
|
|
305
307
|
def sentence_is_same_as_previous(last_card):
|
306
|
-
if not
|
308
|
+
if not gsm_state.last_mined_line:
|
307
309
|
return False
|
308
|
-
return remove_html_and_cloze_tags(get_sentence(last_card)) == remove_html_and_cloze_tags(
|
310
|
+
return remove_html_and_cloze_tags(get_sentence(last_card)) == remove_html_and_cloze_tags(gsm_state.last_mined_line)
|
309
311
|
|
310
312
|
def get_sentence(card):
|
311
313
|
return card.get_field(get_config().anki.sentence_field)
|
GameSentenceMiner/config_gui.py
CHANGED
@@ -4,12 +4,12 @@ from tkinter import filedialog, messagebox, simpledialog, scrolledtext
|
|
4
4
|
|
5
5
|
import ttkbootstrap as ttk
|
6
6
|
|
7
|
-
from GameSentenceMiner import obs
|
8
|
-
from GameSentenceMiner.
|
9
|
-
from GameSentenceMiner.
|
10
|
-
from GameSentenceMiner.
|
11
|
-
from GameSentenceMiner.
|
12
|
-
from GameSentenceMiner.package import get_current_version, get_latest_version
|
7
|
+
from GameSentenceMiner import obs
|
8
|
+
from GameSentenceMiner.util import configuration
|
9
|
+
from GameSentenceMiner.util.communication.send import send_restart_signal
|
10
|
+
from GameSentenceMiner.util.configuration import *
|
11
|
+
from GameSentenceMiner.util.downloader.download_tools import download_ocenaudio_if_needed
|
12
|
+
from GameSentenceMiner.util.package import get_current_version, get_latest_version
|
13
13
|
|
14
14
|
settings_saved = False
|
15
15
|
on_save = []
|
@@ -172,7 +172,9 @@ class ConfigApp:
|
|
172
172
|
full_auto=self.full_auto.get(),
|
173
173
|
notify_on_update=self.notify_on_update.get(),
|
174
174
|
open_anki_edit=self.open_anki_edit.get(),
|
175
|
-
|
175
|
+
open_anki_in_browser=self.open_anki_browser.get(),
|
176
|
+
backfill_audio=self.backfill_audio.get(),
|
177
|
+
browser_query=self.browser_query.get(),
|
176
178
|
),
|
177
179
|
screenshot=Screenshot(
|
178
180
|
enabled=self.screenshot_enabled.get(),
|
@@ -753,6 +755,19 @@ class ConfigApp:
|
|
753
755
|
self.add_label_and_increment_row(features_frame, "Automatically open Anki for editing after updating.",
|
754
756
|
row=self.current_row, column=2)
|
755
757
|
|
758
|
+
ttk.Label(features_frame, text="Open Anki Note in Browser:").grid(row=self.current_row, column=0, sticky='W')
|
759
|
+
self.open_anki_browser = tk.BooleanVar(value=self.settings.features.open_anki_in_browser)
|
760
|
+
ttk.Checkbutton(features_frame, variable=self.open_anki_browser).grid(row=self.current_row, column=1, sticky='W')
|
761
|
+
self.add_label_and_increment_row(features_frame, "Open Anki note in browser after updating.", row=self.current_row,
|
762
|
+
column=2)
|
763
|
+
|
764
|
+
ttk.Label(features_frame, text="Browser Query:").grid(row=self.current_row, column=0, sticky='W')
|
765
|
+
self.browser_query = ttk.Entry(features_frame, width=50)
|
766
|
+
self.browser_query.insert(0, self.settings.features.browser_query)
|
767
|
+
self.browser_query.grid(row=self.current_row, column=1)
|
768
|
+
self.add_label_and_increment_row(features_frame, "Query to use when opening Anki notes in the browser. Ex: 'Added:1'", row=self.current_row,
|
769
|
+
column=2)
|
770
|
+
|
756
771
|
ttk.Label(features_frame, text="Backfill Audio:").grid(row=self.current_row, column=0, sticky='W')
|
757
772
|
self.backfill_audio = tk.BooleanVar(value=self.settings.features.backfill_audio)
|
758
773
|
ttk.Checkbutton(features_frame, variable=self.backfill_audio).grid(row=self.current_row, column=1, sticky='W')
|
GameSentenceMiner/gametext.py
CHANGED
@@ -6,9 +6,9 @@ import websockets
|
|
6
6
|
from websockets import InvalidStatus
|
7
7
|
|
8
8
|
from GameSentenceMiner import util
|
9
|
-
from GameSentenceMiner.
|
10
|
-
from GameSentenceMiner.
|
11
|
-
from GameSentenceMiner.util import
|
9
|
+
from GameSentenceMiner.util.gsm_utils import do_text_replacements, TEXT_REPLACEMENTS_FILE, run_new_thread
|
10
|
+
from GameSentenceMiner.util.configuration import *
|
11
|
+
from GameSentenceMiner.util.text_log import *
|
12
12
|
|
13
13
|
from GameSentenceMiner.web.texthooking_page import add_event_to_texthooker
|
14
14
|
|
@@ -121,7 +121,7 @@ def reset_line_hotkey_pressed():
|
|
121
121
|
global current_line_time
|
122
122
|
logger.info("LINE RESET HOTKEY PRESSED")
|
123
123
|
current_line_time = datetime.now()
|
124
|
-
|
124
|
+
gsm_state.last_mined_line = ""
|
125
125
|
|
126
126
|
|
127
127
|
def run_websocket_listener():
|
@@ -129,7 +129,7 @@ def run_websocket_listener():
|
|
129
129
|
|
130
130
|
|
131
131
|
async def start_text_monitor():
|
132
|
-
|
132
|
+
run_new_thread(run_websocket_listener)
|
133
133
|
if get_config().general.use_websocket:
|
134
134
|
if get_config().general.use_both_clipboard_and_websocket:
|
135
135
|
logger.info("Listening for Text on both WebSocket and Clipboard.")
|
GameSentenceMiner/gsm.py
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
import asyncio
|
2
|
+
import subprocess
|
2
3
|
import sys
|
4
|
+
import threading
|
3
5
|
|
4
|
-
from GameSentenceMiner.
|
6
|
+
from GameSentenceMiner.util.gsm_utils import wait_for_stable_file, make_unique_file_name, run_new_thread
|
7
|
+
from GameSentenceMiner.util.communication.send import send_restart_signal
|
8
|
+
from GameSentenceMiner.util.downloader.download_tools import download_obs_if_needed, download_ffmpeg_if_needed
|
9
|
+
from GameSentenceMiner.vad import vad_processor, VADResult
|
5
10
|
|
6
11
|
try:
|
7
12
|
import os.path
|
@@ -19,26 +24,21 @@ try:
|
|
19
24
|
|
20
25
|
from GameSentenceMiner import anki
|
21
26
|
from GameSentenceMiner import config_gui
|
22
|
-
from GameSentenceMiner import configuration
|
23
|
-
from GameSentenceMiner import ffmpeg
|
27
|
+
from GameSentenceMiner.util import configuration, notification, ffmpeg
|
24
28
|
from GameSentenceMiner import gametext
|
25
|
-
from GameSentenceMiner import notification
|
26
29
|
from GameSentenceMiner import obs
|
27
|
-
from GameSentenceMiner import
|
28
|
-
from GameSentenceMiner.communication import
|
29
|
-
from GameSentenceMiner.communication.send import send_restart_signal
|
30
|
-
from GameSentenceMiner.communication.websocket import connect_websocket, register_websocket_message_handler, \
|
30
|
+
from GameSentenceMiner.util.communication import Message
|
31
|
+
from GameSentenceMiner.util.communication.websocket import connect_websocket, register_websocket_message_handler, \
|
31
32
|
FunctionName
|
32
|
-
from GameSentenceMiner.configuration import *
|
33
|
-
from GameSentenceMiner.
|
34
|
-
from GameSentenceMiner.ffmpeg import get_audio_and_trim, get_video_timings
|
33
|
+
from GameSentenceMiner.util.configuration import *
|
34
|
+
from GameSentenceMiner.util.ffmpeg import get_audio_and_trim, get_video_timings
|
35
35
|
from GameSentenceMiner.obs import check_obs_folder_is_correct
|
36
|
-
from GameSentenceMiner.text_log import GameLine, get_text_event, get_mined_line, get_all_lines
|
36
|
+
from GameSentenceMiner.util.text_log import GameLine, get_text_event, get_mined_line, get_all_lines
|
37
37
|
from GameSentenceMiner.util import *
|
38
38
|
from GameSentenceMiner.web import texthooking_page
|
39
39
|
from GameSentenceMiner.web.texthooking_page import run_text_hooker_page
|
40
40
|
except Exception as e:
|
41
|
-
from GameSentenceMiner.configuration import logger, is_linux, is_windows
|
41
|
+
from GameSentenceMiner.util.configuration import logger, is_linux, is_windows
|
42
42
|
import time
|
43
43
|
logger.info("Something bad happened during import/initialization, closing in 5 seconds")
|
44
44
|
logger.exception(e)
|
@@ -48,7 +48,6 @@ except Exception as e:
|
|
48
48
|
if is_windows():
|
49
49
|
import win32api
|
50
50
|
|
51
|
-
silero_trim, whisper_helper, vosk_helper = None, None, None
|
52
51
|
procs_to_close = []
|
53
52
|
settings_window: config_gui.ConfigApp = None
|
54
53
|
obs_paused = False
|
@@ -87,11 +86,10 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
87
86
|
else:
|
88
87
|
logger.info("Replay buffer initiated externally. Skipping processing.")
|
89
88
|
return
|
90
|
-
with
|
91
|
-
|
89
|
+
with gsm_state.lock:
|
90
|
+
gsm_state.last_mined_line = anki.get_sentence(last_note)
|
92
91
|
if os.path.exists(video_path) and os.access(video_path, os.R_OK):
|
93
92
|
logger.debug(f"Video found and is readable: {video_path}")
|
94
|
-
|
95
93
|
if get_config().obs.minimum_replay_size and not ffmpeg.is_video_big_enough(video_path,
|
96
94
|
get_config().obs.minimum_replay_size):
|
97
95
|
logger.debug("Checking if video is big enough")
|
@@ -212,7 +210,6 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
212
210
|
|
213
211
|
@staticmethod
|
214
212
|
def get_audio(game_line, next_line_time, video_path, anki_card_creation_time=None, temporary=False, timing_only=False, mined_line=None):
|
215
|
-
logger.info("Getting audio from video...")
|
216
213
|
trimmed_audio = get_audio_and_trim(video_path, game_line, next_line_time, anki_card_creation_time)
|
217
214
|
if temporary:
|
218
215
|
return trimmed_audio
|
@@ -220,47 +217,16 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
220
217
|
f"{os.path.abspath(configuration.get_temporary_directory())}/{obs.get_current_game(sanitize=True)}.{get_config().audio.extension}")
|
221
218
|
final_audio_output = make_unique_file_name(os.path.join(get_config().paths.audio_destination,
|
222
219
|
f"{obs.get_current_game(sanitize=True)}.{get_config().audio.extension}"))
|
223
|
-
|
224
|
-
|
225
|
-
result = do_vad_processing(get_config().vad.selected_vad_model, trimmed_audio, vad_trimmed_audio, game_line=mined_line)
|
226
|
-
if not result.success:
|
227
|
-
result = do_vad_processing(get_config().vad.selected_vad_model, trimmed_audio,
|
228
|
-
vad_trimmed_audio, game_line=mined_line)
|
229
|
-
if not result.success:
|
230
|
-
if get_config().vad.add_audio_on_no_results:
|
231
|
-
logger.info("No voice activity detected, using full audio.")
|
232
|
-
vad_trimmed_audio = trimmed_audio
|
233
|
-
else:
|
234
|
-
logger.info("No voice activity detected.")
|
235
|
-
return None, result, None
|
236
|
-
else:
|
237
|
-
logger.info(result.trim_successful_string())
|
220
|
+
|
221
|
+
vad_result = vad_processor.trim_audio_with_vad(trimmed_audio, vad_trimmed_audio, game_line)
|
238
222
|
if timing_only:
|
239
|
-
return
|
223
|
+
return vad_result
|
240
224
|
if get_config().audio.ffmpeg_reencode_options and os.path.exists(vad_trimmed_audio):
|
241
225
|
ffmpeg.reencode_file_with_user_config(vad_trimmed_audio, final_audio_output,
|
242
226
|
get_config().audio.ffmpeg_reencode_options)
|
243
227
|
elif os.path.exists(vad_trimmed_audio):
|
244
228
|
shutil.move(vad_trimmed_audio, final_audio_output)
|
245
|
-
return final_audio_output,
|
246
|
-
|
247
|
-
|
248
|
-
def do_vad_processing(model, trimmed_audio, vad_trimmed_audio, game_line=None, second_pass=False):
|
249
|
-
match model:
|
250
|
-
case configuration.OFF:
|
251
|
-
pass
|
252
|
-
case configuration.GROQ:
|
253
|
-
from GameSentenceMiner.vad import groq_trim
|
254
|
-
return groq_trim.process_audio_with_groq(trimmed_audio, vad_trimmed_audio, game_line)
|
255
|
-
case configuration.SILERO:
|
256
|
-
from GameSentenceMiner.vad import silero_trim
|
257
|
-
return silero_trim.process_audio_with_silero(trimmed_audio, vad_trimmed_audio, game_line)
|
258
|
-
case configuration.VOSK:
|
259
|
-
from GameSentenceMiner.vad import vosk_helper
|
260
|
-
return vosk_helper.process_audio_with_vosk(trimmed_audio, vad_trimmed_audio, game_line)
|
261
|
-
case configuration.WHISPER:
|
262
|
-
from GameSentenceMiner.vad import whisper_helper
|
263
|
-
return whisper_helper.process_audio_with_whisper(trimmed_audio, vad_trimmed_audio, game_line)
|
229
|
+
return final_audio_output, vad_result, vad_trimmed_audio
|
264
230
|
|
265
231
|
|
266
232
|
def play_audio_in_external(filepath):
|
@@ -535,7 +501,7 @@ def restart_obs():
|
|
535
501
|
|
536
502
|
def cleanup():
|
537
503
|
logger.info("Performing cleanup...")
|
538
|
-
|
504
|
+
gsm_state.keep_running = False
|
539
505
|
|
540
506
|
if get_config().obs.enabled:
|
541
507
|
obs.stop_replay_buffer()
|
@@ -567,7 +533,7 @@ def cleanup():
|
|
567
533
|
def handle_exit():
|
568
534
|
"""Signal handler for graceful termination."""
|
569
535
|
|
570
|
-
def _handle_exit(signum):
|
536
|
+
def _handle_exit(signum, *args):
|
571
537
|
logger.info(f"Received signal {signum}. Exiting gracefully...")
|
572
538
|
cleanup()
|
573
539
|
sys.exit(0)
|
@@ -602,7 +568,7 @@ def initialize_async():
|
|
602
568
|
threads = []
|
603
569
|
tasks.append(anki.start_monitoring_anki)
|
604
570
|
for task in tasks:
|
605
|
-
threads.append(
|
571
|
+
threads.append(run_new_thread(task))
|
606
572
|
return threads
|
607
573
|
|
608
574
|
def handle_websocket_message(message: Message):
|
@@ -640,14 +606,7 @@ def async_loop():
|
|
640
606
|
await register_scene_switcher_callback()
|
641
607
|
await check_obs_folder_is_correct()
|
642
608
|
logger.info("Post-Initialization started.")
|
643
|
-
|
644
|
-
from GameSentenceMiner.vad import vosk_helper
|
645
|
-
vosk_helper.get_vosk_model()
|
646
|
-
if get_config().vad.is_whisper():
|
647
|
-
from GameSentenceMiner.vad import whisper_helper
|
648
|
-
whisper_helper.initialize_whisper_model()
|
649
|
-
if get_config().vad.is_silero():
|
650
|
-
from GameSentenceMiner.vad import silero_trim
|
609
|
+
vad_processor.init()
|
651
610
|
|
652
611
|
asyncio.run(loop())
|
653
612
|
|
@@ -693,9 +652,9 @@ async def async_main(reloading=False):
|
|
693
652
|
if not is_linux():
|
694
653
|
register_hotkeys()
|
695
654
|
|
696
|
-
|
697
|
-
|
698
|
-
|
655
|
+
run_new_thread(post_init2)
|
656
|
+
run_new_thread(run_text_hooker_page)
|
657
|
+
run_new_thread(async_loop)
|
699
658
|
|
700
659
|
# Register signal handlers for graceful shutdown
|
701
660
|
signal.signal(signal.SIGTERM, handle_exit()) # Handle `kill` commands
|
GameSentenceMiner/obs.py
CHANGED
@@ -7,10 +7,9 @@ import psutil
|
|
7
7
|
|
8
8
|
import obsws_python as obs
|
9
9
|
|
10
|
-
|
11
|
-
from GameSentenceMiner import
|
12
|
-
from GameSentenceMiner.
|
13
|
-
from GameSentenceMiner.model import *
|
10
|
+
from GameSentenceMiner.util import configuration
|
11
|
+
from GameSentenceMiner.util.configuration import *
|
12
|
+
from GameSentenceMiner.util.gsm_utils import sanitize_filename, make_unique_file_name
|
14
13
|
|
15
14
|
client: obs.ReqClient = None
|
16
15
|
event_client: obs.EventClient = None
|
@@ -140,7 +139,7 @@ async def connect_to_obs(retry_count=0):
|
|
140
139
|
if not get_config().obs.enabled:
|
141
140
|
return
|
142
141
|
|
143
|
-
if
|
142
|
+
if is_windows():
|
144
143
|
get_obs_websocket_config_values()
|
145
144
|
|
146
145
|
while True:
|
@@ -174,7 +173,7 @@ def connect_to_obs_sync(retry_count=0):
|
|
174
173
|
if not get_config().obs.enabled or client:
|
175
174
|
return
|
176
175
|
|
177
|
-
if
|
176
|
+
if is_windows():
|
178
177
|
get_obs_websocket_config_values()
|
179
178
|
|
180
179
|
while True:
|
@@ -314,7 +313,7 @@ async def register_scene_change_callback(callback):
|
|
314
313
|
|
315
314
|
def get_screenshot(compression=-1):
|
316
315
|
try:
|
317
|
-
screenshot =
|
316
|
+
screenshot = make_unique_file_name(os.path.abspath(
|
318
317
|
configuration.get_temporary_directory()) + '/screenshot.png')
|
319
318
|
update_current_game()
|
320
319
|
if not configuration.current_game:
|
@@ -367,7 +366,7 @@ def get_current_game(sanitize=False):
|
|
367
366
|
update_current_game()
|
368
367
|
|
369
368
|
if sanitize:
|
370
|
-
return
|
369
|
+
return sanitize_filename(configuration.current_game)
|
371
370
|
return configuration.current_game
|
372
371
|
|
373
372
|
|
@@ -9,7 +9,7 @@ from PIL import Image, ImageTk, ImageDraw
|
|
9
9
|
|
10
10
|
from GameSentenceMiner import obs # Import your actual obs module
|
11
11
|
from GameSentenceMiner.ocr.gsm_ocr_config import set_dpi_awareness
|
12
|
-
from GameSentenceMiner.util import sanitize_filename
|
12
|
+
from GameSentenceMiner.util.gsm_utils import sanitize_filename
|
13
13
|
|
14
14
|
try:
|
15
15
|
import pygetwindow as gw
|
@@ -1,10 +1,8 @@
|
|
1
1
|
import asyncio
|
2
|
-
import ctypes
|
3
2
|
import json
|
4
3
|
import logging
|
5
4
|
import os
|
6
5
|
import queue
|
7
|
-
import re
|
8
6
|
import threading
|
9
7
|
import time
|
10
8
|
from datetime import datetime
|
@@ -16,13 +14,12 @@ import mss
|
|
16
14
|
import websockets
|
17
15
|
from rapidfuzz import fuzz
|
18
16
|
|
19
|
-
from GameSentenceMiner import obs
|
20
|
-
from GameSentenceMiner.configuration import get_config, get_app_directory, get_temporary_directory
|
21
|
-
from GameSentenceMiner.electron_config import get_ocr_scan_rate, get_requires_open_window
|
22
|
-
from GameSentenceMiner.ocr.gsm_ocr_config import OCRConfig,
|
17
|
+
from GameSentenceMiner import obs
|
18
|
+
from GameSentenceMiner.util.configuration import get_config, get_app_directory, get_temporary_directory
|
19
|
+
from GameSentenceMiner.util.electron_config import get_ocr_scan_rate, get_requires_open_window
|
20
|
+
from GameSentenceMiner.ocr.gsm_ocr_config import OCRConfig, set_dpi_awareness
|
23
21
|
from GameSentenceMiner.owocr.owocr import screen_coordinate_picker, run
|
24
|
-
from GameSentenceMiner.
|
25
|
-
from GameSentenceMiner.util import do_text_replacements, OCR_REPLACEMENTS_FILE
|
22
|
+
from GameSentenceMiner.util.gsm_utils import sanitize_filename, do_text_replacements, OCR_REPLACEMENTS_FILE
|
26
23
|
|
27
24
|
CONFIG_FILE = Path("ocr_config.json")
|
28
25
|
DEFAULT_IMAGE_PATH = r"C:\Users\Beangate\Pictures\msedge_acbl8GL7Ax.jpg" # CHANGE THIS
|
@@ -66,7 +63,7 @@ def get_new_game_cords():
|
|
66
63
|
ocr_config_dir = app_dir / "ocr_config"
|
67
64
|
ocr_config_dir.mkdir(parents=True, exist_ok=True)
|
68
65
|
obs.connect_to_obs_sync()
|
69
|
-
scene =
|
66
|
+
scene = sanitize_filename(obs.get_current_scene())
|
70
67
|
config_path = ocr_config_dir / f"{scene}.json"
|
71
68
|
with open(config_path, 'w') as f:
|
72
69
|
json.dump({"scene": scene, "window": None, "rectangles": coords_list}, f, indent=4)
|
@@ -80,7 +77,7 @@ def get_ocr_config() -> OCRConfig:
|
|
80
77
|
ocr_config_dir = app_dir / "ocr_config"
|
81
78
|
os.makedirs(ocr_config_dir, exist_ok=True)
|
82
79
|
obs.connect_to_obs_sync()
|
83
|
-
scene =
|
80
|
+
scene = sanitize_filename(obs.get_current_scene())
|
84
81
|
config_path = ocr_config_dir / f"{scene}.json"
|
85
82
|
if not config_path.exists():
|
86
83
|
config_path.touch()
|
@@ -214,11 +211,11 @@ def do_second_ocr(ocr1_text, time, img, filtering, scrolling=False):
|
|
214
211
|
engine=ocr2)
|
215
212
|
if scrolling:
|
216
213
|
return text
|
217
|
-
if fuzz.ratio(last_ocr2_result,
|
214
|
+
if fuzz.ratio(last_ocr2_result, text) >= 80:
|
218
215
|
logger.info("Seems like the same text from previous ocr2 result, not sending")
|
219
216
|
return
|
220
217
|
save_result_image(img)
|
221
|
-
last_ocr2_result =
|
218
|
+
last_ocr2_result = text
|
222
219
|
asyncio.run(send_result(text, time))
|
223
220
|
except json.JSONDecodeError:
|
224
221
|
print("Invalid JSON received.")
|
@@ -289,13 +286,15 @@ def text_callback(text, orig_text, time, img=None, came_from_ss=False, filtering
|
|
289
286
|
previous_img_local = previous_img
|
290
287
|
if previous_text and fuzz.ratio(orig_text_string, previous_orig_text) >= 90:
|
291
288
|
logger.info("Seems like Text we already sent, not doing anything.")
|
289
|
+
previous_text = None
|
292
290
|
return
|
293
291
|
previous_orig_text = orig_text_string
|
294
292
|
previous_ocr1_result = previous_text
|
295
293
|
if crop_coords:
|
296
294
|
previous_img_local.save(os.path.join(get_temporary_directory(), "pre_oneocrcrop.png"))
|
297
295
|
previous_img_local = previous_img_local.crop(crop_coords)
|
298
|
-
|
296
|
+
second_ocr_queue.put((previous_text, stable_time, previous_img_local, filtering))
|
297
|
+
# threading.Thread(target=do_second_ocr, args=(previous_text, stable_time, previous_img_local, filtering), daemon=True).start()
|
299
298
|
previous_img = None
|
300
299
|
previous_text = None
|
301
300
|
text_stable_start_time = None
|
@@ -312,6 +311,22 @@ def text_callback(text, orig_text, time, img=None, came_from_ss=False, filtering
|
|
312
311
|
|
313
312
|
done = False
|
314
313
|
|
314
|
+
# Create a queue for tasks
|
315
|
+
second_ocr_queue = queue.Queue()
|
316
|
+
|
317
|
+
def process_task_queue():
|
318
|
+
while True:
|
319
|
+
try:
|
320
|
+
task = second_ocr_queue.get()
|
321
|
+
if task is None: # Exit signal
|
322
|
+
break
|
323
|
+
ocr1_text, stable_time, previous_img_local, filtering = task
|
324
|
+
do_second_ocr(ocr1_text, stable_time, previous_img_local, filtering)
|
325
|
+
except Exception as e:
|
326
|
+
logger.exception(f"Error processing task: {e}")
|
327
|
+
finally:
|
328
|
+
second_ocr_queue.task_done()
|
329
|
+
|
315
330
|
|
316
331
|
def run_oneocr(ocr_config: OCRConfig, rectangles):
|
317
332
|
global done
|
@@ -420,6 +435,8 @@ if __name__ == "__main__":
|
|
420
435
|
ocr_thread = threading.Thread(target=run_oneocr, args=(ocr_config,rectangles ), daemon=True)
|
421
436
|
ocr_thread.start()
|
422
437
|
if not ssonly:
|
438
|
+
worker_thread = threading.Thread(target=process_task_queue, daemon=True)
|
439
|
+
worker_thread.start()
|
423
440
|
websocket_server_thread = WebsocketServerThread(read=True)
|
424
441
|
websocket_server_thread.start()
|
425
442
|
try:
|
@@ -55,7 +55,7 @@ except ImportError:
|
|
55
55
|
pass
|
56
56
|
from .config import Config
|
57
57
|
from .screen_coordinate_picker import get_screen_selection
|
58
|
-
from
|
58
|
+
from GameSentenceMiner.util.configuration import get_temporary_directory
|
59
59
|
|
60
60
|
config = None
|
61
61
|
|
@@ -5,7 +5,7 @@ from typing import Dict, Optional, Any
|
|
5
5
|
from dataclasses_json import dataclass_json, Undefined
|
6
6
|
from websocket import WebSocket
|
7
7
|
|
8
|
-
from GameSentenceMiner.configuration import get_app_directory
|
8
|
+
from GameSentenceMiner.util.configuration import get_app_directory
|
9
9
|
|
10
10
|
CONFIG_FILE = os.path.join(get_app_directory(), "shared_config.json")
|
11
11
|
websocket: WebSocket = None
|
@@ -7,8 +7,8 @@ from enum import Enum
|
|
7
7
|
|
8
8
|
from websocket import WebSocket
|
9
9
|
|
10
|
-
from GameSentenceMiner.communication import Message
|
11
|
-
from GameSentenceMiner.configuration import get_app_directory, logger
|
10
|
+
from GameSentenceMiner.util.communication import Message
|
11
|
+
from GameSentenceMiner.util.configuration import get_app_directory, logger
|
12
12
|
|
13
13
|
CONFIG_FILE = os.path.join(get_app_directory(), "shared_config.json")
|
14
14
|
websocket: WebSocket = None
|
@@ -6,9 +6,9 @@ import urllib.request
|
|
6
6
|
import platform
|
7
7
|
import zipfile
|
8
8
|
|
9
|
-
from GameSentenceMiner.downloader.Untitled_json import scenes
|
10
|
-
from GameSentenceMiner.configuration import get_app_directory, logger
|
11
|
-
from GameSentenceMiner.ffmpeg import get_ffmpeg_path, get_ffprobe_path
|
9
|
+
from GameSentenceMiner.util.downloader.Untitled_json import scenes
|
10
|
+
from GameSentenceMiner.util.configuration import get_app_directory, logger
|
11
|
+
from GameSentenceMiner.util.ffmpeg import get_ffmpeg_path, get_ffprobe_path
|
12
12
|
from GameSentenceMiner.obs import get_obs_path
|
13
13
|
|
14
14
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|