GameSentenceMiner 2.13.15__py3-none-any.whl → 2.14.0rc1__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 +77 -132
- GameSentenceMiner/anki.py +48 -6
- GameSentenceMiner/config_gui.py +196 -30
- GameSentenceMiner/gametext.py +8 -19
- GameSentenceMiner/gsm.py +5 -4
- GameSentenceMiner/locales/en_us.json +21 -11
- GameSentenceMiner/locales/ja_jp.json +21 -11
- GameSentenceMiner/locales/zh_cn.json +9 -11
- GameSentenceMiner/owocr/owocr/ocr.py +20 -23
- GameSentenceMiner/tools/__init__.py +0 -0
- GameSentenceMiner/util/configuration.py +241 -105
- GameSentenceMiner/util/db.py +408 -0
- GameSentenceMiner/util/ffmpeg.py +2 -10
- GameSentenceMiner/util/get_overlay_coords.py +324 -0
- GameSentenceMiner/util/model.py +8 -2
- GameSentenceMiner/util/text_log.py +1 -1
- GameSentenceMiner/web/texthooking_page.py +1 -1
- GameSentenceMiner/wip/__init___.py +0 -0
- {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/METADATA +5 -1
- {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/RECORD +27 -25
- GameSentenceMiner/util/package.py +0 -37
- GameSentenceMiner/wip/get_overlay_coords.py +0 -535
- /GameSentenceMiner/{util → tools}/audio_offset_selector.py +0 -0
- /GameSentenceMiner/{util → tools}/ss_selector.py +0 -0
- /GameSentenceMiner/{util → tools}/window_transparency.py +0 -0
- {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/top_level.txt +0 -0
GameSentenceMiner/config_gui.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import asyncio
|
2
|
+
import copy
|
2
3
|
import json
|
3
4
|
import subprocess
|
4
5
|
import time
|
@@ -12,12 +13,18 @@ from GameSentenceMiner import obs
|
|
12
13
|
from GameSentenceMiner.util import configuration
|
13
14
|
from GameSentenceMiner.util.communication.send import send_restart_signal
|
14
15
|
from GameSentenceMiner.util.configuration import *
|
16
|
+
from GameSentenceMiner.util.db import AIModelsTable
|
15
17
|
from GameSentenceMiner.util.downloader.download_tools import download_ocenaudio_if_needed
|
16
|
-
|
17
|
-
|
18
|
+
|
18
19
|
settings_saved = False
|
19
20
|
on_save = []
|
20
21
|
exit_func = None
|
22
|
+
RECOMMENDED_GROQ_MODELS = ['meta-llama/llama-4-maverick-17b-128e-instruct',
|
23
|
+
'meta-llama/llama-4-scout-17b-16e-instruct',
|
24
|
+
'llama-3.1-8b-instant',
|
25
|
+
'qwen/qwen3-32b',
|
26
|
+
'openai/gpt-oss-120b']
|
27
|
+
RECOMMENDED_GEMINI_MODELS = ["gemini-2.5-flash", "gemini-2.5-flash-lite", "gemma-3-27b-it"]
|
21
28
|
|
22
29
|
|
23
30
|
# It's assumed that a file named 'en_us.json' exists in the same directory
|
@@ -167,6 +174,7 @@ class ConfigApp:
|
|
167
174
|
self.profiles_tab = None
|
168
175
|
self.ai_tab = None
|
169
176
|
self.advanced_tab = None
|
177
|
+
self.overlay_tab = None
|
170
178
|
self.wip_tab = None
|
171
179
|
self.monitors = []
|
172
180
|
|
@@ -346,15 +354,17 @@ class ConfigApp:
|
|
346
354
|
self.groq_model_value = tk.StringVar(value=self.settings.ai.groq_model)
|
347
355
|
self.gemini_api_key_value = tk.StringVar(value=self.settings.ai.gemini_api_key)
|
348
356
|
self.groq_api_key_value = tk.StringVar(value=self.settings.ai.groq_api_key)
|
349
|
-
self.
|
357
|
+
self.open_ai_api_key_value = tk.StringVar(value=self.settings.ai.open_ai_api_key)
|
358
|
+
self.open_ai_model_value = tk.StringVar(value=self.settings.ai.open_ai_model)
|
359
|
+
self.open_ai_url_value = tk.StringVar(value=self.settings.ai.open_ai_url)
|
350
360
|
self.ai_anki_field_value = tk.StringVar(value=self.settings.ai.anki_field)
|
351
361
|
self.use_canned_translation_prompt_value = tk.BooleanVar(value=self.settings.ai.use_canned_translation_prompt)
|
352
362
|
self.use_canned_context_prompt_value = tk.BooleanVar(value=self.settings.ai.use_canned_context_prompt)
|
353
363
|
self.ai_dialogue_context_length_value = tk.StringVar(value=str(self.settings.ai.dialogue_context_length))
|
354
364
|
|
355
365
|
# WIP Settings
|
356
|
-
self.overlay_websocket_port_value = tk.StringVar(value=str(self.settings.
|
357
|
-
self.overlay_websocket_send_value = tk.BooleanVar(value=self.settings.
|
366
|
+
self.overlay_websocket_port_value = tk.StringVar(value=str(self.settings.overlay.websocket_port))
|
367
|
+
self.overlay_websocket_send_value = tk.BooleanVar(value=self.settings.overlay.monitor_to_capture)
|
358
368
|
|
359
369
|
# Master Config Settings
|
360
370
|
self.switch_to_default_if_not_found_value = tk.BooleanVar(value=self.master_config.switch_to_default_if_not_found)
|
@@ -374,7 +384,8 @@ class ConfigApp:
|
|
374
384
|
self.create_profiles_tab()
|
375
385
|
self.create_ai_tab()
|
376
386
|
self.create_advanced_tab()
|
377
|
-
self.
|
387
|
+
self.create_overlay_tab()
|
388
|
+
# self.create_wip_tab()
|
378
389
|
|
379
390
|
def add_reset_button(self, frame, category, row, column=0, recreate_tab=None):
|
380
391
|
"""
|
@@ -568,18 +579,24 @@ class ConfigApp:
|
|
568
579
|
gemini_api_key=self.gemini_api_key_value.get(),
|
569
580
|
api_key=self.gemini_api_key_value.get(),
|
570
581
|
groq_api_key=self.groq_api_key_value.get(),
|
571
|
-
local_model=self.local_ai_model_value.get(),
|
572
582
|
anki_field=self.ai_anki_field_value.get(),
|
583
|
+
open_ai_api_key=self.open_ai_api_key_value.get(),
|
584
|
+
open_ai_model=self.open_ai_model_value.get(),
|
585
|
+
open_ai_url=self.open_ai_url_value.get(),
|
573
586
|
use_canned_translation_prompt=self.use_canned_translation_prompt_value.get(),
|
574
587
|
use_canned_context_prompt=self.use_canned_context_prompt_value.get(),
|
575
588
|
custom_prompt=self.custom_prompt.get("1.0", tk.END).strip(),
|
576
589
|
dialogue_context_length=int(self.ai_dialogue_context_length_value.get()),
|
577
590
|
),
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
monitor_to_capture=self.monitor_to_capture.current() if self.monitors else 0
|
591
|
+
overlay=Overlay(
|
592
|
+
websocket_port=int(self.overlay_websocket_port_value.get()),
|
593
|
+
monitor_to_capture=self.overlay_monitor.current() if self.monitors else 0
|
582
594
|
)
|
595
|
+
# wip=WIP(
|
596
|
+
# overlay_websocket_port=int(self.overlay_websocket_port_value.get()),
|
597
|
+
# overlay_websocket_send=self.overlay_websocket_send_value.get(),
|
598
|
+
# monitor_to_capture=self.monitor_to_capture.current() if self.monitors else 0
|
599
|
+
# )
|
583
600
|
)
|
584
601
|
|
585
602
|
# Find the display name for "Custom" to check against
|
@@ -1552,14 +1569,12 @@ class ConfigApp:
|
|
1552
1569
|
def call_audio_offset_selector(self):
|
1553
1570
|
try:
|
1554
1571
|
path, beginning_offset, end_offset = gsm_state.previous_trim_args
|
1555
|
-
current_dir = os.path.dirname(os.path.abspath(__file__))
|
1556
|
-
script_path = os.path.join(current_dir, "audio_offset_selector.py")
|
1557
1572
|
|
1558
|
-
logger.info(' '.join([sys.executable, "-m", "GameSentenceMiner.
|
1573
|
+
logger.info(' '.join([sys.executable, "-m", "GameSentenceMiner.tools.audio_offset_selector",
|
1559
1574
|
"--path", path, "--beginning_offset", str(beginning_offset), "--end_offset", str(end_offset)]))
|
1560
1575
|
|
1561
1576
|
result = subprocess.run(
|
1562
|
-
[sys.executable, "-m", "GameSentenceMiner.
|
1577
|
+
[sys.executable, "-m", "GameSentenceMiner.tools.audio_offset_selector",
|
1563
1578
|
"--path", path, "--beginning_offset", str(beginning_offset), "--end_offset", str(end_offset)],
|
1564
1579
|
capture_output=True, text=True, check=False
|
1565
1580
|
)
|
@@ -1578,9 +1593,6 @@ class ConfigApp:
|
|
1578
1593
|
except subprocess.CalledProcessError as e:
|
1579
1594
|
logger.error(f"Error calling script: {e}\nStderr: {e.stderr.strip()}")
|
1580
1595
|
return None
|
1581
|
-
except FileNotFoundError:
|
1582
|
-
logger.error(f"Error: Script not found at {script_path}.")
|
1583
|
-
return None
|
1584
1596
|
except Exception as e:
|
1585
1597
|
logger.error(f"An unexpected error occurred: {e}")
|
1586
1598
|
return None
|
@@ -1867,13 +1879,15 @@ class ConfigApp:
|
|
1867
1879
|
provider_i18n = ai_i18n.get('provider', {})
|
1868
1880
|
HoverInfoLabelWidget(ai_frame, text=provider_i18n.get('label', '...'), tooltip=provider_i18n.get('tooltip', '...'), row=self.current_row,
|
1869
1881
|
column=0)
|
1870
|
-
ttk.Combobox(ai_frame, textvariable=self.ai_provider_value, values=[AI_GEMINI, AI_GROQ,
|
1882
|
+
ttk.Combobox(ai_frame, textvariable=self.ai_provider_value, values=[AI_GEMINI, AI_GROQ, AI_OPENAI], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1871
1883
|
self.current_row += 1
|
1872
1884
|
|
1873
1885
|
gemini_model_i18n = ai_i18n.get('gemini_model', {})
|
1874
1886
|
HoverInfoLabelWidget(ai_frame, text=gemini_model_i18n.get('label', '...'), tooltip=gemini_model_i18n.get('tooltip', '...'),
|
1875
1887
|
row=self.current_row, column=0)
|
1876
|
-
|
1888
|
+
|
1889
|
+
self.gemini_model_combobox = ttk.Combobox(ai_frame, textvariable=self.gemini_model_value, values=RECOMMENDED_GEMINI_MODELS, state="readonly")
|
1890
|
+
self.gemini_model_combobox.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1877
1891
|
self.current_row += 1
|
1878
1892
|
|
1879
1893
|
gemini_key_i18n = ai_i18n.get('gemini_api_key', {})
|
@@ -1886,21 +1900,42 @@ class ConfigApp:
|
|
1886
1900
|
groq_model_i18n = ai_i18n.get('groq_model', {})
|
1887
1901
|
HoverInfoLabelWidget(ai_frame, text=groq_model_i18n.get('label', '...'), tooltip=groq_model_i18n.get('tooltip', '...'),
|
1888
1902
|
row=self.current_row, column=0)
|
1889
|
-
ttk.Combobox(ai_frame, textvariable=self.groq_model_value, values=
|
1890
|
-
|
1891
|
-
'llama-3.1-8b-instant'], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1903
|
+
self.groq_models_combobox = ttk.Combobox(ai_frame, textvariable=self.groq_model_value, values=RECOMMENDED_GROQ_MODELS, state="readonly")
|
1904
|
+
self.groq_models_combobox.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1892
1905
|
self.current_row += 1
|
1893
|
-
|
1906
|
+
|
1907
|
+
self.get_online_models()
|
1908
|
+
|
1894
1909
|
groq_key_i18n = ai_i18n.get('groq_api_key', {})
|
1895
1910
|
HoverInfoLabelWidget(ai_frame, text=groq_key_i18n.get('label', '...'), tooltip=groq_key_i18n.get('tooltip', '...'),
|
1896
1911
|
row=self.current_row, column=0)
|
1897
|
-
ttk.Entry(ai_frame, show="*", textvariable=self.groq_api_key_value)
|
1912
|
+
groq_apikey_entry = ttk.Entry(ai_frame, show="*", textvariable=self.groq_api_key_value)
|
1913
|
+
groq_apikey_entry.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1914
|
+
groq_apikey_entry.bind("<FocusOut>", lambda e, row=self.current_row: self.get_online_models())
|
1915
|
+
groq_apikey_entry.bind("<Return>", lambda e, row=self.current_row: self.get_online_models())
|
1916
|
+
self.current_row += 1
|
1917
|
+
|
1918
|
+
|
1919
|
+
|
1920
|
+
openai_url_i18n = ai_i18n.get('openai_url', {})
|
1921
|
+
HoverInfoLabelWidget(ai_frame, text=openai_url_i18n.get('label', '...'), tooltip=openai_url_i18n.get('tooltip', '...'),
|
1922
|
+
row=self.current_row, column=0)
|
1923
|
+
entry = ttk.Entry(ai_frame, textvariable=self.open_ai_url_value)
|
1924
|
+
entry.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1898
1925
|
self.current_row += 1
|
1926
|
+
|
1927
|
+
entry.bind("<FocusOut>", lambda e, row=self.current_row: self.update_models_element(ai_frame, row))
|
1928
|
+
entry.bind("<Return>", lambda e, row=self.current_row: self.update_models_element(ai_frame, row))
|
1899
1929
|
|
1900
|
-
|
1901
|
-
|
1902
|
-
|
1903
|
-
|
1930
|
+
self.openai_model_options = []
|
1931
|
+
self.update_models_element(ai_frame, self.current_row)
|
1932
|
+
self.current_row += 1
|
1933
|
+
|
1934
|
+
|
1935
|
+
openai_key_i18n = ai_i18n.get('openai_apikey', {})
|
1936
|
+
HoverInfoLabelWidget(ai_frame, text=openai_key_i18n.get('label', '...'), tooltip=openai_key_i18n.get('tooltip', '...'),
|
1937
|
+
row=self.current_row, column=0)
|
1938
|
+
ttk.Entry(ai_frame, show="*", textvariable=self.open_ai_api_key_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1904
1939
|
self.current_row += 1
|
1905
1940
|
|
1906
1941
|
anki_field_i18n = ai_i18n.get('anki_field', {})
|
@@ -1946,6 +1981,137 @@ class ConfigApp:
|
|
1946
1981
|
|
1947
1982
|
return ai_frame
|
1948
1983
|
|
1984
|
+
|
1985
|
+
def get_online_models(self):
|
1986
|
+
ai_models = AIModelsTable.one()
|
1987
|
+
|
1988
|
+
def get_models_thread():
|
1989
|
+
groq_models = get_groq_models()
|
1990
|
+
gemini_models = get_gemini_models()
|
1991
|
+
AIModelsTable.update_models(gemini_models, groq_models)
|
1992
|
+
|
1993
|
+
def get_groq_models():
|
1994
|
+
list_of_groq_models = ["RECOMMENDED"] + RECOMMENDED_GROQ_MODELS + ['OTHER']
|
1995
|
+
try:
|
1996
|
+
from groq import Groq
|
1997
|
+
client = Groq(api_key=self.settings.ai.groq_api_key)
|
1998
|
+
models = client.models.list()
|
1999
|
+
for m in models.data:
|
2000
|
+
if not m.active:
|
2001
|
+
continue
|
2002
|
+
name = m.id
|
2003
|
+
if name not in list_of_groq_models and not any(x in name for x in ["guard", "tts", "whisper"]):
|
2004
|
+
list_of_groq_models.append(name)
|
2005
|
+
except Exception as e:
|
2006
|
+
print(f"Error occurred while fetching Groq models: {e}")
|
2007
|
+
list_of_groq_models = RECOMMENDED_GROQ_MODELS
|
2008
|
+
with open(os.path.join(get_app_directory(), "ai_last_groq_models"), "w") as f:
|
2009
|
+
f.write("\n".join(list_of_groq_models))
|
2010
|
+
self.groq_models_combobox['values'] = list_of_groq_models
|
2011
|
+
return list_of_groq_models
|
2012
|
+
|
2013
|
+
def get_gemini_models():
|
2014
|
+
full_list_of_models = ["RECOMMENDED"] + RECOMMENDED_GEMINI_MODELS + ["OTHER"]
|
2015
|
+
try:
|
2016
|
+
from google import genai
|
2017
|
+
|
2018
|
+
client = genai.Client()
|
2019
|
+
for m in client.models.list():
|
2020
|
+
name = m.name.replace("models/", "")
|
2021
|
+
for action in m.supported_actions:
|
2022
|
+
if action == "generateContent":
|
2023
|
+
if "1.5" not in name:
|
2024
|
+
if "2.0" in name and any(x in name for x in ["exp", "preview", "001"]):
|
2025
|
+
continue
|
2026
|
+
if name not in full_list_of_models:
|
2027
|
+
full_list_of_models.append(name)
|
2028
|
+
except Exception as e:
|
2029
|
+
print(f"Error occurred while fetching models: {e}")
|
2030
|
+
full_list_of_models = RECOMMENDED_GEMINI_MODELS
|
2031
|
+
self.gemini_model_combobox['values'] = full_list_of_models
|
2032
|
+
return full_list_of_models
|
2033
|
+
|
2034
|
+
if ai_models and ai_models.gemini_models and ai_models.groq_models:
|
2035
|
+
if time.time() - ai_models.last_updated > 3600 * 6:
|
2036
|
+
print("AI models are outdated, fetching new ones.")
|
2037
|
+
threading.Thread(target=get_models_thread, daemon=True).start()
|
2038
|
+
self.gemini_model_combobox['values'] = ai_models.gemini_models
|
2039
|
+
self.groq_models_combobox['values'] = ai_models.groq_models
|
2040
|
+
else:
|
2041
|
+
print("No AI models found, fetching new ones.")
|
2042
|
+
threading.Thread(target=get_models_thread, daemon=True).start()
|
2043
|
+
|
2044
|
+
def update_models_element(self, frame, row):
|
2045
|
+
if hasattr(self, 'last_url') and self.last_url == self.open_ai_url_value.get().strip():
|
2046
|
+
print("OpenAI URL unchanged, skipping model update.")
|
2047
|
+
return
|
2048
|
+
self.last_url = self.open_ai_url_value.get().strip()
|
2049
|
+
if self.open_ai_url_value.get().strip() != "" and any(c in self.open_ai_url_value.get() for c in ["localhost", "127.0.0.1"]):
|
2050
|
+
import openai
|
2051
|
+
# get models from openai compatible url
|
2052
|
+
client = openai.Client(api_key=self.settings.ai.open_ai_api_key, base_url=self.open_ai_url_value.get().strip())
|
2053
|
+
try:
|
2054
|
+
models = client.models.list()
|
2055
|
+
if models:
|
2056
|
+
self.openai_model_options = [model.id for model in models.data]
|
2057
|
+
else:
|
2058
|
+
self.openai_model_options = []
|
2059
|
+
except Exception as e:
|
2060
|
+
self.openai_model_options = []
|
2061
|
+
for widget in frame.grid_slaves(row=row, column=0):
|
2062
|
+
widget.destroy()
|
2063
|
+
|
2064
|
+
ai_i18n = self.i18n.get('tabs', {}).get('ai', {})
|
2065
|
+
openai_model_i18n = ai_i18n.get('openai_model', {})
|
2066
|
+
HoverInfoLabelWidget(frame, text=openai_model_i18n.get('label', '...'), tooltip=openai_model_i18n.get('tooltip', '...'),
|
2067
|
+
row=row, column=0)
|
2068
|
+
if not self.openai_model_options:
|
2069
|
+
self.openai_model_combobox = ttk.Entry(frame, textvariable=self.open_ai_model_value)
|
2070
|
+
self.openai_model_combobox.grid(row=row, column=1, sticky='EW', pady=2)
|
2071
|
+
else:
|
2072
|
+
self.openai_model_combobox = ttk.Combobox(frame, textvariable=self.open_ai_model_value,
|
2073
|
+
values=self.openai_model_options, state="readonly")
|
2074
|
+
self.openai_model_combobox.grid(row=row, column=1, sticky='EW', pady=2)
|
2075
|
+
|
2076
|
+
|
2077
|
+
# Settings for Official Overlay
|
2078
|
+
@new_tab
|
2079
|
+
def create_overlay_tab(self):
|
2080
|
+
if self.overlay_tab is None:
|
2081
|
+
overlay_i18n = self.i18n.get('tabs', {}).get('overlay', {})
|
2082
|
+
self.overlay_tab = ttk.Frame(self.notebook, padding=15)
|
2083
|
+
self.notebook.add(self.overlay_tab, text=overlay_i18n.get('title', 'Overlay'))
|
2084
|
+
else:
|
2085
|
+
for widget in self.overlay_tab.winfo_children():
|
2086
|
+
widget.destroy()
|
2087
|
+
|
2088
|
+
overlay_frame = self.overlay_tab
|
2089
|
+
overlay_i18n = self.i18n.get('tabs', {}).get('overlay', {})
|
2090
|
+
websocket_port_i18n = overlay_i18n.get('websocket_port', {})
|
2091
|
+
HoverInfoLabelWidget(overlay_frame, text=websocket_port_i18n.get('label', '...'),
|
2092
|
+
tooltip=websocket_port_i18n.get('tooltip', '...'),
|
2093
|
+
row=self.current_row, column=0)
|
2094
|
+
ttk.Entry(overlay_frame, textvariable=self.overlay_websocket_port_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
2095
|
+
self.current_row += 1
|
2096
|
+
|
2097
|
+
overlay_monitor_i18n = overlay_i18n.get('overlay_monitor', {})
|
2098
|
+
HoverInfoLabelWidget(overlay_frame, text=overlay_monitor_i18n.get('label', '...'),
|
2099
|
+
tooltip=overlay_monitor_i18n.get('tooltip', '...'),
|
2100
|
+
row=self.current_row, column=0)
|
2101
|
+
self.overlay_monitor = ttk.Combobox(overlay_frame, values=self.monitors, state="readonly")
|
2102
|
+
self.overlay_monitor.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
2103
|
+
self.current_row += 1
|
2104
|
+
|
2105
|
+
if self.monitors:
|
2106
|
+
# Ensure the index is valid
|
2107
|
+
monitor_index = self.settings.overlay.monitor_to_capture
|
2108
|
+
if 0 <= monitor_index < len(self.monitors):
|
2109
|
+
self.overlay_monitor.current(monitor_index)
|
2110
|
+
else:
|
2111
|
+
self.overlay_monitor.current(0)
|
2112
|
+
|
2113
|
+
self.add_reset_button(overlay_frame, "overlay", self.current_row, 0, self.create_overlay_tab)
|
2114
|
+
|
1949
2115
|
@new_tab
|
1950
2116
|
def create_wip_tab(self):
|
1951
2117
|
if self.wip_tab is None:
|
@@ -1992,7 +2158,7 @@ class ConfigApp:
|
|
1992
2158
|
|
1993
2159
|
if self.monitors:
|
1994
2160
|
# Ensure the index is valid
|
1995
|
-
monitor_index = self.settings.
|
2161
|
+
monitor_index = self.settings.overlay.monitor_to_capture
|
1996
2162
|
if 0 <= monitor_index < len(self.monitors):
|
1997
2163
|
self.monitor_to_capture.current(monitor_index)
|
1998
2164
|
else:
|
GameSentenceMiner/gametext.py
CHANGED
@@ -7,14 +7,13 @@ import websockets
|
|
7
7
|
from websockets import InvalidStatus
|
8
8
|
from rapidfuzz import fuzz
|
9
9
|
|
10
|
+
from GameSentenceMiner.util.db import GameLinesTable
|
10
11
|
from GameSentenceMiner.util.gsm_utils import do_text_replacements, TEXT_REPLACEMENTS_FILE, run_new_thread
|
11
12
|
from GameSentenceMiner.util.configuration import *
|
12
13
|
from GameSentenceMiner.util.text_log import *
|
13
14
|
from GameSentenceMiner import obs
|
14
15
|
from GameSentenceMiner.web.texthooking_page import add_event_to_texthooker, send_word_coordinates_to_overlay, overlay_server_thread
|
15
|
-
|
16
|
-
if get_config().wip.overlay_websocket_send:
|
17
|
-
import GameSentenceMiner.wip.get_overlay_coords as get_overlay_coords
|
16
|
+
from GameSentenceMiner.util.get_overlay_coords import OverlayProcessor
|
18
17
|
|
19
18
|
|
20
19
|
current_line = ''
|
@@ -31,6 +30,8 @@ last_clipboard = ''
|
|
31
30
|
reconnecting = False
|
32
31
|
websocket_connected = {}
|
33
32
|
|
33
|
+
overlay_processor = OverlayProcessor()
|
34
|
+
|
34
35
|
async def monitor_clipboard():
|
35
36
|
global current_line, last_clipboard
|
36
37
|
current_line = pyperclip.paste()
|
@@ -189,22 +190,10 @@ async def add_line_to_text_log(line, line_time=None):
|
|
189
190
|
add_line(current_line_after_regex, line_time if line_time else datetime.now())
|
190
191
|
if len(get_text_log().values) > 0:
|
191
192
|
await add_event_to_texthooker(get_text_log()[-1])
|
192
|
-
if get_config().
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
async def find_box_for_sentence(sentence):
|
198
|
-
boxes = []
|
199
|
-
logger.info(f"Finding Box for Sentence: {sentence}")
|
200
|
-
boxes, font_size = await get_overlay_coords.find_box_for_sentence(sentence)
|
201
|
-
# logger.info(f"Found Boxes: {boxes}, Font Size: {font_size}")
|
202
|
-
# if boxes:
|
203
|
-
# x1, y1, x2, y2 = box
|
204
|
-
# boxes.append({'sentence': sentence, 'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2, 'fontSize': font_size})
|
205
|
-
# x1, y1, x2, y2 = box
|
206
|
-
# requests.post("http://localhost:3000/open-overlay", json={"sentence": sentence, "x1": x1, "y1": y1, "x2": x2, "y2": y2, "fontSize": font_size})
|
207
|
-
return boxes
|
193
|
+
if get_config().overlay.websocket_port and overlay_server_thread.has_clients():
|
194
|
+
await overlay_processor.find_box_and_send_to_overlay(current_line_after_regex)
|
195
|
+
GameLinesTable.add_line(get_text_log()[-1])
|
196
|
+
|
208
197
|
|
209
198
|
def reset_line_hotkey_pressed():
|
210
199
|
global current_line_time
|
GameSentenceMiner/gsm.py
CHANGED
@@ -116,6 +116,10 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
116
116
|
if get_config().features.backfill_audio:
|
117
117
|
last_note = anki.get_cards_by_sentence(
|
118
118
|
gametext.current_line_after_regex)
|
119
|
+
|
120
|
+
note, last_note = anki.get_initial_card_info(last_note, selected_lines)
|
121
|
+
tango = last_note.get_field(
|
122
|
+
get_config().anki.word_field) if last_note else ''
|
119
123
|
|
120
124
|
# Get Info of line mined
|
121
125
|
line_cutoff = None
|
@@ -145,9 +149,6 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
145
149
|
|
146
150
|
if last_note:
|
147
151
|
logger.debug(last_note.to_json())
|
148
|
-
note = anki.get_initial_card_info(last_note, selected_lines)
|
149
|
-
tango = last_note.get_field(
|
150
|
-
get_config().anki.word_field) if last_note else ''
|
151
152
|
|
152
153
|
if get_config().anki.sentence_audio_field and get_config().audio.enabled:
|
153
154
|
logger.debug("Attempting to get audio from video")
|
@@ -633,7 +634,7 @@ async def register_scene_switcher_callback():
|
|
633
634
|
|
634
635
|
|
635
636
|
async def run_test_code():
|
636
|
-
if get_config().
|
637
|
+
if get_config().overlay.websocket_port:
|
637
638
|
boxes = await gametext.find_box_for_sentence("ちぇっ少しなの?")
|
638
639
|
if boxes:
|
639
640
|
await texthooking_page.send_word_coordinates_to_overlay(boxes)
|
@@ -510,6 +510,18 @@
|
|
510
510
|
"label": "Local AI Model:",
|
511
511
|
"tooltip": "Local AI Model to Use, Only very basic Translation is supported atm. May require some other setup, but idk."
|
512
512
|
},
|
513
|
+
"openai_model": {
|
514
|
+
"label": "OpenAI Model:",
|
515
|
+
"tooltip": "Select the OpenAI model to use."
|
516
|
+
},
|
517
|
+
"openai_apikey": {
|
518
|
+
"label": "OpenAI API Key:",
|
519
|
+
"tooltip": "API key for OpenAI provider."
|
520
|
+
},
|
521
|
+
"openai_url": {
|
522
|
+
"label": "OpenAI API URL:",
|
523
|
+
"tooltip": "Custom URL for OpenAI API. Leave blank to use the default."
|
524
|
+
},
|
513
525
|
"anki_field": {
|
514
526
|
"label": "Anki Field:",
|
515
527
|
"tooltip": "Field in Anki for AI-generated content."
|
@@ -531,23 +543,21 @@
|
|
531
543
|
"tooltip": "Custom prompt for AI processing."
|
532
544
|
}
|
533
545
|
},
|
534
|
-
"
|
535
|
-
"
|
536
|
-
"warning_experimental": "Warning: These features are experimental and may not work as expected.",
|
537
|
-
"warning_overlay_deps": "Overlay requires OwOCR dependencies to be installed, and requires an external app to be running.",
|
538
|
-
"overlay_port": {
|
546
|
+
"overlay": {
|
547
|
+
"websocket_port": {
|
539
548
|
"label": "Overlay WebSocket Port:",
|
540
549
|
"tooltip": "Port for the overlay WebSocket communication. Used for experimental overlay features."
|
541
550
|
},
|
542
|
-
"
|
543
|
-
"label": "Overlay WebSocket Send:",
|
544
|
-
"tooltip": "Enable to send overlay data via WebSocket. Experimental feature."
|
545
|
-
},
|
546
|
-
"monitor_capture": {
|
551
|
+
"overlay_monitor": {
|
547
552
|
"label": "Monitor to Capture:",
|
548
553
|
"tooltip": "Select the monitor to capture (1-based index).",
|
549
554
|
"not_detected": "OwOCR Not Detected"
|
550
|
-
}
|
555
|
+
}
|
556
|
+
},
|
557
|
+
"wip": {
|
558
|
+
"title": "WIP",
|
559
|
+
"warning_experimental": "Warning: These features are experimental and may not work as expected.",
|
560
|
+
"warning_overlay_deps": "Overlay requires OwOCR dependencies to be installed, and requires an external app to be running.",
|
551
561
|
"error_setup": "Error setting up WIP tab"
|
552
562
|
}
|
553
563
|
},
|
@@ -505,6 +505,18 @@
|
|
505
505
|
"label": "Groq APIキー:",
|
506
506
|
"tooltip": "Groq AIプロバイダーのAPIキー。"
|
507
507
|
},
|
508
|
+
"openai_model": {
|
509
|
+
"label": "OpenAIモデル:",
|
510
|
+
"tooltip": "使用するOpenAIモデルを選択。"
|
511
|
+
},
|
512
|
+
"openai_apikey": {
|
513
|
+
"label": "OpenAI APIキー:",
|
514
|
+
"tooltip": "OpenAIプロバイダーのAPIキー。"
|
515
|
+
},
|
516
|
+
"openai_url": {
|
517
|
+
"label": "OpenAI API URL:",
|
518
|
+
"tooltip": "OpenAI APIのカスタムURL。デフォルトを使用するには空白のままにします。"
|
519
|
+
},
|
508
520
|
"local_model": {
|
509
521
|
"label": "ローカルAIモデル:",
|
510
522
|
"tooltip": "使用するローカルAIモデル。基本的な翻訳のみ対応。"
|
@@ -530,23 +542,21 @@
|
|
530
542
|
"tooltip": "AI処理用のカスタムプロンプト。"
|
531
543
|
}
|
532
544
|
},
|
533
|
-
"
|
534
|
-
"
|
535
|
-
"warning_experimental": "警告:これらの機能は実験的なものであり、期待通りに動作しない可能性があります。",
|
536
|
-
"warning_overlay_deps": "オーバーレイ機能にはOwOCRの依存関係と外部アプリの実行が必要です。",
|
537
|
-
"overlay_port": {
|
545
|
+
"overlay": {
|
546
|
+
"websocket_port": {
|
538
547
|
"label": "オーバーレイWebSocketポート:",
|
539
548
|
"tooltip": "実験的なオーバーレイ機能用のWebSocketポート。"
|
540
549
|
},
|
541
|
-
"
|
542
|
-
"label": "オーバーレイWebSocket送信:",
|
543
|
-
"tooltip": "WebSocketでオーバーレイデータを送信します(実験的)。"
|
544
|
-
},
|
545
|
-
"monitor_capture": {
|
550
|
+
"overlay_monitor": {
|
546
551
|
"label": "キャプチャ対象モニター:",
|
547
552
|
"tooltip": "キャプチャするモニターを選択(1から始まるインデックス)。",
|
548
553
|
"not_detected": "OwOCRが検出されません"
|
549
|
-
}
|
554
|
+
}
|
555
|
+
},
|
556
|
+
"wip": {
|
557
|
+
"title": "WIP",
|
558
|
+
"warning_experimental": "警告:これらの機能は実験的なものであり、期待通りに動作しない可能性があります。",
|
559
|
+
"warning_overlay_deps": "オーバーレイ機能にはOwOCRの依存関係と外部アプリの実行が必要です。",
|
550
560
|
"error_setup": "WIPタブの設定中にエラーが発生しました"
|
551
561
|
}
|
552
562
|
},
|
@@ -531,23 +531,21 @@
|
|
531
531
|
"tooltip": "用于 AI 处理的自定义提示。"
|
532
532
|
}
|
533
533
|
},
|
534
|
-
"
|
535
|
-
"
|
536
|
-
"warning_experimental": "警告:这些功能是实验性的,可能无法按预期工作。",
|
537
|
-
"warning_overlay_deps": "覆盖层需要安装 OwOCR 依赖项,并需要运行一个外部应用程序。",
|
538
|
-
"overlay_port": {
|
534
|
+
"overlay": {
|
535
|
+
"websocket_port": {
|
539
536
|
"label": "覆盖层 WebSocket 端口:",
|
540
537
|
"tooltip": "用于实验性覆盖层功能的覆盖层 WebSocket 通信端口。"
|
541
538
|
},
|
542
|
-
"
|
543
|
-
"label": "覆盖层 WebSocket 发送:",
|
544
|
-
"tooltip": "启用通过 WebSocket 发送覆盖层数据。实验性功能。"
|
545
|
-
},
|
546
|
-
"monitor_capture": {
|
539
|
+
"overlay_monitor": {
|
547
540
|
"label": "捕获的显示器:",
|
548
541
|
"tooltip": "选择要捕获的显示器(从1开始的索引)。",
|
549
542
|
"not_detected": "未检测到 OwOCR"
|
550
|
-
}
|
543
|
+
}
|
544
|
+
},
|
545
|
+
"wip": {
|
546
|
+
"title": "WIP",
|
547
|
+
"warning_experimental": "警告:这些功能是实验性的,可能无法按预期工作。",
|
548
|
+
"warning_overlay_deps": "覆盖层需要安装 OwOCR 依赖项,并需要运行一个外部应用程序。",
|
551
549
|
"error_setup": "设置 WIP 选项卡时出错"
|
552
550
|
}
|
553
551
|
},
|
@@ -900,7 +900,7 @@ class OneOCR:
|
|
900
900
|
self.regex = re.compile(
|
901
901
|
r'[a-zA-Z\u00C0-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u1D00-\u1D7F\u1D80-\u1DBF\u1E00-\u1EFF\u2C60-\u2C7F\uA720-\uA7FF\uAB30-\uAB6F]')
|
902
902
|
|
903
|
-
def __call__(self, img, furigana_filter_sensitivity=0, return_coords=False, multiple_crop_coords=False):
|
903
|
+
def __call__(self, img, furigana_filter_sensitivity=0, return_coords=False, multiple_crop_coords=False, return_one_box=True):
|
904
904
|
lang = get_ocr_language()
|
905
905
|
furigana_filter_sensitivity = get_furigana_filter_sensitivity()
|
906
906
|
if lang != self.initial_lang:
|
@@ -983,23 +983,15 @@ class OneOCR:
|
|
983
983
|
# else:
|
984
984
|
# continue
|
985
985
|
# res += '\n'
|
986
|
-
|
987
|
-
for line in filtered_lines:
|
988
|
-
for word in line['words']:
|
989
|
-
box = {
|
990
|
-
"text": word['text'],
|
991
|
-
"bounding_rect": word['bounding_rect']
|
992
|
-
}
|
993
|
-
boxes.append(box)
|
986
|
+
else:
|
994
987
|
res = ocr_resp['text']
|
995
|
-
|
988
|
+
|
989
|
+
if multiple_crop_coords:
|
990
|
+
logger.info(f"Getting multiple crop coords for {len(filtered_lines)} lines")
|
996
991
|
for line in filtered_lines:
|
997
992
|
crop_coords_list.append(
|
998
993
|
(line['bounding_rect']['x1'] - 5, line['bounding_rect']['y1'] - 5,
|
999
994
|
line['bounding_rect']['x3'] + 5, line['bounding_rect']['y3'] + 5))
|
1000
|
-
res = ocr_resp['text']
|
1001
|
-
else:
|
1002
|
-
res = ocr_resp['text']
|
1003
995
|
|
1004
996
|
except RuntimeError as e:
|
1005
997
|
return (False, e)
|
@@ -1015,12 +1007,14 @@ class OneOCR:
|
|
1015
1007
|
return (False, 'Unknown error!')
|
1016
1008
|
|
1017
1009
|
res = res.json()['text']
|
1010
|
+
|
1011
|
+
x = [True, res]
|
1018
1012
|
if return_coords:
|
1019
|
-
x
|
1020
|
-
|
1021
|
-
x
|
1022
|
-
|
1023
|
-
x
|
1013
|
+
x.append(filtered_lines)
|
1014
|
+
if multiple_crop_coords:
|
1015
|
+
x.append(crop_coords_list)
|
1016
|
+
if return_one_box:
|
1017
|
+
x.append(crop_coords)
|
1024
1018
|
if is_path:
|
1025
1019
|
img.close()
|
1026
1020
|
return x
|
@@ -1410,9 +1404,12 @@ class localLLMOCR:
|
|
1410
1404
|
base_url=self.api_url.replace('/v1/chat/completions', '/v1'),
|
1411
1405
|
api_key=self.api_key
|
1412
1406
|
)
|
1413
|
-
|
1414
|
-
|
1415
|
-
|
1407
|
+
if self.client.models.retrieve(self.model):
|
1408
|
+
self.model = self.model
|
1409
|
+
logger.info(f'Local LLM OCR (OpenAI-compatible) ready with model {self.model}')
|
1410
|
+
if self.keep_warm:
|
1411
|
+
self.keep_llm_hot_thread = threading.Thread(target=self.keep_llm_warm, daemon=True)
|
1412
|
+
self.keep_llm_hot_thread.start()
|
1416
1413
|
except Exception as e:
|
1417
1414
|
logger.warning(f'Error initializing Local LLM OCR, Local LLM OCR will not work!')
|
1418
1415
|
|
@@ -1441,7 +1438,7 @@ class localLLMOCR:
|
|
1441
1438
|
prompt = self.custom_prompt.strip()
|
1442
1439
|
else:
|
1443
1440
|
prompt = f"""
|
1444
|
-
Extract all {CommonLanguages.from_code(get_ocr_language())} Text from Image. Ignore all Furigana. Do not return any commentary, just the text in the image. If there is no text in the image, return "" (Empty String).
|
1441
|
+
Extract all {CommonLanguages.from_code(get_ocr_language()).name} Text from Image. Ignore all Furigana. Do not return any commentary, just the text in the image. If there is no text in the image, return "" (Empty String).
|
1445
1442
|
"""
|
1446
1443
|
|
1447
1444
|
response = self.client.chat.completions.create(
|
@@ -1455,7 +1452,7 @@ class localLLMOCR:
|
|
1455
1452
|
],
|
1456
1453
|
}
|
1457
1454
|
],
|
1458
|
-
max_tokens=
|
1455
|
+
max_tokens=4096,
|
1459
1456
|
temperature=0.1
|
1460
1457
|
)
|
1461
1458
|
self.last_ocr_time = time.time()
|
File without changes
|