GameSentenceMiner 2.9.3__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.
Files changed (49) hide show
  1. GameSentenceMiner/ai/ai_prompting.py +3 -3
  2. GameSentenceMiner/anki.py +17 -11
  3. GameSentenceMiner/assets/icon.png +0 -0
  4. GameSentenceMiner/assets/icon128.png +0 -0
  5. GameSentenceMiner/assets/icon256.png +0 -0
  6. GameSentenceMiner/assets/icon32.png +0 -0
  7. GameSentenceMiner/assets/icon512.png +0 -0
  8. GameSentenceMiner/assets/icon64.png +0 -0
  9. GameSentenceMiner/assets/pickaxe.png +0 -0
  10. GameSentenceMiner/config_gui.py +22 -7
  11. GameSentenceMiner/gametext.py +5 -5
  12. GameSentenceMiner/gsm.py +26 -67
  13. GameSentenceMiner/obs.py +7 -9
  14. GameSentenceMiner/ocr/owocr_area_selector.py +1 -1
  15. GameSentenceMiner/ocr/owocr_helper.py +30 -13
  16. GameSentenceMiner/owocr/owocr/ocr.py +0 -2
  17. GameSentenceMiner/owocr/owocr/run.py +1 -1
  18. GameSentenceMiner/{communication → util/communication}/__init__.py +1 -1
  19. GameSentenceMiner/{communication → util/communication}/send.py +1 -1
  20. GameSentenceMiner/{communication → util/communication}/websocket.py +2 -2
  21. GameSentenceMiner/{downloader → util/downloader}/download_tools.py +3 -3
  22. GameSentenceMiner/vad.py +344 -0
  23. GameSentenceMiner/web/texthooking_page.py +78 -55
  24. {gamesentenceminer-2.9.3.dist-info → gamesentenceminer-2.9.5.dist-info}/METADATA +2 -3
  25. gamesentenceminer-2.9.5.dist-info/RECORD +57 -0
  26. GameSentenceMiner/configuration.py +0 -647
  27. GameSentenceMiner/electron_config.py +0 -315
  28. GameSentenceMiner/ffmpeg.py +0 -441
  29. GameSentenceMiner/model.py +0 -177
  30. GameSentenceMiner/notification.py +0 -105
  31. GameSentenceMiner/package.py +0 -39
  32. GameSentenceMiner/ss_selector.py +0 -121
  33. GameSentenceMiner/text_log.py +0 -186
  34. GameSentenceMiner/util.py +0 -262
  35. GameSentenceMiner/vad/groq_trim.py +0 -82
  36. GameSentenceMiner/vad/result.py +0 -21
  37. GameSentenceMiner/vad/silero_trim.py +0 -52
  38. GameSentenceMiner/vad/vad_utils.py +0 -13
  39. GameSentenceMiner/vad/vosk_helper.py +0 -158
  40. GameSentenceMiner/vad/whisper_helper.py +0 -105
  41. gamesentenceminer-2.9.3.dist-info/RECORD +0 -64
  42. /GameSentenceMiner/{downloader → assets}/__init__.py +0 -0
  43. /GameSentenceMiner/{downloader → util/downloader}/Untitled_json.py +0 -0
  44. /GameSentenceMiner/{vad → util/downloader}/__init__.py +0 -0
  45. /GameSentenceMiner/{downloader → util/downloader}/oneocr_dl.py +0 -0
  46. {gamesentenceminer-2.9.3.dist-info → gamesentenceminer-2.9.5.dist-info}/WHEEL +0 -0
  47. {gamesentenceminer-2.9.3.dist-info → gamesentenceminer-2.9.5.dist-info}/entry_points.txt +0 -0
  48. {gamesentenceminer-2.9.3.dist-info → gamesentenceminer-2.9.5.dist-info}/licenses/LICENSE +0 -0
  49. {gamesentenceminer-2.9.3.dist-info → gamesentenceminer-2.9.5.dist-info}/top_level.txt +0 -0
@@ -1,105 +0,0 @@
1
- import platform
2
-
3
- import requests
4
- from plyer import notification
5
- from GameSentenceMiner.configuration import logger, is_windows
6
-
7
- if is_windows():
8
- from win10toast import ToastNotifier
9
-
10
- if is_windows():
11
- class MyToastNotifier(ToastNotifier):
12
- def __init__(self):
13
- super().__init__()
14
-
15
- def on_destroy(self, hwnd, msg, wparam, lparam):
16
- super().on_destroy(hwnd, msg, wparam, lparam)
17
- return 0
18
-
19
- if is_windows():
20
- notifier = MyToastNotifier()
21
- else:
22
- notifier = notification
23
-
24
-
25
- def open_anki_card(note_id):
26
- url = "http://localhost:8765"
27
- headers = {'Content-Type': 'application/json'}
28
-
29
- data = {
30
- "action": "guiEditNote",
31
- "version": 6,
32
- "params": {
33
- "note": note_id
34
- }
35
- }
36
-
37
- try:
38
- response = requests.post(url, json=data, headers=headers)
39
- if response.status_code == 200:
40
- logger.info(f"Opened Anki note with ID {note_id}")
41
- else:
42
- logger.error(f"Failed to open Anki note with ID {note_id}")
43
- except Exception as e:
44
- logger.info(f"Error connecting to AnkiConnect: {e}")
45
-
46
-
47
-
48
- def send_notification(title, message, timeout):
49
- if is_windows():
50
- notifier.show_toast(title, message, duration=timeout, threaded=True)
51
- else:
52
- notification.notify(
53
- title=title,
54
- message=message,
55
- app_name="GameSentenceMiner",
56
- timeout=timeout # Notification disappears after 5 seconds
57
- )
58
-
59
- def send_note_updated(tango):
60
- send_notification(
61
- title="Anki Card Updated",
62
- message=f"Audio and/or Screenshot added to note: {tango}",
63
- timeout=5 # Notification disappears after 5 seconds
64
- )
65
-
66
- def send_screenshot_updated(tango):
67
- send_notification(
68
- title="Anki Card Updated",
69
- message=f"Screenshot updated on note: {tango}",
70
- timeout=5 # Notification disappears after 5 seconds
71
- )
72
-
73
-
74
- def send_screenshot_saved(path):
75
- send_notification(
76
- title="Screenshot Saved",
77
- message=f"Screenshot saved to : {path}",
78
- timeout=5 # Notification disappears after 5 seconds
79
- )
80
-
81
-
82
- def send_audio_generated_notification(audio_path):
83
- send_notification(
84
- title="Audio Trimmed",
85
- message=f"Audio Trimmed and placed at {audio_path}",
86
- timeout=5 # Notification disappears after 5 seconds
87
- )
88
-
89
-
90
- def send_check_obs_notification(reason):
91
- send_notification(
92
- title="OBS Replay Invalid",
93
- message=f"Check OBS Settings! Reason: {reason}",
94
- timeout=5 # Notification disappears after 5 seconds
95
- )
96
-
97
-
98
- def send_error_no_anki_update():
99
- send_notification(
100
- title="Error",
101
- message=f"Anki Card not updated, Check Console for Reason!",
102
- timeout=5 # Notification disappears after 5 seconds
103
- )
104
-
105
-
@@ -1,39 +0,0 @@
1
- import os
2
- from importlib import metadata
3
-
4
- import requests
5
-
6
- from GameSentenceMiner.configuration import logger, get_app_directory
7
-
8
- PACKAGE_NAME = "GameSentenceMiner"
9
- VERSION_FILE_PATH = os.path.join(get_app_directory(), 'version.txt')
10
-
11
- def get_current_version():
12
- try:
13
- version = metadata.version(PACKAGE_NAME)
14
- return version
15
- except metadata.PackageNotFoundError:
16
- return None
17
-
18
- def get_latest_version():
19
- try:
20
- response = requests.get(f"https://pypi.org/pypi/{PACKAGE_NAME}/json")
21
- latest_version = response.json()["info"]["version"]
22
- return latest_version
23
- except Exception as e:
24
- logger.error(f"Error fetching latest version: {e}")
25
- return None
26
-
27
- def check_for_updates(force=False):
28
- try:
29
- installed_version = get_current_version()
30
- latest_version = get_latest_version()
31
-
32
- if installed_version != latest_version or force:
33
- logger.info(f"Update available: {installed_version} -> {latest_version}")
34
- return True, latest_version
35
- else:
36
- logger.info("You are already using the latest version.")
37
- return False, latest_version
38
- except Exception as e:
39
- logger.error(f"Error checking for updates: {e}")
@@ -1,121 +0,0 @@
1
- import tkinter as tk
2
- from PIL import Image, ImageTk
3
- import subprocess
4
- import os
5
- import sys
6
-
7
- from GameSentenceMiner import ffmpeg
8
- from GameSentenceMiner.configuration import get_temporary_directory, logger
9
- from GameSentenceMiner.ffmpeg import ffmpeg_base_command_list
10
- from GameSentenceMiner.util import sanitize_filename
11
-
12
- def extract_frames(video_path, timestamp, temp_dir, mode):
13
- frame_paths = []
14
- timestamp_number = float(timestamp)
15
- golden_frame_index = 1 # Default to the first frame
16
- golden_frame = None
17
- video_duration = ffmpeg.get_video_duration(video_path)
18
-
19
- if mode == 'middle':
20
- timestamp_number = max(0.0, timestamp_number - 2.5)
21
- elif mode == 'end':
22
- timestamp_number = max(0.0, timestamp_number - 5.0)
23
-
24
- if video_duration is not None and timestamp_number > video_duration:
25
- logger.debug(f"Timestamp {timestamp_number} exceeds video duration {video_duration}.")
26
- return None
27
-
28
- try:
29
- command = ffmpeg_base_command_list + [
30
- "-y",
31
- "-ss", str(timestamp_number),
32
- "-i", video_path,
33
- "-vf", f"fps=1/{0.25}",
34
- "-vframes", "20",
35
- os.path.join(temp_dir, "frame_%02d.png")
36
- ]
37
- subprocess.run(command, check=True, capture_output=True)
38
- for i in range(1, 21):
39
- if os.path.exists(os.path.join(temp_dir, f"frame_{i:02d}.png")):
40
- frame_paths.append(os.path.join(temp_dir, f"frame_{i:02d}.png"))
41
-
42
- if mode == "beginning":
43
- golden_frame = frame_paths[0]
44
- if mode == "middle":
45
- golden_frame = frame_paths[len(frame_paths) // 2]
46
- if mode == "end":
47
- golden_frame = frame_paths[-1]
48
- except subprocess.CalledProcessError as e:
49
- logger.debug(f"Error extracting frames: {e}")
50
- logger.debug(f"Command was: {' '.join(command)}")
51
- logger.debug(f"FFmpeg output:\n{e.stderr.decode()}")
52
- return None
53
- except Exception as e:
54
- logger.debug(f"An error occurred: {e}")
55
- return None
56
- return frame_paths, golden_frame
57
-
58
- def timestamp_to_seconds(timestamp):
59
- hours, minutes, seconds = map(int, timestamp.split(':'))
60
- return hours * 3600 + minutes * 60 + seconds
61
-
62
- def display_images(image_paths, golden_frame):
63
- window = tk.Tk()
64
- window.configure(bg="black") # Set the background color to black
65
- window.title("Image Selector")
66
- selected_path = tk.StringVar()
67
- image_widgets = []
68
-
69
- def on_image_click(event):
70
- widget = event.widget
71
- index = image_widgets.index(widget)
72
- selected_path.set(image_paths[index])
73
- window.quit()
74
-
75
- for i, path in enumerate(image_paths):
76
- img = Image.open(path)
77
- img.thumbnail((450, 450))
78
- img_tk = ImageTk.PhotoImage(img)
79
- if golden_frame and path == golden_frame:
80
- label = tk.Label(window, image=img_tk, borderwidth=5, relief="solid")
81
- label.config(highlightbackground="yellow", highlightthickness=5)
82
- else:
83
- label = tk.Label(window, image=img_tk)
84
- label.image = img_tk
85
- label.grid(row=i // 5, column=i % 5, padx=5, pady=5)
86
- label.bind("<Button-1>", on_image_click) # Bind click event to the label
87
- image_widgets.append(label)
88
-
89
- window.attributes("-topmost", True)
90
- window.mainloop()
91
- return selected_path.get()
92
-
93
- def run_extraction_and_display(video_path, timestamp_str, mode):
94
- temp_dir = os.path.join(get_temporary_directory(False), "screenshot_frames", sanitize_filename(os.path.splitext(os.path.basename(video_path))[0]))
95
- os.makedirs(temp_dir, exist_ok=True)
96
- image_paths, golden_frame = extract_frames(video_path, timestamp_str, temp_dir, mode)
97
- if image_paths:
98
- selected_image_path = display_images(image_paths, golden_frame)
99
- if selected_image_path:
100
- print(selected_image_path)
101
- else:
102
- logger.debug("No image was selected.")
103
- else:
104
- logger.debug("Frame extraction failed.")
105
-
106
- def main():
107
- # if len(sys.argv) != 3:
108
- # print("Usage: python script.py <video_path> <timestamp>")
109
- # sys.exit(1)
110
- try:
111
- video_path = sys.argv[1]
112
- timestamp_str = sys.argv[2]
113
- mode = sys.argv[3] if len(sys.argv) > 3 else "beginning"
114
- run_extraction_and_display(video_path, timestamp_str, mode)
115
- except Exception as e:
116
- logger.debug(e)
117
- sys.exit(1)
118
-
119
-
120
- if __name__ == "__main__":
121
- main()
@@ -1,186 +0,0 @@
1
- import uuid
2
- from dataclasses import dataclass
3
- from datetime import datetime
4
- from difflib import SequenceMatcher
5
- from typing import Optional
6
-
7
- from GameSentenceMiner.configuration import logger, get_config
8
- from GameSentenceMiner.model import AnkiCard
9
- from GameSentenceMiner.util import remove_html_and_cloze_tags
10
-
11
- initial_time = datetime.now()
12
-
13
-
14
- @dataclass
15
- class GameLine:
16
- id: str
17
- text: str
18
- time: datetime
19
- prev: 'GameLine | None'
20
- next: 'GameLine | None'
21
- index: int = 0
22
-
23
- def get_previous_time(self):
24
- if self.prev:
25
- return self.prev.time
26
- return initial_time
27
-
28
- def get_next_time(self):
29
- if self.next:
30
- return self.next.time
31
- return 0
32
-
33
- def __str__(self):
34
- return str({"text": self.text, "time": self.time})
35
-
36
-
37
- @dataclass
38
- class GameText:
39
- values: list[GameLine]
40
- values_dict: dict[str, GameLine]
41
- game_line_index = 0
42
-
43
- def __init__(self):
44
- self.values = []
45
- self.values_dict = {}
46
-
47
- def __getitem__(self, index):
48
- return self.values[index]
49
-
50
- def get_by_id(self, line_id: str) -> Optional[GameLine]:
51
- if not self.values_dict:
52
- return None
53
- return self.values_dict.get(line_id)
54
-
55
- def get_time(self, line_text: str, occurrence: int = -1) -> datetime:
56
- matches = [line for line in self.values if line.text == line_text]
57
- if matches:
58
- return matches[occurrence].time # Default to latest
59
- return initial_time
60
-
61
- def get_event(self, line_text: str, occurrence: int = -1) -> GameLine | None:
62
- matches = [line for line in self.values if line.text == line_text]
63
- if matches:
64
- return matches[occurrence]
65
- return None
66
-
67
- def add_line(self, line_text, line_time=None):
68
- if not line_text:
69
- return
70
- line_id = str(uuid.uuid1())
71
- new_line = GameLine(
72
- id=line_id, # Time-based UUID as an integer
73
- text=line_text,
74
- time=line_time if line_time else datetime.now(),
75
- prev=self.values[-1] if self.values else None,
76
- next=None,
77
- index=self.game_line_index
78
- )
79
- self.values_dict[line_id] = new_line
80
- logger.debug(f"Adding line: {new_line}")
81
- self.game_line_index += 1
82
- if self.values:
83
- self.values[-1].next = new_line
84
- self.values.append(new_line)
85
- # self.remove_old_events(datetime.now() - timedelta(minutes=10))
86
-
87
- def has_line(self, line_text) -> bool:
88
- for game_line in self.values:
89
- if game_line.text == line_text:
90
- return True
91
- return False
92
-
93
-
94
- text_log = GameText()
95
-
96
-
97
- def similar(a, b):
98
- return SequenceMatcher(None, a, b).ratio()
99
-
100
-
101
- def one_contains_the_other(a, b):
102
- return a in b or b in a
103
-
104
-
105
- def lines_match(a, b):
106
- similarity = similar(a, b)
107
- logger.debug(f"Comparing: {a} with {b} - Similarity: {similarity}, Or One contains the other: {one_contains_the_other(a, b)}")
108
- return similar(a, b) >= 0.60 or one_contains_the_other(a, b)
109
-
110
-
111
- def get_text_event(last_note) -> GameLine:
112
- lines = text_log.values
113
-
114
- if not lines:
115
- raise Exception("No lines in history. Text is required from either clipboard or websocket for GSM to work. Please check your setup/config.")
116
-
117
- if not last_note:
118
- return lines[-1]
119
-
120
- sentence = last_note.get_field(get_config().anki.sentence_field)
121
- if not sentence:
122
- return lines[-1]
123
-
124
- for line in reversed(lines):
125
- if lines_match(line.text, remove_html_and_cloze_tags(sentence)):
126
- return line
127
-
128
- logger.info("Could not find matching sentence from GSM's history. Using the latest line.")
129
- return lines[-1]
130
-
131
-
132
- def get_line_and_future_lines(last_note):
133
- if not last_note:
134
- return []
135
-
136
- sentence = last_note.get_field(get_config().anki.sentence_field)
137
- found_lines = []
138
- if sentence:
139
- found = False
140
- for line in text_log.values:
141
- if found:
142
- found_lines.append(line.text)
143
- if lines_match(line.text, remove_html_and_cloze_tags(sentence)): # 80% similarity threshold
144
- found = True
145
- found_lines.append(line.text)
146
- return found_lines
147
-
148
-
149
- def get_mined_line(last_note: AnkiCard, lines):
150
- if not last_note:
151
- return lines[-1]
152
- if not lines:
153
- lines = get_all_lines()
154
-
155
- sentence = last_note.get_field(get_config().anki.sentence_field)
156
- for line in lines:
157
- if lines_match(line.text, remove_html_and_cloze_tags(sentence)):
158
- return line
159
- return lines[-1]
160
-
161
-
162
- def get_time_of_line(line):
163
- return text_log.get_time(line)
164
-
165
-
166
- def get_all_lines():
167
- return text_log.values
168
-
169
-
170
- def get_text_log() -> GameText:
171
- return text_log
172
-
173
- def add_line(current_line_after_regex, line_time):
174
- text_log.add_line(current_line_after_regex, line_time)
175
-
176
- def get_line_by_id(line_id: str) -> Optional[GameLine]:
177
- """
178
- Retrieve a GameLine by its unique ID.
179
-
180
- Args:
181
- line_id (str): The unique identifier of the GameLine.
182
-
183
- Returns:
184
- Optional[GameLine]: The GameLine object if found, otherwise None.
185
- """
186
- return text_log.get_by_id(line_id)