GameSentenceMiner 2.8.17__tar.gz → 2.8.19__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/config_gui.py +18 -15
  2. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/configuration.py +1 -0
  3. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/gsm.py +2 -2
  4. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/owocr/owocr/__main__.py +1 -1
  5. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/owocr/owocr/ocr.py +76 -0
  6. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/owocr/owocr/run.py +2 -1
  7. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner.egg-info/PKG-INFO +1 -1
  8. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/PKG-INFO +1 -1
  9. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/pyproject.toml +1 -1
  10. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/__init__.py +0 -0
  11. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/ai/__init__.py +0 -0
  12. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/ai/ai_prompting.py +0 -0
  13. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/anki.py +0 -0
  14. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/communication/__init__.py +0 -0
  15. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/communication/send.py +0 -0
  16. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/communication/websocket.py +0 -0
  17. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/downloader/Untitled_json.py +0 -0
  18. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/downloader/__init__.py +0 -0
  19. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/downloader/download_tools.py +0 -0
  20. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/downloader/oneocr_dl.py +0 -0
  21. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/electron_config.py +0 -0
  22. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/ffmpeg.py +0 -0
  23. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/gametext.py +0 -0
  24. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/model.py +0 -0
  25. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/notification.py +0 -0
  26. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/obs.py +0 -0
  27. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/ocr/__init__.py +0 -0
  28. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/ocr/gsm_ocr_config.py +0 -0
  29. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/ocr/ocrconfig.py +0 -0
  30. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/ocr/owocr_area_selector.py +0 -0
  31. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/ocr/owocr_helper.py +0 -0
  32. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/owocr/owocr/__init__.py +0 -0
  33. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/owocr/owocr/config.py +0 -0
  34. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -0
  35. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -0
  36. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/package.py +0 -0
  37. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/text_log.py +0 -0
  38. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/util.py +0 -0
  39. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/vad/__init__.py +0 -0
  40. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/vad/silero_trim.py +0 -0
  41. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/vad/vosk_helper.py +0 -0
  42. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/vad/whisper_helper.py +0 -0
  43. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/web/__init__.py +0 -0
  44. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/web/static/__init__.py +0 -0
  45. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
  46. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/web/static/favicon-96x96.png +0 -0
  47. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/web/static/favicon.ico +0 -0
  48. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/web/static/favicon.svg +0 -0
  49. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/web/static/site.webmanifest +0 -0
  50. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/web/static/style.css +0 -0
  51. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
  52. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
  53. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/web/templates/__init__.py +0 -0
  54. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/web/templates/text_replacements.html +0 -0
  55. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/web/templates/utility.html +0 -0
  56. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner/web/texthooking_page.py +0 -0
  57. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner.egg-info/SOURCES.txt +0 -0
  58. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner.egg-info/dependency_links.txt +0 -0
  59. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner.egg-info/entry_points.txt +0 -0
  60. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner.egg-info/requires.txt +0 -0
  61. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/GameSentenceMiner.egg-info/top_level.txt +0 -0
  62. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/LICENSE +0 -0
  63. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/README.md +0 -0
  64. {gamesentenceminer-2.8.17 → gamesentenceminer-2.8.19}/setup.cfg +0 -0
@@ -166,7 +166,7 @@ class ConfigApp:
166
166
  extension=self.audio_extension.get(),
167
167
  beginning_offset=float(self.beginning_offset.get()),
168
168
  end_offset=float(self.end_offset.get()),
169
- ffmpeg_reencode_options=self.ffmpeg_reencode_options.get(),
169
+ ffmpeg_reencode_options=self.audio_ffmpeg_reencode_options.get(),
170
170
  external_tool = self.external_tool.get(),
171
171
  anki_media_collection=self.anki_media_collection.get(),
172
172
  external_tool_enabled=self.external_tool_enabled.get(),
@@ -221,6 +221,9 @@ class ConfigApp:
221
221
  )
222
222
  )
223
223
 
224
+ if self.ffmpeg_audio_preset_options.get() == "Custom":
225
+ config.audio.custom_encode_settings = self.audio_ffmpeg_reencode_options.get()
226
+
224
227
  if config.features.backfill_audio and config.features.full_auto:
225
228
  messagebox.showerror("Configuration Error", "Cannot have Full Auto and Backfill mode on at the same time! Note: Backfill is a very niche workflow.")
226
229
  return
@@ -789,13 +792,13 @@ class ConfigApp:
789
792
  #
790
793
 
791
794
  def update_audio_ffmpeg_settings(self, event):
792
- selected_option = self.ffmpeg_preset_options.get()
793
- if selected_option in self.ffmpeg_preset_options_map:
794
- self.ffmpeg_reencode_options.delete(0, tk.END)
795
- self.ffmpeg_reencode_options.insert(0, self.ffmpeg_preset_options_map[selected_option])
795
+ selected_option = self.ffmpeg_audio_preset_options.get()
796
+ if selected_option in self.ffmpeg_audio_preset_options_map:
797
+ self.audio_ffmpeg_reencode_options.delete(0, tk.END)
798
+ self.audio_ffmpeg_reencode_options.insert(0, self.ffmpeg_audio_preset_options_map[selected_option])
796
799
  else:
797
- self.ffmpeg_reencode_options.delete(0, tk.END)
798
- self.ffmpeg_reencode_options.insert(0, "")
800
+ self.audio_ffmpeg_reencode_options.delete(0, tk.END)
801
+ self.audio_ffmpeg_reencode_options.insert(0, "")
799
802
 
800
803
  @new_tab
801
804
  def create_audio_tab(self):
@@ -830,29 +833,29 @@ class ConfigApp:
830
833
  ttk.Label(audio_frame, text="FFmpeg Preset Options:").grid(row=self.current_row, column=0, sticky='W')
831
834
 
832
835
  # Define display names and their corresponding values
833
- self.ffmpeg_preset_options_map = {
836
+ self.ffmpeg_audio_preset_options_map = {
834
837
  "No Re-encode" : "",
835
838
  "Simple loudness normalization (Simplest, Start Here)": "-c:a libopus -f opus -af \"loudnorm=I=-23:LRA=7:TP=-2\"",
836
839
  "Downmix to mono with normalization (Recommended(?))": "-c:a libopus -ac 1 -f opus -application voip -apply_phase_inv 0 -af \"loudnorm=I=-23:dual_mono=true\"",
837
840
  "Downmix to mono, 30kbps, normalized (Optimal(?))": "-c:a libopus -b:a 30k -ac 1 -f opus -application voip -apply_phase_inv 0 -af \"loudnorm=I=-23:dual_mono=true\"",
838
-
841
+ "Custom": get_config().audio.custom_encode_settings,
839
842
  }
840
843
 
841
844
  # Create a Combobox with display names
842
- self.ffmpeg_preset_options = ttk.Combobox(audio_frame, values=list(self.ffmpeg_preset_options_map.keys()), width=50)
845
+ self.ffmpeg_audio_preset_options = ttk.Combobox(audio_frame, values=list(self.ffmpeg_audio_preset_options_map.keys()), width=50)
843
846
  # self.ffmpeg_preset_options.set("Downmix to mono with normalization") # Set default display name
844
- self.ffmpeg_preset_options.grid(row=self.current_row, column=1)
847
+ self.ffmpeg_audio_preset_options.grid(row=self.current_row, column=1)
845
848
 
846
849
  # Bind selection to update settings
847
- self.ffmpeg_preset_options.bind("<<ComboboxSelected>>", self.update_audio_ffmpeg_settings)
850
+ self.ffmpeg_audio_preset_options.bind("<<ComboboxSelected>>", self.update_audio_ffmpeg_settings)
848
851
 
849
852
  self.add_label_and_increment_row(audio_frame, "Select a preset FFmpeg option for re-encoding screenshots.",
850
853
  row=self.current_row, column=2)
851
854
 
852
855
  ttk.Label(audio_frame, text="FFmpeg Reencode Options:").grid(row=self.current_row, column=0, sticky='W')
853
- self.ffmpeg_reencode_options = ttk.Entry(audio_frame, width=50)
854
- self.ffmpeg_reencode_options.insert(0, self.settings.audio.ffmpeg_reencode_options)
855
- self.ffmpeg_reencode_options.grid(row=self.current_row, column=1)
856
+ self.audio_ffmpeg_reencode_options = ttk.Entry(audio_frame, width=50)
857
+ self.audio_ffmpeg_reencode_options.insert(0, self.settings.audio.ffmpeg_reencode_options)
858
+ self.audio_ffmpeg_reencode_options.grid(row=self.current_row, column=1)
856
859
  self.add_label_and_increment_row(audio_frame, "Custom FFmpeg options for re-encoding audio files.",
857
860
  row=self.current_row, column=2)
858
861
 
@@ -137,6 +137,7 @@ class Audio:
137
137
  external_tool: str = ""
138
138
  anki_media_collection: str = ""
139
139
  external_tool_enabled: bool = True
140
+ custom_encode_settings: str = ''
140
141
 
141
142
 
142
143
  @dataclass_json
@@ -192,9 +192,9 @@ class VideoToAudioHandler(FileSystemEventHandler):
192
192
  logger.info("No voice activity detected, using full audio.")
193
193
  vad_trimmed_audio = trimmed_audio
194
194
  should_update_audio = True
195
- if get_config().audio.ffmpeg_reencode_options and os.path.exists(vad_trimmed_audio):
195
+ if get_config().audio.audio_ffmpeg_reencode_options and os.path.exists(vad_trimmed_audio):
196
196
  ffmpeg.reencode_file_with_user_config(vad_trimmed_audio, final_audio_output,
197
- get_config().audio.ffmpeg_reencode_options)
197
+ get_config().audio.audio_ffmpeg_reencode_options)
198
198
  elif os.path.exists(vad_trimmed_audio):
199
199
  shutil.move(vad_trimmed_audio, final_audio_output)
200
200
  return final_audio_output, should_update_audio, vad_trimmed_audio, vad_beginning, vad_end
@@ -1,4 +1,4 @@
1
- from .run import run, init_config
1
+ from GameSentenceMiner.owocr.owocr.run import run, init_config
2
2
 
3
3
 
4
4
  def main():
@@ -1048,3 +1048,79 @@ class GeminiOCR:
1048
1048
 
1049
1049
  def _preprocess(self, img):
1050
1050
  return pil_image_to_bytes(img, png_compression=1)
1051
+
1052
+
1053
+ class GroqOCR:
1054
+ name = 'groq'
1055
+ readable_name = 'Groq OCR'
1056
+ key = 'j'
1057
+ available = False
1058
+
1059
+ def __init__(self, config={'api_key': None}):
1060
+ try:
1061
+ import groq
1062
+ self.api_key = config['api_key']
1063
+ if not self.api_key:
1064
+ logger.warning('Groq API key not provided, GroqOCR will not work!')
1065
+ else:
1066
+ self.client = groq.Groq(api_key=self.api_key)
1067
+ self.available = True
1068
+ logger.info('Groq OCR ready')
1069
+ except ImportError:
1070
+ logger.warning('groq module not available, GroqOCR will not work!')
1071
+ except Exception as e:
1072
+ logger.error(f'Error initializing Groq client: {e}')
1073
+
1074
+ def __call__(self, img_or_path):
1075
+ if not self.available:
1076
+ return (False, 'GroqOCR is not available due to missing API key or configuration error.')
1077
+
1078
+ try:
1079
+ if isinstance(img_or_path, str) or isinstance(img_or_path, Path):
1080
+ img = Image.open(img_or_path).convert("RGB")
1081
+ elif isinstance(img_or_path, Image.Image):
1082
+ img = img_or_path.convert("RGB")
1083
+ else:
1084
+ raise ValueError(f'img_or_path must be a path or PIL.Image, instead got: {img_or_path}')
1085
+
1086
+ img_base64 = self._preprocess(img)
1087
+ if not img_base64:
1088
+ return (False, 'Error processing image for Groq.')
1089
+
1090
+ prompt = (
1091
+ "Analyze this image and extract text from it"
1092
+ # "(speech bubbles or panels containing character dialogue). From the extracted dialogue text, "
1093
+ # "filter out any furigana. Ignore and do not include any text found outside of dialogue boxes, "
1094
+ # "including character names, speaker labels, or sound effects. Return *only* the filtered dialogue text. "
1095
+ # "If no text is found within dialogue boxes after applying filters, return an empty string. "
1096
+ # "OR, if there are no text bubbles or dialogue boxes found, return everything."
1097
+ "Do not include any other output, formatting markers, or commentary, only the text from the image."
1098
+ )
1099
+
1100
+ response = self.client.chat.completions.create(
1101
+ model="meta-llama/llama-4-scout-17b-16e-instruct",
1102
+ messages=[
1103
+ {
1104
+ "role": "user",
1105
+ "content": [
1106
+ {"type": "text", "text": prompt},
1107
+ {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img_base64}"}},
1108
+ ],
1109
+ }
1110
+ ],
1111
+ max_tokens=300
1112
+ )
1113
+
1114
+ if response.choices and response.choices[0].message.content:
1115
+ text_output = response.choices[0].message.content.strip()
1116
+ return (True, text_output)
1117
+ else:
1118
+ return (True, "")
1119
+
1120
+ except FileNotFoundError:
1121
+ return (False, f'File not found: {img_or_path}')
1122
+ except Exception as e:
1123
+ return (False, f'Groq API request failed: {e}')
1124
+
1125
+ def _preprocess(self, img):
1126
+ return base64.b64encode(pil_image_to_bytes(img, png_compression=1)).decode('utf-8')
@@ -585,7 +585,8 @@ def process_and_write_results(img_or_path, write_to, notifications, last_result,
585
585
  for i, instance in enumerate(engine_instances):
586
586
  if instance.name.lower() in engine.lower():
587
587
  engine_instance = instance
588
- last_result = (last_result[0], i)
588
+ if last_result:
589
+ last_result = (last_result[0], i)
589
590
  break
590
591
  else:
591
592
  engine_instance = engine_instances[engine_index]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.8.17
3
+ Version: 2.8.19
4
4
  Summary: A tool for mining sentences from games. Update: Multi-Line Mining! Fixed!
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.8.17
3
+ Version: 2.8.19
4
4
  Summary: A tool for mining sentences from games. Update: Multi-Line Mining! Fixed!
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "GameSentenceMiner"
10
- version = "2.8.17"
10
+ version = "2.8.19"
11
11
  description = "A tool for mining sentences from games. Update: Multi-Line Mining! Fixed!"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.10"