GameSentenceMiner 2.8.13__py3-none-any.whl → 2.8.15__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/anki.py +1 -1
- GameSentenceMiner/config_gui.py +63 -19
- GameSentenceMiner/configuration.py +12 -0
- GameSentenceMiner/ffmpeg.py +25 -7
- GameSentenceMiner/gametext.py +9 -7
- GameSentenceMiner/gsm.py +8 -8
- GameSentenceMiner/ocr/gsm_ocr_config.py +1 -0
- GameSentenceMiner/ocr/owocr_helper.py +16 -12
- GameSentenceMiner/util.py +10 -10
- GameSentenceMiner/web/templates/utility.html +72 -28
- GameSentenceMiner/web/texthooking_page.py +21 -4
- {gamesentenceminer-2.8.13.dist-info → gamesentenceminer-2.8.15.dist-info}/METADATA +1 -1
- {gamesentenceminer-2.8.13.dist-info → gamesentenceminer-2.8.15.dist-info}/RECORD +17 -17
- {gamesentenceminer-2.8.13.dist-info → gamesentenceminer-2.8.15.dist-info}/WHEEL +1 -1
- {gamesentenceminer-2.8.13.dist-info → gamesentenceminer-2.8.15.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.8.13.dist-info → gamesentenceminer-2.8.15.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.8.13.dist-info → gamesentenceminer-2.8.15.dist-info}/top_level.txt +0 -0
GameSentenceMiner/anki.py
CHANGED
@@ -282,7 +282,7 @@ def update_new_card():
|
|
282
282
|
texthooking_page.reset_checked_lines()
|
283
283
|
else:
|
284
284
|
logger.info("New card(s) detected! Added to Processing Queue!")
|
285
|
-
card_queue.append(last_card)
|
285
|
+
card_queue.append((last_card, datetime.now()))
|
286
286
|
try:
|
287
287
|
obs.save_replay_buffer()
|
288
288
|
except Exception as e:
|
GameSentenceMiner/config_gui.py
CHANGED
@@ -157,8 +157,9 @@ class ConfigApp:
|
|
157
157
|
custom_ffmpeg_settings=self.screenshot_custom_ffmpeg_settings.get(),
|
158
158
|
screenshot_hotkey_updates_anki=self.screenshot_hotkey_update_anki.get(),
|
159
159
|
seconds_after_line = self.seconds_after_line.get(),
|
160
|
-
use_beginning_of_line_as_screenshot=self.use_beginning_of_line_as_screenshot.get(),
|
161
|
-
use_new_screenshot_logic=self.use_new_screenshot_logic.get()
|
160
|
+
# use_beginning_of_line_as_screenshot=self.use_beginning_of_line_as_screenshot.get(),
|
161
|
+
# use_new_screenshot_logic=self.use_new_screenshot_logic.get(),
|
162
|
+
screenshot_timing_setting=self.screenshot_timing.get()
|
162
163
|
),
|
163
164
|
audio=Audio(
|
164
165
|
enabled=self.audio_enabled.get(),
|
@@ -199,10 +200,11 @@ class ConfigApp:
|
|
199
200
|
advanced=Advanced(
|
200
201
|
audio_player_path=self.audio_player_path.get(),
|
201
202
|
video_player_path=self.video_player_path.get(),
|
202
|
-
show_screenshot_buttons=self.show_screenshot_button.get(),
|
203
|
+
# show_screenshot_buttons=self.show_screenshot_button.get(),
|
203
204
|
multi_line_line_break=self.multi_line_line_break.get(),
|
204
205
|
multi_line_sentence_storage_field=self.multi_line_sentence_storage_field.get(),
|
205
206
|
ocr_sends_to_clipboard=self.ocr_sends_to_clipboard.get(),
|
207
|
+
use_anki_note_creation_time=self.use_anki_note_creation_time.get(),
|
206
208
|
),
|
207
209
|
ai=Ai(
|
208
210
|
enabled=self.ai_enabled.get(),
|
@@ -754,28 +756,46 @@ class ConfigApp:
|
|
754
756
|
self.add_label_and_increment_row(screenshot_frame, "Custom FFmpeg options for re-encoding screenshots.",
|
755
757
|
row=self.current_row, column=2)
|
756
758
|
|
759
|
+
|
757
760
|
ttk.Label(screenshot_frame, text="Screenshot Hotkey Updates Anki:").grid(row=self.current_row, column=0, sticky='W')
|
758
761
|
self.screenshot_hotkey_update_anki = tk.BooleanVar(value=self.settings.screenshot.screenshot_hotkey_updates_anki)
|
759
762
|
ttk.Checkbutton(screenshot_frame, variable=self.screenshot_hotkey_update_anki).grid(row=self.current_row, column=1, sticky='W')
|
760
763
|
self.add_label_and_increment_row(screenshot_frame, "Enable to allow Screenshot hotkey/button to update the latest anki card.", row=self.current_row,
|
761
764
|
column=2)
|
762
765
|
|
763
|
-
ttk.Label(screenshot_frame, text="
|
766
|
+
ttk.Label(screenshot_frame, text="Screenshot Timing:").grid(row=self.current_row, column=0, sticky='W')
|
767
|
+
self.screenshot_timing = ttk.Combobox(screenshot_frame, values=['beginning', 'middle', 'end'])
|
768
|
+
self.screenshot_timing.insert(0, self.settings.screenshot.screenshot_timing_setting)
|
769
|
+
self.screenshot_timing.grid(row=self.current_row, column=1)
|
770
|
+
self.add_label_and_increment_row(screenshot_frame, "Select when to take the screenshot relative to the line: beginning, middle, or end.", row=self.current_row,
|
771
|
+
column=2)
|
772
|
+
|
773
|
+
ttk.Label(screenshot_frame, text="Screenshot Offset:").grid(row=self.current_row, column=0, sticky='W')
|
764
774
|
self.seconds_after_line = ttk.Entry(screenshot_frame)
|
765
775
|
self.seconds_after_line.insert(0, str(self.settings.screenshot.seconds_after_line))
|
766
776
|
self.seconds_after_line.grid(row=self.current_row, column=1)
|
767
|
-
self.add_label_and_increment_row(screenshot_frame, "
|
777
|
+
self.add_label_and_increment_row(screenshot_frame, "Time in seconds to offset the screenshot based on the Timing setting above (should almost always be positive)", row=self.current_row,
|
768
778
|
column=2)
|
769
779
|
|
770
|
-
ttk.Label(screenshot_frame, text="Use Beginning of Line as Screenshot:").grid(row=self.current_row, column=0, sticky='W')
|
771
|
-
self.use_beginning_of_line_as_screenshot = tk.BooleanVar(value=self.settings.screenshot.use_beginning_of_line_as_screenshot)
|
772
|
-
ttk.Checkbutton(screenshot_frame, variable=self.use_beginning_of_line_as_screenshot).grid(row=self.current_row, column=1, sticky='W')
|
773
|
-
self.add_label_and_increment_row(screenshot_frame, "Enable to use the beginning of the line as the screenshot point. Adjust the above setting to fine-tine timing.", row=self.current_row, column=2)
|
774
|
-
|
775
|
-
ttk.Label(screenshot_frame, text="Use alternative screenshot logic:").grid(row=self.current_row, column=0, sticky='W')
|
776
|
-
self.use_new_screenshot_logic = tk.BooleanVar(value=self.settings.screenshot.use_new_screenshot_logic)
|
777
|
-
ttk.Checkbutton(screenshot_frame, variable=self.use_new_screenshot_logic).grid(row=self.current_row, column=1, sticky='W')
|
778
|
-
self.add_label_and_increment_row(screenshot_frame, "Enable to use the new screenshot logic. This will try to take the screenshot in the middle of the voiceline, or middle of the line if no audio/vad.", row=self.current_row, column=2)
|
780
|
+
# ttk.Label(screenshot_frame, text="Use Beginning of Line as Screenshot:").grid(row=self.current_row, column=0, sticky='W')
|
781
|
+
# self.use_beginning_of_line_as_screenshot = tk.BooleanVar(value=self.settings.screenshot.use_beginning_of_line_as_screenshot)
|
782
|
+
# ttk.Checkbutton(screenshot_frame, variable=self.use_beginning_of_line_as_screenshot).grid(row=self.current_row, column=1, sticky='W')
|
783
|
+
# self.add_label_and_increment_row(screenshot_frame, "Enable to use the beginning of the line as the screenshot point. Adjust the above setting to fine-tine timing.", row=self.current_row, column=2)
|
784
|
+
#
|
785
|
+
# ttk.Label(screenshot_frame, text="Use alternative screenshot logic:").grid(row=self.current_row, column=0, sticky='W')
|
786
|
+
# self.use_new_screenshot_logic = tk.BooleanVar(value=self.settings.screenshot.use_new_screenshot_logic)
|
787
|
+
# ttk.Checkbutton(screenshot_frame, variable=self.use_new_screenshot_logic).grid(row=self.current_row, column=1, sticky='W')
|
788
|
+
# self.add_label_and_increment_row(screenshot_frame, "Enable to use the new screenshot logic. This will try to take the screenshot in the middle of the voiceline, or middle of the line if no audio/vad.", row=self.current_row, column=2)
|
789
|
+
#
|
790
|
+
|
791
|
+
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])
|
796
|
+
else:
|
797
|
+
self.ffmpeg_reencode_options.delete(0, tk.END)
|
798
|
+
self.ffmpeg_reencode_options.insert(0, "")
|
779
799
|
|
780
800
|
@new_tab
|
781
801
|
def create_audio_tab(self):
|
@@ -807,6 +827,28 @@ class ConfigApp:
|
|
807
827
|
self.add_label_and_increment_row(audio_frame, "Offset in seconds to end audio processing.",
|
808
828
|
row=self.current_row, column=2)
|
809
829
|
|
830
|
+
ttk.Label(audio_frame, text="FFmpeg Preset Options:").grid(row=self.current_row, column=0, sticky='W')
|
831
|
+
|
832
|
+
# Define display names and their corresponding values
|
833
|
+
self.ffmpeg_preset_options_map = {
|
834
|
+
"No Re-encode" : "",
|
835
|
+
"Simple loudness normalization (Simplest, Start Here)": "-c:a libopus -f opus -af \"loudnorm=I=-23:LRA=7:TP=-2\"",
|
836
|
+
"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
|
+
"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
|
+
|
839
|
+
}
|
840
|
+
|
841
|
+
# 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)
|
843
|
+
# 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)
|
845
|
+
|
846
|
+
# Bind selection to update settings
|
847
|
+
self.ffmpeg_preset_options.bind("<<ComboboxSelected>>", self.update_audio_ffmpeg_settings)
|
848
|
+
|
849
|
+
self.add_label_and_increment_row(audio_frame, "Select a preset FFmpeg option for re-encoding screenshots.",
|
850
|
+
row=self.current_row, column=2)
|
851
|
+
|
810
852
|
ttk.Label(audio_frame, text="FFmpeg Reencode Options:").grid(row=self.current_row, column=0, sticky='W')
|
811
853
|
self.ffmpeg_reencode_options = ttk.Entry(audio_frame, width=50)
|
812
854
|
self.ffmpeg_reencode_options.insert(0, self.settings.audio.ffmpeg_reencode_options)
|
@@ -814,6 +856,7 @@ class ConfigApp:
|
|
814
856
|
self.add_label_and_increment_row(audio_frame, "Custom FFmpeg options for re-encoding audio files.",
|
815
857
|
row=self.current_row, column=2)
|
816
858
|
|
859
|
+
|
817
860
|
ttk.Label(audio_frame, text="Anki Media Collection:").grid(row=self.current_row, column=0, sticky='W')
|
818
861
|
self.anki_media_collection = ttk.Entry(audio_frame)
|
819
862
|
self.anki_media_collection.insert(0, self.settings.audio.anki_media_collection)
|
@@ -974,11 +1017,6 @@ class ConfigApp:
|
|
974
1017
|
self.play_latest_audio_hotkey.grid(row=self.current_row, column=1)
|
975
1018
|
self.add_label_and_increment_row(advanced_frame, "Hotkey to trim and play the latest audio.", row=self.current_row, column=2)
|
976
1019
|
|
977
|
-
ttk.Label(advanced_frame, text="Show Screenshot Button:").grid(row=self.current_row, column=0, sticky='W')
|
978
|
-
self.show_screenshot_button = tk.BooleanVar(value=self.settings.advanced.show_screenshot_buttons)
|
979
|
-
ttk.Checkbutton(advanced_frame, variable=self.show_screenshot_button).grid(row=self.current_row, column=1, sticky='W')
|
980
|
-
self.add_label_and_increment_row(advanced_frame, "Show the screenshot button in the utility gui.", row=self.current_row, column=2)
|
981
|
-
|
982
1020
|
ttk.Label(advanced_frame, text="Multi-line Line-Break:").grid(row=self.current_row, column=0, sticky='W')
|
983
1021
|
self.multi_line_line_break = ttk.Entry(advanced_frame)
|
984
1022
|
self.multi_line_line_break.insert(0, self.settings.advanced.multi_line_line_break)
|
@@ -997,6 +1035,12 @@ class ConfigApp:
|
|
997
1035
|
self.add_label_and_increment_row(advanced_frame, "Enable to send OCR results to clipboard.", row=self.current_row, column=2)
|
998
1036
|
|
999
1037
|
|
1038
|
+
ttk.Label(advanced_frame, text="Use Anki Creation Date for Audio Timing:").grid(row=self.current_row, column=0, sticky='W')
|
1039
|
+
self.use_anki_note_creation_time = tk.BooleanVar(value=self.settings.advanced.use_anki_note_creation_time)
|
1040
|
+
ttk.Checkbutton(advanced_frame, variable=self.use_anki_note_creation_time).grid(row=self.current_row, column=1, sticky='W')
|
1041
|
+
self.add_label_and_increment_row(advanced_frame, "Use the Anki note creation date for audio timing instead of the OBS replay time.", row=self.current_row, column=2)
|
1042
|
+
|
1043
|
+
|
1000
1044
|
|
1001
1045
|
@new_tab
|
1002
1046
|
def create_ai_tab(self):
|
@@ -109,10 +109,21 @@ class Screenshot:
|
|
109
109
|
quality: str = 85
|
110
110
|
extension: str = "webp"
|
111
111
|
custom_ffmpeg_settings: str = ''
|
112
|
+
custom_ffmpeg_option_selected: str = ''
|
112
113
|
screenshot_hotkey_updates_anki: bool = False
|
113
114
|
seconds_after_line: float = 1.0
|
114
115
|
use_beginning_of_line_as_screenshot: bool = True
|
115
116
|
use_new_screenshot_logic: bool = False
|
117
|
+
screenshot_timing_setting: str = '' # 'middle', 'end'
|
118
|
+
|
119
|
+
def __post_init__(self):
|
120
|
+
if not self.screenshot_timing_setting and self.use_beginning_of_line_as_screenshot:
|
121
|
+
self.screenshot_timing_setting = 'beginning'
|
122
|
+
if not self.screenshot_timing_setting and self.use_new_screenshot_logic:
|
123
|
+
self.screenshot_timing_setting = 'middle'
|
124
|
+
if not self.screenshot_timing_setting and not self.use_beginning_of_line_as_screenshot and not self.use_new_screenshot_logic:
|
125
|
+
self.screenshot_timing_setting = 'end'
|
126
|
+
|
116
127
|
|
117
128
|
|
118
129
|
@dataclass_json
|
@@ -181,6 +192,7 @@ class Advanced:
|
|
181
192
|
multi_line_line_break: str = '<br>'
|
182
193
|
multi_line_sentence_storage_field: str = ''
|
183
194
|
ocr_sends_to_clipboard: bool = True
|
195
|
+
use_anki_note_creation_time: bool = False
|
184
196
|
|
185
197
|
@dataclass_json
|
186
198
|
@dataclass
|
GameSentenceMiner/ffmpeg.py
CHANGED
@@ -57,6 +57,8 @@ def get_screenshot_time(video_path, game_line, default_beginning=False, vad_begi
|
|
57
57
|
# Assuming initial_time is defined elsewhere if game_line is None
|
58
58
|
line_time = initial_time
|
59
59
|
|
60
|
+
logger.info("Calculating screenshot time for line: " + str(game_line.text))
|
61
|
+
|
60
62
|
file_length = get_video_duration(video_path)
|
61
63
|
file_mod_time = get_file_modification_time(video_path)
|
62
64
|
|
@@ -69,14 +71,27 @@ def get_screenshot_time(video_path, game_line, default_beginning=False, vad_begi
|
|
69
71
|
if vad_beginning and vad_end and not doing_multi_line:
|
70
72
|
logger.debug("Using VAD to determine screenshot time")
|
71
73
|
screenshot_time_from_beginning = line_timestamp_in_video + vad_end - screenshot_offset
|
72
|
-
elif get_config().screenshot.
|
74
|
+
elif get_config().screenshot.screenshot_timing_setting == "beginning":
|
75
|
+
logger.debug("Using beginning of line for screenshot")
|
76
|
+
screenshot_time_from_beginning = line_timestamp_in_video + screenshot_offset
|
77
|
+
elif get_config().screenshot.screenshot_timing_setting == "middle":
|
73
78
|
if game_line.next:
|
74
79
|
logger.debug("Finding time between lines for screenshot")
|
75
80
|
screenshot_time_from_beginning = line_timestamp_in_video + ((game_line.next.time - game_line.time).total_seconds() / 2)
|
76
81
|
else:
|
77
82
|
logger.debug("Using end of line for screenshot")
|
78
83
|
screenshot_time_from_beginning = file_length - screenshot_offset
|
84
|
+
elif get_config().screenshot.screenshot_timing_setting == "end":
|
85
|
+
logger.debug("Using end of line for screenshot")
|
86
|
+
if game_line.next:
|
87
|
+
logger.debug("Finding time between lines for screenshot")
|
88
|
+
screenshot_time_from_beginning = line_timestamp_in_video + (game_line.next.time - game_line.time).total_seconds() - screenshot_offset
|
89
|
+
else:
|
90
|
+
logger.debug("Using end of video for screenshot")
|
91
|
+
# If no next line, use the end of the video
|
92
|
+
screenshot_time_from_beginning = file_length - screenshot_offset
|
79
93
|
else:
|
94
|
+
logger.error(f"Invalid screenshot timing setting: {get_config().screenshot.screenshot_timing_setting}")
|
80
95
|
screenshot_time_from_beginning = line_timestamp_in_video + screenshot_offset
|
81
96
|
|
82
97
|
# Check if the calculated time is out of bounds
|
@@ -150,7 +165,7 @@ def get_audio_codec(video_path):
|
|
150
165
|
return None
|
151
166
|
|
152
167
|
|
153
|
-
def get_audio_and_trim(video_path, game_line, next_line_time):
|
168
|
+
def get_audio_and_trim(video_path, game_line, next_line_time, anki_card_creation_time):
|
154
169
|
supported_formats = {
|
155
170
|
'opus': 'libopus',
|
156
171
|
'mp3': 'libmp3lame',
|
@@ -184,7 +199,7 @@ def get_audio_and_trim(video_path, game_line, next_line_time):
|
|
184
199
|
|
185
200
|
subprocess.run(command)
|
186
201
|
|
187
|
-
return trim_audio_based_on_last_line(untrimmed_audio, video_path, game_line, next_line_time)
|
202
|
+
return trim_audio_based_on_last_line(untrimmed_audio, video_path, game_line, next_line_time, anki_card_creation_time)
|
188
203
|
|
189
204
|
|
190
205
|
def get_video_duration(file_path):
|
@@ -202,10 +217,10 @@ def get_video_duration(file_path):
|
|
202
217
|
return float(duration_info["format"]["duration"]) # Return the duration in seconds
|
203
218
|
|
204
219
|
|
205
|
-
def trim_audio_based_on_last_line(untrimmed_audio, video_path, game_line, next_line):
|
220
|
+
def trim_audio_based_on_last_line(untrimmed_audio, video_path, game_line, next_line, anki_card_creation_time):
|
206
221
|
trimmed_audio = tempfile.NamedTemporaryFile(dir=configuration.get_temporary_directory(),
|
207
222
|
suffix=f".{get_config().audio.extension}").name
|
208
|
-
start_trim_time, total_seconds, total_seconds_after_offset = get_video_timings(video_path, game_line)
|
223
|
+
start_trim_time, total_seconds, total_seconds_after_offset = get_video_timings(video_path, game_line, anki_card_creation_time)
|
209
224
|
|
210
225
|
ffmpeg_command = ffmpeg_base_command_list + [
|
211
226
|
"-i", untrimmed_audio,
|
@@ -232,8 +247,11 @@ def trim_audio_based_on_last_line(untrimmed_audio, video_path, game_line, next_l
|
|
232
247
|
logger.info(f"Audio trimmed and saved to {trimmed_audio}")
|
233
248
|
return trimmed_audio
|
234
249
|
|
235
|
-
def get_video_timings(video_path, game_line):
|
236
|
-
|
250
|
+
def get_video_timings(video_path, game_line, anki_card_creation_time):
|
251
|
+
if anki_card_creation_time and get_config().advanced.use_anki_note_creation_time:
|
252
|
+
file_mod_time = anki_card_creation_time
|
253
|
+
else:
|
254
|
+
file_mod_time = get_file_modification_time(video_path)
|
237
255
|
file_length = get_video_duration(video_path)
|
238
256
|
time_delta = file_mod_time - game_line.time
|
239
257
|
# Convert time_delta to FFmpeg-friendly format (HH:MM:SS.milliseconds)
|
GameSentenceMiner/gametext.py
CHANGED
@@ -78,13 +78,15 @@ async def listen_websocket():
|
|
78
78
|
if e.response.status_code == 404:
|
79
79
|
logger.info("Texthooker WebSocket connection failed. Attempting some fixes...")
|
80
80
|
try_other = True
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
81
|
+
await asyncio.sleep(0.1)
|
82
|
+
else:
|
83
|
+
if not (isinstance(e, ConnectionResetError) or isinstance(e, ConnectionError) or isinstance(e, InvalidStatus) or isinstance(e, websockets.ConnectionClosed)):
|
84
|
+
logger.error(f"Unexpected error in Texthooker WebSocket connection: {e}")
|
85
|
+
websocket_connected = False
|
86
|
+
if not reconnecting:
|
87
|
+
logger.warning(f"Texthooker WebSocket connection lost, Defaulting to clipboard if enabled. Attempting to Reconnect...")
|
88
|
+
reconnecting = True
|
89
|
+
await asyncio.sleep(5)
|
88
90
|
|
89
91
|
async def handle_new_text_event(current_clipboard, line_time=None):
|
90
92
|
global current_line, current_line_time, current_line_after_regex
|
GameSentenceMiner/gsm.py
CHANGED
@@ -90,8 +90,9 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
90
90
|
return
|
91
91
|
try:
|
92
92
|
last_note = None
|
93
|
+
anki_card_creation_time = None
|
93
94
|
if anki.card_queue and len(anki.card_queue) > 0:
|
94
|
-
last_note = anki.card_queue.pop(0)
|
95
|
+
last_note, anki_card_creation_time = anki.card_queue.pop(0)
|
95
96
|
with util.lock:
|
96
97
|
util.set_last_mined_line(anki.get_sentence(last_note))
|
97
98
|
if os.path.exists(video_path) and os.access(video_path, os.R_OK):
|
@@ -136,7 +137,8 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
136
137
|
final_audio_output, should_update_audio, vad_trimmed_audio, vad_beginning, vad_end = VideoToAudioHandler.get_audio(
|
137
138
|
start_line,
|
138
139
|
line_cutoff,
|
139
|
-
video_path
|
140
|
+
video_path,
|
141
|
+
anki_card_creation_time)
|
140
142
|
else:
|
141
143
|
final_audio_output = ""
|
142
144
|
should_update_audio = False
|
@@ -148,7 +150,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
148
150
|
elif not get_config().anki.sentence_audio_field:
|
149
151
|
logger.info("No SentenceAudio Field in config, skipping audio processing!")
|
150
152
|
|
151
|
-
ss_timing = ffmpeg.get_screenshot_time(video_path, mined_line, vad_beginning, vad_end, bool(selected_lines))
|
153
|
+
ss_timing = ffmpeg.get_screenshot_time(video_path, mined_line, vad_beginning=vad_beginning, vad_end=vad_end, doing_multi_line=bool(selected_lines))
|
152
154
|
|
153
155
|
if get_config().anki.update_anki and last_note:
|
154
156
|
anki.update_anki_card(last_note, note, audio_path=final_audio_output, video_path=video_path,
|
@@ -171,8 +173,8 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
171
173
|
|
172
174
|
|
173
175
|
@staticmethod
|
174
|
-
def get_audio(game_line, next_line_time, video_path, temporary=False):
|
175
|
-
trimmed_audio = get_audio_and_trim(video_path, game_line, next_line_time)
|
176
|
+
def get_audio(game_line, next_line_time, video_path, anki_card_creation_time,temporary=False):
|
177
|
+
trimmed_audio = get_audio_and_trim(video_path, game_line, next_line_time, anki_card_creation_time)
|
176
178
|
if temporary:
|
177
179
|
return trimmed_audio
|
178
180
|
vad_trimmed_audio = make_unique_file_name(
|
@@ -292,8 +294,6 @@ def get_screenshot():
|
|
292
294
|
encoded_image = ffmpeg.process_image(image)
|
293
295
|
if get_config().anki.update_anki and get_config().screenshot.screenshot_hotkey_updates_anki:
|
294
296
|
last_note = anki.get_last_anki_card()
|
295
|
-
if last_note:
|
296
|
-
logger.debug(json.dumps(last_note))
|
297
297
|
if get_config().features.backfill_audio:
|
298
298
|
last_note = anki.get_cards_by_sentence(gametext.current_line)
|
299
299
|
if last_note:
|
@@ -306,7 +306,7 @@ def get_screenshot():
|
|
306
306
|
else:
|
307
307
|
notification.send_screenshot_saved(encoded_image)
|
308
308
|
except Exception as e:
|
309
|
-
logger.error(f"Failed to get Screenshot {e}")
|
309
|
+
logger.error(f"Failed to get Screenshot: {e}")
|
310
310
|
|
311
311
|
|
312
312
|
def create_image():
|
@@ -43,6 +43,7 @@ class OCRConfig:
|
|
43
43
|
coordinate_system: str = None
|
44
44
|
window_geometry: Optional[WindowGeometry] = None
|
45
45
|
window: Optional[str] = None
|
46
|
+
language: str = "ja"
|
46
47
|
|
47
48
|
def __post_init__(self):
|
48
49
|
if self.coordinate_system and self.coordinate_system == "percentage" and self.window:
|
@@ -320,7 +320,7 @@ def run_oneocr(ocr_config: OCRConfig, i, area=False):
|
|
320
320
|
text_callback=text_callback,
|
321
321
|
screen_capture_exclusions=exclusions,
|
322
322
|
rectangle=i,
|
323
|
-
language=
|
323
|
+
language=language)
|
324
324
|
done = True
|
325
325
|
|
326
326
|
|
@@ -335,27 +335,31 @@ def get_window(window_name):
|
|
335
335
|
else:
|
336
336
|
return None
|
337
337
|
except Exception as e:
|
338
|
-
print(f"Error finding window '{
|
338
|
+
print(f"Error finding window '{window_name}': {e}")
|
339
339
|
return None
|
340
340
|
|
341
341
|
if __name__ == "__main__":
|
342
|
-
global ocr1, ocr2, twopassocr
|
342
|
+
global ocr1, ocr2, twopassocr, language
|
343
343
|
import sys
|
344
344
|
|
345
345
|
args = sys.argv[1:]
|
346
|
-
if len(args) ==
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
346
|
+
if len(args) == 4:
|
347
|
+
language = args[0]
|
348
|
+
ocr1 = args[1]
|
349
|
+
ocr2 = args[2]
|
350
|
+
twopassocr = bool(int(args[3]))
|
351
|
+
elif len(args) == 3:
|
352
|
+
language = args[0]
|
353
|
+
ocr1 = args[1]
|
354
|
+
ocr2 = args[2]
|
353
355
|
twopassocr = True
|
354
|
-
elif len(args) ==
|
355
|
-
|
356
|
+
elif len(args) == 2:
|
357
|
+
language = args[0]
|
358
|
+
ocr1 = args[1]
|
356
359
|
ocr2 = None
|
357
360
|
twopassocr = False
|
358
361
|
else:
|
362
|
+
language = "ja"
|
359
363
|
ocr1 = "oneocr"
|
360
364
|
ocr2 = "glens"
|
361
365
|
twopassocr = True
|
GameSentenceMiner/util.py
CHANGED
@@ -58,7 +58,7 @@ def timedelta_to_ffmpeg_friendly_format(td_obj):
|
|
58
58
|
|
59
59
|
|
60
60
|
def get_file_modification_time(file_path):
|
61
|
-
mod_time_epoch = os.path.
|
61
|
+
mod_time_epoch = os.path.getmtime(file_path)
|
62
62
|
mod_time = datetime.fromtimestamp(mod_time_epoch)
|
63
63
|
return mod_time
|
64
64
|
|
@@ -255,12 +255,12 @@ os.makedirs(os.path.dirname(TEXT_REPLACEMENTS_FILE), exist_ok=True)
|
|
255
255
|
|
256
256
|
import urllib.request
|
257
257
|
|
258
|
-
if not os.path.exists(OCR_REPLACEMENTS_FILE):
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
258
|
+
# if not os.path.exists(OCR_REPLACEMENTS_FILE):
|
259
|
+
# url = "https://raw.githubusercontent.com/bpwhelan/GameSentenceMiner/refs/heads/main/electron-src/assets/ocr_replacements.json"
|
260
|
+
# try:
|
261
|
+
# with urllib.request.urlopen(url) as response:
|
262
|
+
# data = response.read().decode('utf-8')
|
263
|
+
# with open(OCR_REPLACEMENTS_FILE, 'w', encoding='utf-8') as f:
|
264
|
+
# f.write(data)
|
265
|
+
# except Exception as e:
|
266
|
+
# logger.error(f"Failed to fetch JSON from {url}: {e}")
|
@@ -107,26 +107,30 @@
|
|
107
107
|
</style>
|
108
108
|
</head>
|
109
109
|
<body>
|
110
|
+
<div style="position: fixed; top: 20px; right: 20px; display: flex; gap: 10px;">
|
111
|
+
<button onclick="window.location.href='/textreplacements'" style="background-color: #1a73e8; color: #ffffff; border: none; padding: 10px 20px; font-size: 12px; cursor: pointer; transition: background-color 0.3s; border-radius: 5px;">
|
112
|
+
Text Replacements
|
113
|
+
</button>
|
114
|
+
<button id="delete-history" style="background-color: #1a73e8; color: #ffffff; border: none; padding: 10px 20px; font-size: 12px; cursor: pointer; transition: background-color 0.3s; border-radius: 5px;">
|
115
|
+
Clear History
|
116
|
+
</button>
|
117
|
+
</div>
|
110
118
|
<div id="initial-events">
|
111
119
|
|
112
120
|
</div>
|
113
121
|
<hr class="initial-events-separator" id="initial-events-separator" style="display: none;">
|
114
122
|
<div id="session-events">
|
115
123
|
|
116
|
-
</div>
|
117
|
-
<div>
|
118
|
-
<button onclick="window.location.href='/textreplacements'" style="margin-top: 20px; background-color: #1a73e8; color: #ffffff; border: none; padding: 10px 20px; font-size: 16px; cursor: pointer; transition: background-color 0.3s; border-radius: 5px;">
|
119
|
-
Text Replacements
|
120
|
-
</button>
|
121
124
|
</div>
|
122
125
|
<script>
|
123
126
|
let mainStyle = document.querySelector('head style');
|
127
|
+
let deleteHistoryButton = document.getElementById('delete-history');
|
124
128
|
console.log(mainStyle);
|
125
129
|
let displayedEventIds = new Set();
|
126
130
|
let isTabActive = true;
|
127
131
|
let isFetching = false; // Flag to track if a fetch is in progress
|
128
132
|
let intervalId = 0;
|
129
|
-
const fetchInterval =
|
133
|
+
const fetchInterval = 100; // Define the interval as a constant
|
130
134
|
const websocketPort = {{ websocket_port }} || 55001;
|
131
135
|
|
132
136
|
// Drag selection variables
|
@@ -135,11 +139,13 @@
|
|
135
139
|
let newCheckboxState = false;
|
136
140
|
let hoveredCheckboxes = new Set();
|
137
141
|
let checkboxes = []; // Will hold all checkbox elements
|
142
|
+
let checkboxMap = {};
|
138
143
|
let checkboxes_being_updated = new Set();
|
139
144
|
|
140
145
|
// Shift click selection variable
|
141
146
|
let lastChecked = null;
|
142
147
|
|
148
|
+
|
143
149
|
async function fetchEvents() {
|
144
150
|
if (document.hidden || isFetching) {
|
145
151
|
return;
|
@@ -152,21 +158,30 @@
|
|
152
158
|
}
|
153
159
|
const events = await res.json();
|
154
160
|
|
161
|
+
let historyEvents = []
|
155
162
|
events.forEach(ev => {
|
156
163
|
if (!displayedEventIds.has(ev.id)) {
|
157
164
|
if (ev.history) {
|
158
|
-
|
165
|
+
historyEvents.push(ev);
|
159
166
|
document.getElementById('initial-events-separator').style.display = 'block';
|
160
167
|
} else {
|
161
168
|
addNewEvent(ev)
|
162
169
|
}
|
163
170
|
}
|
164
|
-
|
165
|
-
|
166
|
-
|
171
|
+
if (!ev.history) {
|
172
|
+
console.log(checkboxMap[ev.id])
|
173
|
+
if (!checkboxes_being_updated.has(ev.id)) {
|
174
|
+
const checkbox = checkboxMap[ev.id];
|
175
|
+
if (checkbox) {
|
176
|
+
checkbox.checked = ev.checked;
|
177
|
+
}
|
178
|
+
}
|
167
179
|
}
|
168
180
|
});
|
169
|
-
|
181
|
+
if (historyEvents.length > 0) {
|
182
|
+
addEventsToHistory(historyEvents);
|
183
|
+
}
|
184
|
+
// checkboxes = Array.from(document.querySelectorAll('#session-events input[type="checkbox"]')); // Update checkboxes array after new events
|
170
185
|
} catch (error) {
|
171
186
|
console.error("Error fetching events:", error);
|
172
187
|
} finally {
|
@@ -174,29 +189,32 @@
|
|
174
189
|
}
|
175
190
|
}
|
176
191
|
|
177
|
-
function
|
178
|
-
displayedEventIds.add(event.id);
|
192
|
+
function addEventsToHistory(events) {
|
179
193
|
const container = document.getElementById('initial-events');
|
180
|
-
const
|
181
|
-
// div.className = 'textline';
|
194
|
+
const fragment = document.createDocumentFragment();
|
182
195
|
|
183
|
-
|
196
|
+
events.forEach(event => {
|
197
|
+
displayedEventIds.add(event.id);
|
198
|
+
const div = document.createElement('div');
|
199
|
+
const shadowRoot = div.attachShadow({ mode: 'open' });
|
184
200
|
|
185
|
-
|
186
|
-
|
187
|
-
|
201
|
+
const wrapper = document.createElement('div');
|
202
|
+
wrapper.className = 'textline';
|
203
|
+
wrapper.innerHTML = `<p>${event.text}</p>
|
188
204
|
<em class="clock-icon">${event.time.replace(' GMT', '')}</em>
|
189
205
|
`;
|
190
206
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
207
|
+
const style = document.createElement('style');
|
208
|
+
style.textContent = mainStyle.innerHTML;
|
209
|
+
shadowRoot.appendChild(style);
|
210
|
+
shadowRoot.appendChild(wrapper);
|
195
211
|
|
196
|
-
|
212
|
+
fragment.appendChild(div);
|
213
|
+
});
|
214
|
+
|
215
|
+
container.appendChild(fragment);
|
197
216
|
window.scrollTo({
|
198
217
|
top: document.documentElement.scrollHeight,
|
199
|
-
behavior: 'smooth'
|
200
218
|
});
|
201
219
|
}
|
202
220
|
|
@@ -235,10 +253,13 @@
|
|
235
253
|
shadowRoot.appendChild(style);
|
236
254
|
shadowRoot.appendChild(wrapper);
|
237
255
|
|
256
|
+
let checkbox = shadowRoot.querySelector('.multi-line-checkbox')
|
257
|
+
checkboxes.push(checkbox);
|
258
|
+
checkboxMap[event.id] = checkbox; // Store the checkbox in the map for easy access
|
259
|
+
|
238
260
|
container.appendChild(div);
|
239
261
|
window.scrollTo({
|
240
262
|
top: document.documentElement.scrollHeight,
|
241
|
-
behavior: 'smooth'
|
242
263
|
});
|
243
264
|
}
|
244
265
|
|
@@ -344,10 +365,34 @@
|
|
344
365
|
lastChecked = e.target;
|
345
366
|
}
|
346
367
|
|
368
|
+
function deleteHistory(e) {
|
369
|
+
e.preventDefault();
|
370
|
+
if (confirm("Are you sure you want to delete the history? This action cannot be undone.")) {
|
371
|
+
fetch('/clear_history', {
|
372
|
+
method: 'POST',
|
373
|
+
headers: { 'Content-Type': 'application/json' }
|
374
|
+
})
|
375
|
+
.then(response => {
|
376
|
+
if (!response.ok) {
|
377
|
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
378
|
+
}
|
379
|
+
// Clear the displayed events
|
380
|
+
displayedEventIds.clear();
|
381
|
+
document.getElementById('initial-events').innerHTML = '';
|
382
|
+
document.getElementById('session-events').innerHTML = '';
|
383
|
+
document.getElementById('initial-events-separator').style.display = 'none';
|
384
|
+
})
|
385
|
+
.catch(error => {
|
386
|
+
console.error("Error deleting history:", error);
|
387
|
+
});
|
388
|
+
}
|
389
|
+
}
|
390
|
+
|
347
391
|
document.addEventListener('mousedown', handleMouseDown);
|
348
392
|
document.addEventListener('mouseup', handleMouseUp);
|
349
393
|
document.addEventListener('mouseover', handleMouseOver);
|
350
394
|
document.addEventListener('click', handleCheckboxClick);
|
395
|
+
deleteHistoryButton.addEventListener('click', deleteHistory);
|
351
396
|
|
352
397
|
const websocketURL = 'ws://localhost:' + websocketPort;
|
353
398
|
let websocket = {};
|
@@ -404,7 +449,7 @@
|
|
404
449
|
return websocket;
|
405
450
|
};
|
406
451
|
|
407
|
-
connectWebSocket();
|
452
|
+
// connectWebSocket();
|
408
453
|
|
409
454
|
|
410
455
|
fetchEvents();
|
@@ -419,7 +464,6 @@
|
|
419
464
|
|
420
465
|
window.scrollTo({
|
421
466
|
top: document.documentElement.scrollHeight,
|
422
|
-
behavior: 'smooth'
|
423
467
|
});
|
424
468
|
</script>
|
425
469
|
</body>
|
@@ -103,6 +103,10 @@ class EventManager:
|
|
103
103
|
event_queue.put(new_event)
|
104
104
|
return new_event
|
105
105
|
|
106
|
+
def reset_checked_lines(self):
|
107
|
+
for event in self.events:
|
108
|
+
event.checked = False
|
109
|
+
|
106
110
|
def get_events(self):
|
107
111
|
return self.events
|
108
112
|
|
@@ -117,6 +121,14 @@ class EventManager:
|
|
117
121
|
if self.conn:
|
118
122
|
self.conn.close()
|
119
123
|
|
124
|
+
def clear_history(self):
|
125
|
+
self.cursor.execute("DELETE FROM events WHERE time < ?", (initial_time.isoformat(),))
|
126
|
+
logger.info(f"Cleared history before {initial_time.isoformat()}")
|
127
|
+
self.conn.commit()
|
128
|
+
# Clear the in-memory events as well
|
129
|
+
event_manager.events = [event for event in event_manager if not event.history]
|
130
|
+
event_manager.events_dict = {event.id: event for event in event_manager.events}
|
131
|
+
|
120
132
|
class EventProcessor(threading.Thread):
|
121
133
|
def __init__(self, event_queue, db_path):
|
122
134
|
super().__init__()
|
@@ -227,6 +239,13 @@ def textreplacements():
|
|
227
239
|
def get_data():
|
228
240
|
return jsonify([event.to_dict() for event in event_manager])
|
229
241
|
|
242
|
+
@app.route('/clear_history', methods=['POST'])
|
243
|
+
def clear_history():
|
244
|
+
temp_em = EventManager()
|
245
|
+
temp_em.clear_history()
|
246
|
+
temp_em.close_connection()
|
247
|
+
return jsonify({'message': 'History cleared successfully'}), 200
|
248
|
+
|
230
249
|
|
231
250
|
async def add_event_to_texthooker(line: GameLine):
|
232
251
|
logger.info("Adding event to web server: %s", line.text)
|
@@ -248,8 +267,7 @@ def update_event():
|
|
248
267
|
return jsonify({'error': 'Missing id or checked status'}), 400
|
249
268
|
|
250
269
|
event_manager.get(event_id).checked = checked
|
251
|
-
|
252
|
-
return jsonify({'error': 'Event not found'}), 404
|
270
|
+
return jsonify({'message': 'Event updated successfully'}), 200
|
253
271
|
|
254
272
|
@app.route('/get-screenshot', methods=['Post'])
|
255
273
|
def get_screenshot():
|
@@ -339,8 +357,7 @@ def are_lines_selected():
|
|
339
357
|
return any(item.checked for item in event_manager)
|
340
358
|
|
341
359
|
def reset_checked_lines():
|
342
|
-
|
343
|
-
item.checked = False
|
360
|
+
event_manager.reset_checked_lines()
|
344
361
|
|
345
362
|
def open_texthooker():
|
346
363
|
webbrowser.open(url + '/texthooker')
|
@@ -1,17 +1,17 @@
|
|
1
1
|
GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
GameSentenceMiner/anki.py,sha256=
|
3
|
-
GameSentenceMiner/config_gui.py,sha256
|
4
|
-
GameSentenceMiner/configuration.py,sha256
|
2
|
+
GameSentenceMiner/anki.py,sha256=OCLgZa-iEp93v-R0zKFkDCjule_EAoP5rIqtnMHLnOw,14226
|
3
|
+
GameSentenceMiner/config_gui.py,sha256=-xZRTzFtnhnmbSiL03XpNrzV29Pb891nLuXHI_CZw7I,73759
|
4
|
+
GameSentenceMiner/configuration.py,sha256=-De0KfXu0jHTPl0xyktUxT-_fNUS_AK7nmzblKXRdGc,22525
|
5
5
|
GameSentenceMiner/electron_config.py,sha256=dGcPYCISPehXubYSzsDuI2Gl092MYK0u3bTnkL9Jh1Y,9787
|
6
|
-
GameSentenceMiner/ffmpeg.py,sha256=
|
7
|
-
GameSentenceMiner/gametext.py,sha256=
|
8
|
-
GameSentenceMiner/gsm.py,sha256=
|
6
|
+
GameSentenceMiner/ffmpeg.py,sha256=lLZ9v8_h5wfenLC6W36Ikt0Q7HQrChxsZ6lPk2umXjI,14658
|
7
|
+
GameSentenceMiner/gametext.py,sha256=pyIjMxMgKAAIyKzvtq8FV-zdg1ETRf7GVgkE19_2-B4,5513
|
8
|
+
GameSentenceMiner/gsm.py,sha256=bwDt7yAM9_Jf0BY0oxs6vwL8jGrHxnw_sznqbJp5Hdg,25382
|
9
9
|
GameSentenceMiner/model.py,sha256=JdnkT4VoPOXmOpRgFdvERZ09c9wLN6tUJxdrKlGZcqo,5305
|
10
10
|
GameSentenceMiner/notification.py,sha256=FY39ChSRK0Y8TQ6lBGsLnpZUFPtFpSy2tweeXVoV7kc,2809
|
11
11
|
GameSentenceMiner/obs.py,sha256=GPlsFrcv1eYelXyJfpspGK0iZK5AXPkoFsIGdB7eJrk,10002
|
12
12
|
GameSentenceMiner/package.py,sha256=YlS6QRMuVlm6mdXx0rlXv9_3erTGS21jaP3PNNWfAH0,1250
|
13
13
|
GameSentenceMiner/text_log.py,sha256=tBuZ8ElUgPtiQV8U6U90kmRxposwIkL3fjOYejdzikc,5153
|
14
|
-
GameSentenceMiner/util.py,sha256=
|
14
|
+
GameSentenceMiner/util.py,sha256=LzWGIDZb8NLv-RyrE_d6ycoQEwM1zpaDhWp0LKb6_Zc,8928
|
15
15
|
GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
16
|
GameSentenceMiner/ai/ai_prompting.py,sha256=uTtXIDRpcQqPT4EC-R_pwRP9pBmZ64I6vMDOexhJRp8,9505
|
17
17
|
GameSentenceMiner/communication/__init__.py,sha256=_jGn9PJxtOAOPtJ2rI-Qu9hEHVZVpIvWlxKvqk91_zI,638
|
@@ -22,10 +22,10 @@ GameSentenceMiner/downloader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
22
22
|
GameSentenceMiner/downloader/download_tools.py,sha256=mI1u_FGBmBqDIpCH3jOv8DOoZ3obgP5pIf9o9SVfX2Q,8131
|
23
23
|
GameSentenceMiner/downloader/oneocr_dl.py,sha256=o3ANp5IodEQoQ8GPcJdg9Y8JzA_lictwnebFPwwUZVk,10144
|
24
24
|
GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
25
|
-
GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=
|
25
|
+
GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=rQC6C8PKJXWoAvwCOYa363kodQQBwl1YNeYsD0bBbx4,1957
|
26
26
|
GameSentenceMiner/ocr/ocrconfig.py,sha256=_tY8mjnzHMJrLS8E5pHqYXZjMuLoGKYgJwdhYgN-ny4,6466
|
27
27
|
GameSentenceMiner/ocr/owocr_area_selector.py,sha256=gwYOz-fA5qoL63wh77eyGJtBtO7YVvWyO5cHb3D0Oz4,46738
|
28
|
-
GameSentenceMiner/ocr/owocr_helper.py,sha256
|
28
|
+
GameSentenceMiner/ocr/owocr_helper.py,sha256=-ezF6AO7ayZBs2vMt0Zj7D-gc8YbrgO69mFsJD7VmRs,17383
|
29
29
|
GameSentenceMiner/owocr/owocr/__init__.py,sha256=opjBOyGGyEqZCE6YdZPnyt7nVfiwyELHsXA0jAsjm14,25
|
30
30
|
GameSentenceMiner/owocr/owocr/__main__.py,sha256=r8MI6RAmbkTWqOJ59uvXoDS7CSw5jX5war9ULGWELrA,128
|
31
31
|
GameSentenceMiner/owocr/owocr/config.py,sha256=n-xtVylb2Q_H84jb1ZsIGxPQjTNnyvnRny1RhtaLJM8,7550
|
@@ -38,7 +38,7 @@ GameSentenceMiner/vad/silero_trim.py,sha256=ULf3zwS-JMsY82cKF7gZxREHw8L6lgpWF2U1
|
|
38
38
|
GameSentenceMiner/vad/vosk_helper.py,sha256=125X8C9NxFPlWWpoNsbOnEqKx8RCjXN109zNx_QXhyg,6070
|
39
39
|
GameSentenceMiner/vad/whisper_helper.py,sha256=JJ-iltCh813XdjyEw0Wn5DaErf6PDqfH0Efu1Md8cIY,3543
|
40
40
|
GameSentenceMiner/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
41
|
-
GameSentenceMiner/web/texthooking_page.py,sha256=
|
41
|
+
GameSentenceMiner/web/texthooking_page.py,sha256=AtVV9RS7HC3XnOq4X0FIMqJrzFoGlfSHFvS_CfhuzuA,13558
|
42
42
|
GameSentenceMiner/web/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
43
43
|
GameSentenceMiner/web/static/apple-touch-icon.png,sha256=OcMI8af_68DA_tweOsQ5LytTyMwm7-hPW07IfrOVgEs,46132
|
44
44
|
GameSentenceMiner/web/static/favicon-96x96.png,sha256=lOePzjiKl1JY2J1kT_PMdyEnrlJmi5GWbmXJunM12B4,16502
|
@@ -50,10 +50,10 @@ GameSentenceMiner/web/static/web-app-manifest-192x192.png,sha256=EfSNnBmsSaLfESb
|
|
50
50
|
GameSentenceMiner/web/static/web-app-manifest-512x512.png,sha256=wyqgCWCrLEUxSRXmaA3iJEESd-vM-ZmlTtZFBY4V8Pk,230819
|
51
51
|
GameSentenceMiner/web/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
52
52
|
GameSentenceMiner/web/templates/text_replacements.html,sha256=tV5c8mCaWSt_vKuUpbdbLAzXZ3ATZeDvQ9PnnAfqY0M,8598
|
53
|
-
GameSentenceMiner/web/templates/utility.html,sha256=
|
54
|
-
gamesentenceminer-2.8.
|
55
|
-
gamesentenceminer-2.8.
|
56
|
-
gamesentenceminer-2.8.
|
57
|
-
gamesentenceminer-2.8.
|
58
|
-
gamesentenceminer-2.8.
|
59
|
-
gamesentenceminer-2.8.
|
53
|
+
GameSentenceMiner/web/templates/utility.html,sha256=NUp4Yjs6_j7YeqsM2rcF0LzwS6nXSBUWJDl-k2E8BbM,16270
|
54
|
+
gamesentenceminer-2.8.15.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
55
|
+
gamesentenceminer-2.8.15.dist-info/METADATA,sha256=bLKHdjfoOke5LB1zD8IerMRtf_WvCAaAsSGtUxcg-Us,5933
|
56
|
+
gamesentenceminer-2.8.15.dist-info/WHEEL,sha256=GHB6lJx2juba1wDgXDNlMTyM13ckjBMKf-OnwgKOCtA,91
|
57
|
+
gamesentenceminer-2.8.15.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
58
|
+
gamesentenceminer-2.8.15.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
59
|
+
gamesentenceminer-2.8.15.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|