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.
Files changed (29) hide show
  1. GameSentenceMiner/ai/ai_prompting.py +77 -132
  2. GameSentenceMiner/anki.py +48 -6
  3. GameSentenceMiner/config_gui.py +196 -30
  4. GameSentenceMiner/gametext.py +8 -19
  5. GameSentenceMiner/gsm.py +5 -4
  6. GameSentenceMiner/locales/en_us.json +21 -11
  7. GameSentenceMiner/locales/ja_jp.json +21 -11
  8. GameSentenceMiner/locales/zh_cn.json +9 -11
  9. GameSentenceMiner/owocr/owocr/ocr.py +20 -23
  10. GameSentenceMiner/tools/__init__.py +0 -0
  11. GameSentenceMiner/util/configuration.py +241 -105
  12. GameSentenceMiner/util/db.py +408 -0
  13. GameSentenceMiner/util/ffmpeg.py +2 -10
  14. GameSentenceMiner/util/get_overlay_coords.py +324 -0
  15. GameSentenceMiner/util/model.py +8 -2
  16. GameSentenceMiner/util/text_log.py +1 -1
  17. GameSentenceMiner/web/texthooking_page.py +1 -1
  18. GameSentenceMiner/wip/__init___.py +0 -0
  19. {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/METADATA +5 -1
  20. {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/RECORD +27 -25
  21. GameSentenceMiner/util/package.py +0 -37
  22. GameSentenceMiner/wip/get_overlay_coords.py +0 -535
  23. /GameSentenceMiner/{util → tools}/audio_offset_selector.py +0 -0
  24. /GameSentenceMiner/{util → tools}/ss_selector.py +0 -0
  25. /GameSentenceMiner/{util → tools}/window_transparency.py +0 -0
  26. {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/WHEEL +0 -0
  27. {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/entry_points.txt +0 -0
  28. {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/licenses/LICENSE +0 -0
  29. {gamesentenceminer-2.13.15.dist-info → gamesentenceminer-2.14.0rc1.dist-info}/top_level.txt +0 -0
@@ -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
- from GameSentenceMiner.util.package import get_current_version, get_latest_version
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.local_ai_model_value = tk.StringVar(value=self.settings.ai.local_model)
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.wip.overlay_websocket_port))
357
- self.overlay_websocket_send_value = tk.BooleanVar(value=self.settings.wip.overlay_websocket_send)
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.create_wip_tab()
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
- wip=WIP(
579
- overlay_websocket_port=int(self.overlay_websocket_port_value.get()),
580
- overlay_websocket_send=self.overlay_websocket_send_value.get(),
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.util.audio_offset_selector",
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.util.audio_offset_selector",
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, AI_LOCAL], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
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
- ttk.Combobox(ai_frame, textvariable=self.gemini_model_value, values=['gemini-2.5-flash-lite', 'gemini-2.5-flash', 'gemma-3-27b-it', 'gemini-2.0-flash', 'gemini-2.0-flash-lite'], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
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=['meta-llama/llama-4-maverick-17b-128e-instruct',
1890
- 'meta-llama/llama-4-scout-17b-16e-instruct',
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).grid(row=self.current_row, column=1, sticky='EW', pady=2)
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
- local_model_i18n = ai_i18n.get('local_model', {})
1901
- HoverInfoLabelWidget(ai_frame, text=local_model_i18n.get('label', '...'), tooltip=local_model_i18n.get('tooltip', '...'),
1902
- foreground="red", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1903
- ttk.Combobox(ai_frame, textvariable=self.local_ai_model_value, values=[OFF, 'facebook/nllb-200-distilled-600M', 'facebook/nllb-200-1.3B', 'facebook/nllb-200-3.3B']).grid(row=self.current_row, column=1, sticky='EW', pady=2)
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.wip.monitor_to_capture
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:
@@ -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().wip.overlay_websocket_port and get_config().wip.overlay_websocket_send and overlay_server_thread.has_clients():
193
- boxes = await find_box_for_sentence(current_line_after_regex)
194
- if boxes:
195
- await send_word_coordinates_to_overlay(boxes)
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().wip.overlay_websocket_port and get_config().wip.overlay_websocket_send:
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
- "wip": {
535
- "title": "WIP",
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
- "overlay_send": {
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
- "wip": {
534
- "title": "WIP",
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
- "overlay_send": {
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
- "wip": {
535
- "title": "WIP",
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
- "overlay_send": {
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
- elif return_coords:
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
- elif multiple_crop_coords:
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 = (True, res, filtered_lines)
1020
- elif multiple_crop_coords:
1021
- x = (True, res, crop_coords_list)
1022
- else:
1023
- x = (True, res, crop_coords)
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
- logger.info('Local LLM OCR (OpenAI-compatible) ready')
1414
- self.keep_llm_hot_thread = threading.Thread(target=self.keep_llm_warm, daemon=True)
1415
- self.keep_llm_hot_thread.start()
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=512,
1455
+ max_tokens=4096,
1459
1456
  temperature=0.1
1460
1457
  )
1461
1458
  self.last_ocr_time = time.time()
File without changes