GameSentenceMiner 2.8.26__py3-none-any.whl → 2.8.28__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- GameSentenceMiner/ai/ai_prompting.py +3 -3
- GameSentenceMiner/anki.py +14 -7
- GameSentenceMiner/ffmpeg.py +17 -25
- GameSentenceMiner/gsm.py +25 -17
- GameSentenceMiner/ocr/owocr_helper.py +1 -1
- GameSentenceMiner/owocr/owocr/config.py +25 -13
- GameSentenceMiner/owocr/owocr/ocr.py +103 -95
- GameSentenceMiner/owocr/owocr/run.py +602 -598
- GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +3 -2
- GameSentenceMiner/vad/result.py +8 -0
- GameSentenceMiner/vad/silero_trim.py +3 -4
- GameSentenceMiner/vad/vosk_helper.py +5 -5
- GameSentenceMiner/vad/whisper_helper.py +5 -5
- {gamesentenceminer-2.8.26.dist-info → gamesentenceminer-2.8.28.dist-info}/METADATA +1 -1
- {gamesentenceminer-2.8.26.dist-info → gamesentenceminer-2.8.28.dist-info}/RECORD +19 -18
- {gamesentenceminer-2.8.26.dist-info → gamesentenceminer-2.8.28.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.8.26.dist-info → gamesentenceminer-2.8.28.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.8.26.dist-info → gamesentenceminer-2.8.28.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.8.26.dist-info → gamesentenceminer-2.8.28.dist-info}/top_level.txt +0 -0
@@ -99,7 +99,7 @@ class GeminiAI(AIManager):
|
|
99
99
|
genai.configure(api_key=self.ai_config.api_key)
|
100
100
|
model_name = self.ai_config.model
|
101
101
|
self.model = genai.GenerativeModel(model_name)
|
102
|
-
self.logger.
|
102
|
+
self.logger.debug(f"GeminiAIManager initialized with model: {model_name}")
|
103
103
|
except Exception as e:
|
104
104
|
self.logger.error(f"Failed to initialize Gemini API: {e}")
|
105
105
|
self.model = None
|
@@ -134,7 +134,7 @@ class GroqAI(AIManager):
|
|
134
134
|
self.model_name = self.ai_config.model
|
135
135
|
try:
|
136
136
|
self.client = Groq(api_key=self.api_key)
|
137
|
-
self.logger.
|
137
|
+
self.logger.debug(f"GroqAIManager initialized with model: {self.model_name}")
|
138
138
|
except Exception as e:
|
139
139
|
self.logger.error(f"Failed to initialize Groq client: {e}")
|
140
140
|
self.client = None
|
@@ -187,7 +187,7 @@ def get_ai_prompt_result(lines: List[GameLine], sentence: str, current_line: Gam
|
|
187
187
|
return ""
|
188
188
|
return ai_manager.process(lines, sentence, current_line, game_title)
|
189
189
|
except Exception as e:
|
190
|
-
logger.
|
190
|
+
logger.error("Error caught while trying to get AI prompt result. Check logs for more details.")
|
191
191
|
logger.debug(e)
|
192
192
|
return ""
|
193
193
|
|
GameSentenceMiner/anki.py
CHANGED
@@ -8,7 +8,7 @@ from datetime import datetime, timedelta
|
|
8
8
|
from requests import post
|
9
9
|
|
10
10
|
from GameSentenceMiner import obs, util, notification, ffmpeg
|
11
|
-
from GameSentenceMiner.ai.ai_prompting import
|
11
|
+
from GameSentenceMiner.ai.ai_prompting import get_ai_prompt_result
|
12
12
|
from GameSentenceMiner.configuration import *
|
13
13
|
from GameSentenceMiner.configuration import get_config
|
14
14
|
from GameSentenceMiner.model import AnkiCard
|
@@ -28,7 +28,7 @@ card_queue = []
|
|
28
28
|
|
29
29
|
|
30
30
|
def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='', tango='', reuse_audio=False,
|
31
|
-
should_update_audio=True, ss_time=0, game_line=None, selected_lines=None):
|
31
|
+
should_update_audio=True, ss_time=0, game_line=None, selected_lines=None, prev_ss_timing=0):
|
32
32
|
global audio_in_anki, screenshot_in_anki, prev_screenshot_in_anki
|
33
33
|
update_audio = should_update_audio and (get_config().anki.sentence_audio_field and not
|
34
34
|
last_note.get_field(get_config().anki.sentence_audio_field) or get_config().anki.overwrite_audio)
|
@@ -45,7 +45,14 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
45
45
|
if get_config().paths.remove_screenshot:
|
46
46
|
os.remove(screenshot)
|
47
47
|
if get_config().anki.previous_image_field:
|
48
|
-
|
48
|
+
if prev_ss_timing != 0:
|
49
|
+
try:
|
50
|
+
prev_screenshot = ffmpeg.get_screenshot(video_path, prev_ss_timing)
|
51
|
+
except Exception as e:
|
52
|
+
logger.error(f"Error getting previous screenshot based on VAD, Falling back to previous logic: {e}")
|
53
|
+
prev_screenshot = ffmpeg.get_screenshot(video_path, ffmpeg.get_screenshot_time(video_path, selected_lines[0].prev if selected_lines else game_line.prev))
|
54
|
+
else:
|
55
|
+
prev_screenshot = ffmpeg.get_screenshot(video_path, ffmpeg.get_screenshot_time(video_path, selected_lines[0].prev if selected_lines else game_line.prev))
|
49
56
|
prev_screenshot_in_anki = store_media_file(prev_screenshot)
|
50
57
|
if get_config().paths.remove_screenshot:
|
51
58
|
os.remove(prev_screenshot)
|
@@ -69,7 +76,7 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
69
76
|
get_config().anki.sentence_field)
|
70
77
|
translation = get_ai_prompt_result(get_all_lines(), sentence_to_translate,
|
71
78
|
game_line, get_current_game())
|
72
|
-
logger.info(translation)
|
79
|
+
logger.info(f"AI prompt Result: {translation}")
|
73
80
|
note['fields'][get_config().ai.anki_field] = translation
|
74
81
|
|
75
82
|
if prev_screenshot_in_anki:
|
@@ -99,7 +106,7 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
99
106
|
|
100
107
|
|
101
108
|
def open_audio_in_external(fileabspath, shell=False):
|
102
|
-
logger.info(f"Opening audio
|
109
|
+
logger.info(f"Opening audio in external program...")
|
103
110
|
if shell:
|
104
111
|
subprocess.Popen(f' "{get_config().audio.external_tool}" "{fileabspath}" ', shell=True)
|
105
112
|
else:
|
@@ -271,8 +278,8 @@ def update_new_card():
|
|
271
278
|
if not last_card or not check_tags_for_should_update(last_card):
|
272
279
|
return
|
273
280
|
use_prev_audio = sentence_is_same_as_previous(last_card)
|
274
|
-
logger.
|
275
|
-
logger.info(f"
|
281
|
+
logger.debug(f"last mined line: {util.get_last_mined_line()}, current sentence: {get_sentence(last_card)}")
|
282
|
+
logger.info(f"New card using previous audio: {use_prev_audio}")
|
276
283
|
if get_config().obs.get_game_from_scene:
|
277
284
|
obs.update_current_game()
|
278
285
|
if use_prev_audio:
|
GameSentenceMiner/ffmpeg.py
CHANGED
@@ -42,7 +42,7 @@ def get_screenshot(video_file, screenshot_timing):
|
|
42
42
|
# Run the command
|
43
43
|
subprocess.run(ffmpeg_command)
|
44
44
|
|
45
|
-
logger.
|
45
|
+
logger.debug(f"Screenshot saved to: {output_image}")
|
46
46
|
|
47
47
|
return output_image
|
48
48
|
|
@@ -50,14 +50,16 @@ def get_screenshot_for_line(video_file, game_line):
|
|
50
50
|
return get_screenshot(video_file, get_screenshot_time(video_file, game_line))
|
51
51
|
|
52
52
|
|
53
|
-
def get_screenshot_time(video_path, game_line, default_beginning=False,
|
53
|
+
def get_screenshot_time(video_path, game_line, default_beginning=False, vad_result=None, doing_multi_line=False, previous_line=False):
|
54
54
|
if game_line:
|
55
55
|
line_time = game_line.time
|
56
56
|
else:
|
57
57
|
# Assuming initial_time is defined elsewhere if game_line is None
|
58
58
|
line_time = initial_time
|
59
|
-
|
60
|
-
|
59
|
+
if previous_line:
|
60
|
+
logger.debug(f"Calculating screenshot time for previous line: {str(game_line.text)}")
|
61
|
+
else:
|
62
|
+
logger.debug("Calculating screenshot time for line: " + str(game_line.text))
|
61
63
|
|
62
64
|
file_length = get_video_duration(video_path)
|
63
65
|
file_mod_time = get_file_modification_time(video_path)
|
@@ -68,28 +70,24 @@ def get_screenshot_time(video_path, game_line, default_beginning=False, vad_begi
|
|
68
70
|
screenshot_offset = get_config().screenshot.seconds_after_line
|
69
71
|
|
70
72
|
# Calculate screenshot time from the beginning by adding the offset
|
71
|
-
if
|
72
|
-
|
73
|
-
screenshot_time_from_beginning
|
73
|
+
if vad_result and vad_result.success and not doing_multi_line:
|
74
|
+
screenshot_time_from_beginning = line_timestamp_in_video + vad_result.end - 1
|
75
|
+
logger.info(f"Using VAD result {vad_result} for screenshot time: {screenshot_time_from_beginning} seconds from beginning of replay")
|
74
76
|
elif get_config().screenshot.screenshot_timing_setting == "beginning":
|
75
|
-
logger.debug("Using beginning of line for screenshot")
|
76
77
|
screenshot_time_from_beginning = line_timestamp_in_video + screenshot_offset
|
78
|
+
logger.info(f"Using 'beginning' setting for screenshot time: {screenshot_time_from_beginning} seconds from beginning of replay")
|
77
79
|
elif get_config().screenshot.screenshot_timing_setting == "middle":
|
78
80
|
if game_line.next:
|
79
|
-
logger.debug("Finding time between lines for screenshot")
|
80
81
|
screenshot_time_from_beginning = line_timestamp_in_video + ((game_line.next.time - game_line.time).total_seconds() / 2) + screenshot_offset
|
81
82
|
else:
|
82
|
-
logger.debug("Using end of line for screenshot")
|
83
83
|
screenshot_time_from_beginning = file_length - abs(screenshot_offset)
|
84
|
+
logger.info(f"Using 'middle' setting for screenshot time: {screenshot_time_from_beginning} seconds from beginning of replay")
|
84
85
|
elif get_config().screenshot.screenshot_timing_setting == "end":
|
85
|
-
logger.debug("Using end of line for screenshot")
|
86
86
|
if game_line.next:
|
87
|
-
logger.debug("Finding time between lines for screenshot")
|
88
87
|
screenshot_time_from_beginning = line_timestamp_in_video + (game_line.next.time - game_line.time).total_seconds() - screenshot_offset
|
89
88
|
else:
|
90
|
-
logger.debug("Using end of video for screenshot")
|
91
|
-
# If no next line, use the end of the video
|
92
89
|
screenshot_time_from_beginning = file_length - screenshot_offset
|
90
|
+
logger.info(f"Using 'end' setting for screenshot time: {screenshot_time_from_beginning} seconds from beginning of replay")
|
93
91
|
else:
|
94
92
|
logger.error(f"Invalid screenshot timing setting: {get_config().screenshot.screenshot_timing_setting}")
|
95
93
|
screenshot_time_from_beginning = line_timestamp_in_video + screenshot_offset
|
@@ -100,14 +98,9 @@ def get_screenshot_time(video_path, game_line, default_beginning=False, vad_begi
|
|
100
98
|
f"Calculated screenshot time ({screenshot_time_from_beginning:.2f}s) is out of bounds for video (length {file_length:.2f}s)."
|
101
99
|
)
|
102
100
|
if default_beginning:
|
103
|
-
logger.info("Defaulting to using the beginning of the video (1.0s)")
|
104
|
-
# Return time for the start of the video
|
105
101
|
return 1.0
|
106
|
-
logger.info(f"Defaulting to using the end of the video ({file_length:.2f}s)")
|
107
102
|
return file_length - screenshot_offset
|
108
103
|
|
109
|
-
logger.info("Screenshot time from beginning: " + str(screenshot_time_from_beginning))
|
110
|
-
|
111
104
|
# Return the calculated time from the beginning
|
112
105
|
return screenshot_time_from_beginning
|
113
106
|
|
@@ -178,10 +171,10 @@ def get_audio_and_trim(video_path, game_line, next_line_time, anki_card_creation
|
|
178
171
|
|
179
172
|
if codec == get_config().audio.extension:
|
180
173
|
codec_command = ['-c:a', 'copy']
|
181
|
-
logger.
|
174
|
+
logger.debug(f"Extracting {get_config().audio.extension} from video")
|
182
175
|
else:
|
183
176
|
codec_command = ["-c:a", f"{supported_formats[get_config().audio.extension]}"]
|
184
|
-
logger.
|
177
|
+
logger.debug(f"Re-encoding {codec} to {get_config().audio.extension}")
|
185
178
|
|
186
179
|
untrimmed_audio = tempfile.NamedTemporaryFile(dir=configuration.get_temporary_directory(),
|
187
180
|
suffix=f"_untrimmed.{get_config().audio.extension}").name
|
@@ -231,7 +224,7 @@ def trim_audio_based_on_last_line(untrimmed_audio, video_path, game_line, next_l
|
|
231
224
|
minutes, seconds = divmod(remainder, 60)
|
232
225
|
end_trim_time = "{:02}:{:02}:{:06.3f}".format(int(hours), int(minutes), seconds)
|
233
226
|
ffmpeg_command.extend(['-to', end_trim_time])
|
234
|
-
logger.
|
227
|
+
logger.debug(
|
235
228
|
f"Looks Like this is mining from History, or Multiple Lines were selected Trimming end of audio to {end_trim_time}")
|
236
229
|
|
237
230
|
ffmpeg_command.extend([
|
@@ -244,7 +237,7 @@ def trim_audio_based_on_last_line(untrimmed_audio, video_path, game_line, next_l
|
|
244
237
|
|
245
238
|
logger.info(f"{total_seconds_after_offset} trimmed off of beginning")
|
246
239
|
|
247
|
-
logger.
|
240
|
+
logger.debug(f"Audio trimmed and saved to {trimmed_audio}")
|
248
241
|
return trimmed_audio
|
249
242
|
|
250
243
|
def get_video_timings(video_path, game_line, anki_card_creation_time=None):
|
@@ -269,7 +262,7 @@ def get_video_timings(video_path, game_line, anki_card_creation_time=None):
|
|
269
262
|
|
270
263
|
|
271
264
|
def reencode_file_with_user_config(input_file, final_output_audio, user_ffmpeg_options):
|
272
|
-
logger.
|
265
|
+
logger.debug(f"Re-encode running with settings: {user_ffmpeg_options}")
|
273
266
|
temp_file = create_temp_file_with_same_name(input_file)
|
274
267
|
command = ffmpeg_base_command_list + [
|
275
268
|
"-i", input_file,
|
@@ -297,7 +290,6 @@ def replace_file_with_retry(temp_file, input_file, retries=5, delay=1):
|
|
297
290
|
for attempt in range(retries):
|
298
291
|
try:
|
299
292
|
shutil.move(temp_file, input_file)
|
300
|
-
logger.info(f'Re-encode Finished!')
|
301
293
|
return
|
302
294
|
except OSError as e:
|
303
295
|
if attempt < retries - 1:
|
GameSentenceMiner/gsm.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
import asyncio
|
2
2
|
|
3
|
+
from GameSentenceMiner.vad.result import VADResult
|
4
|
+
|
3
5
|
try:
|
4
6
|
import os.path
|
5
7
|
import signal
|
@@ -134,32 +136,38 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
134
136
|
|
135
137
|
if get_config().anki.sentence_audio_field and get_config().audio.enabled:
|
136
138
|
logger.debug("Attempting to get audio from video")
|
137
|
-
final_audio_output,
|
139
|
+
final_audio_output, vad_result, vad_trimmed_audio = VideoToAudioHandler.get_audio(
|
138
140
|
start_line,
|
139
141
|
line_cutoff,
|
140
142
|
video_path,
|
141
143
|
anki_card_creation_time)
|
142
144
|
else:
|
143
145
|
final_audio_output = ""
|
144
|
-
|
146
|
+
vad_result = VADResult(False, 0, 0)
|
145
147
|
vad_trimmed_audio = ""
|
146
|
-
vad_beginning = 0
|
147
|
-
vad_end = 0
|
148
148
|
if not get_config().audio.enabled:
|
149
149
|
logger.info("Audio is disabled in config, skipping audio processing!")
|
150
150
|
elif not get_config().anki.sentence_audio_field:
|
151
151
|
logger.info("No SentenceAudio Field in config, skipping audio processing!")
|
152
152
|
|
153
|
-
ss_timing = ffmpeg.get_screenshot_time(video_path, mined_line,
|
153
|
+
ss_timing = ffmpeg.get_screenshot_time(video_path, mined_line, vad_result=vad_result, doing_multi_line=bool(selected_lines))
|
154
|
+
if get_config().anki.previous_image_field and get_config().vad.do_vad_postprocessing:
|
155
|
+
prev_ss_timing = ffmpeg.get_screenshot_time(video_path, mined_line.prev,
|
156
|
+
vad_result=VideoToAudioHandler.get_audio(game_line=mined_line.prev,
|
157
|
+
next_line_time=mined_line.time,
|
158
|
+
video_path=video_path,
|
159
|
+
anki_card_creation_time=anki_card_creation_time,
|
160
|
+
timing_only=True) ,doing_multi_line=bool(selected_lines), previous_line=True)
|
154
161
|
|
155
162
|
if get_config().anki.update_anki and last_note:
|
156
163
|
anki.update_anki_card(last_note, note, audio_path=final_audio_output, video_path=video_path,
|
157
164
|
tango=tango,
|
158
|
-
should_update_audio=
|
165
|
+
should_update_audio=vad_result.success,
|
159
166
|
ss_time=ss_timing,
|
160
167
|
game_line=start_line,
|
161
|
-
selected_lines=selected_lines
|
162
|
-
|
168
|
+
selected_lines=selected_lines,
|
169
|
+
prev_ss_timing=prev_ss_timing)
|
170
|
+
elif get_config().features.notify_on_update and vad_result.success:
|
163
171
|
notification.send_audio_generated_notification(vad_trimmed_audio)
|
164
172
|
except Exception as e:
|
165
173
|
logger.error(f"Failed Processing and/or adding to Anki: Reason {e}")
|
@@ -173,7 +181,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
173
181
|
|
174
182
|
|
175
183
|
@staticmethod
|
176
|
-
def get_audio(game_line, next_line_time, video_path, anki_card_creation_time=None, temporary=False):
|
184
|
+
def get_audio(game_line, next_line_time, video_path, anki_card_creation_time=None, temporary=False, timing_only=False):
|
177
185
|
trimmed_audio = get_audio_and_trim(video_path, game_line, next_line_time, anki_card_creation_time)
|
178
186
|
if temporary:
|
179
187
|
return trimmed_audio
|
@@ -181,23 +189,23 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
181
189
|
f"{os.path.abspath(configuration.get_temporary_directory())}/{obs.get_current_game(sanitize=True)}.{get_config().audio.extension}")
|
182
190
|
final_audio_output = make_unique_file_name(os.path.join(get_config().paths.audio_destination,
|
183
191
|
f"{obs.get_current_game(sanitize=True)}.{get_config().audio.extension}"))
|
184
|
-
|
185
|
-
vad_beginning, vad_end = 0, 0
|
192
|
+
result = VADResult(False, 0, 0)
|
186
193
|
if get_config().vad.do_vad_postprocessing:
|
187
|
-
|
188
|
-
if not
|
189
|
-
|
194
|
+
result = do_vad_processing(get_config().vad.selected_vad_model, trimmed_audio, vad_trimmed_audio)
|
195
|
+
if not result.success:
|
196
|
+
result = do_vad_processing(get_config().vad.selected_vad_model, trimmed_audio,
|
190
197
|
vad_trimmed_audio)
|
191
|
-
if not
|
198
|
+
if not result.success and get_config().vad.add_audio_on_no_results:
|
192
199
|
logger.info("No voice activity detected, using full audio.")
|
193
200
|
vad_trimmed_audio = trimmed_audio
|
194
|
-
|
201
|
+
if timing_only:
|
202
|
+
return result
|
195
203
|
if get_config().audio.ffmpeg_reencode_options and os.path.exists(vad_trimmed_audio):
|
196
204
|
ffmpeg.reencode_file_with_user_config(vad_trimmed_audio, final_audio_output,
|
197
205
|
get_config().audio.ffmpeg_reencode_options)
|
198
206
|
elif os.path.exists(vad_trimmed_audio):
|
199
207
|
shutil.move(vad_trimmed_audio, final_audio_output)
|
200
|
-
return final_audio_output,
|
208
|
+
return final_audio_output, result, vad_trimmed_audio
|
201
209
|
|
202
210
|
|
203
211
|
def do_vad_processing(model, trimmed_audio, vad_trimmed_audio, second_pass=False):
|
@@ -207,7 +207,6 @@ rectangles = None
|
|
207
207
|
|
208
208
|
def do_second_ocr(ocr1_text, rectangle_index, time, img):
|
209
209
|
global twopassocr, ocr2, last_ocr1_results, last_ocr2_results
|
210
|
-
last_result = ([], -1)
|
211
210
|
try:
|
212
211
|
orig_text, text = run.process_and_write_results(img, None, None, None, None,
|
213
212
|
engine=ocr2)
|
@@ -218,6 +217,7 @@ def do_second_ocr(ocr1_text, rectangle_index, time, img):
|
|
218
217
|
img.save(os.path.join(get_temporary_directory(), "last_successful_ocr.png"))
|
219
218
|
last_ocr2_results[rectangle_index] = text
|
220
219
|
send_result(text, time)
|
220
|
+
img.close()
|
221
221
|
except json.JSONDecodeError:
|
222
222
|
print("Invalid JSON received.")
|
223
223
|
except Exception as e:
|
@@ -4,16 +4,26 @@ import argparse
|
|
4
4
|
import textwrap
|
5
5
|
import urllib.request
|
6
6
|
|
7
|
+
def str2bool(value):
|
8
|
+
if value.lower() == 'true':
|
9
|
+
return True
|
10
|
+
elif value.lower() == 'false':
|
11
|
+
return False
|
12
|
+
else:
|
13
|
+
raise argparse.ArgumentTypeError('Boolean value expected.')
|
14
|
+
|
7
15
|
parser = argparse.ArgumentParser(prog='owocr', description=textwrap.dedent('''\
|
8
16
|
Runs OCR in the background.
|
9
17
|
It can read images copied to the system clipboard or placed in a directory, images sent via a websocket or a Unix domain socket, or directly capture a screen (or a portion of it) or a window.
|
10
|
-
Recognized
|
18
|
+
Recognized text can be either saved to system clipboard, appended to a text file or sent via a websocket.
|
11
19
|
'''))
|
12
20
|
|
13
21
|
parser.add_argument('-r', '--read_from', type=str, default=argparse.SUPPRESS,
|
14
|
-
help='
|
22
|
+
help='Where to read input images from. Can be either "clipboard", "websocket", "unixsocket" (on macOS/Linux), "screencapture", or a path to a directory.')
|
23
|
+
parser.add_argument('-rs', '--read_from_secondary', type=str, default=argparse.SUPPRESS,
|
24
|
+
help="Optional secondary source to read input images from. Same options as read_from, but they can't both be directory paths.")
|
15
25
|
parser.add_argument('-w', '--write_to', type=str, default=argparse.SUPPRESS,
|
16
|
-
help='
|
26
|
+
help='Where to save recognized texts to. Can be either "clipboard", "websocket", or a path to a text file.')
|
17
27
|
parser.add_argument('-e', '--engine', type=str, default=argparse.SUPPRESS,
|
18
28
|
help='OCR engine to use. Available: "mangaocr", "glens", "glensweb", "bing", "gvision", "avision", "alivetext", "azure", "winrtocr", "oneocr", "easyocr", "rapidocr", "ocrspace".')
|
19
29
|
parser.add_argument('-p', '--pause_at_startup', action='store_true', default=argparse.SUPPRESS,
|
@@ -23,21 +33,21 @@ parser.add_argument('-i', '--ignore_flag', action='store_true', default=argparse
|
|
23
33
|
parser.add_argument('-d', '--delete_images', action='store_true', default=argparse.SUPPRESS,
|
24
34
|
help='Delete image files after processing when reading from a directory.')
|
25
35
|
parser.add_argument('-n', '--notifications', action='store_true', default=argparse.SUPPRESS,
|
26
|
-
help='Show an operating system notification with the detected text.')
|
36
|
+
help='Show an operating system notification with the detected text. Will be ignored when reading with screen capture, unless screen_capture_combo is set.')
|
27
37
|
parser.add_argument('-a', '--auto_pause', type=float, default=argparse.SUPPRESS,
|
28
|
-
help='Automatically pause the program after the specified amount of seconds since the last successful text recognition. Will be ignored when reading with screen capture. 0 to disable.')
|
38
|
+
help='Automatically pause the program after the specified amount of seconds since the last successful text recognition. Will be ignored when reading with screen capture, unless screen_capture_combo is set. 0 to disable.')
|
29
39
|
parser.add_argument('-cp', '--combo_pause', type=str, default=argparse.SUPPRESS,
|
30
|
-
help='
|
40
|
+
help='Combo to wait on for pausing the program. As an example: "<ctrl>+<shift>+p". The list of keys can be found here: https://pynput.readthedocs.io/en/latest/keyboard.html#pynput.keyboard.Key')
|
31
41
|
parser.add_argument('-cs', '--combo_engine_switch', type=str, default=argparse.SUPPRESS,
|
32
|
-
help='
|
42
|
+
help='Combo to wait on for switching the OCR engine. As an example: "<ctrl>+<shift>+a". To be used with combo_pause. The list of keys can be found here: https://pynput.readthedocs.io/en/latest/keyboard.html#pynput.keyboard.Key')
|
33
43
|
parser.add_argument('-sa', '--screen_capture_area', type=str, default=argparse.SUPPRESS,
|
34
|
-
help='
|
44
|
+
help='Area to target when reading with screen capture. Can be either empty (automatic selector), a set of coordinates (x,y,width,height), "screen_N" (captures a whole screen, where N is the screen number starting from 1) or a window name (the first matching window title will be used).')
|
35
45
|
parser.add_argument('-sd', '--screen_capture_delay_secs', type=float, default=argparse.SUPPRESS,
|
36
|
-
help='
|
37
|
-
parser.add_argument('-sw', '--screen_capture_only_active_windows',
|
38
|
-
help="When reading with screen capture and screen_capture_area is a window name,
|
46
|
+
help='Delay (in seconds) between screenshots when reading with screen capture.')
|
47
|
+
parser.add_argument('-sw', '--screen_capture_only_active_windows', type=str2bool, default=argparse.SUPPRESS,
|
48
|
+
help="When reading with screen capture and screen_capture_area is a window name, only target the window while it's active.")
|
39
49
|
parser.add_argument('-sc', '--screen_capture_combo', type=str, default=argparse.SUPPRESS,
|
40
|
-
help='When reading with screen capture,
|
50
|
+
help='When reading with screen capture, combo to wait on for taking a screenshot instead of using the delay. As an example: "<ctrl>+<shift>+s". The list of keys can be found here: https://pynput.readthedocs.io/en/latest/keyboard.html#pynput.keyboard.Key')
|
41
51
|
|
42
52
|
class Config:
|
43
53
|
has_config = False
|
@@ -47,6 +57,7 @@ class Config:
|
|
47
57
|
__engine_config = {}
|
48
58
|
__default_config = {
|
49
59
|
'read_from': 'clipboard',
|
60
|
+
'read_from_secondary': '',
|
50
61
|
'write_to': 'clipboard',
|
51
62
|
'engine': '',
|
52
63
|
'pause_at_startup': False,
|
@@ -64,7 +75,8 @@ class Config:
|
|
64
75
|
'screen_capture_area': '',
|
65
76
|
'screen_capture_delay_secs': 3,
|
66
77
|
'screen_capture_only_active_windows': True,
|
67
|
-
'screen_capture_combo': ''
|
78
|
+
'screen_capture_combo': '',
|
79
|
+
'screen_capture_old_macos_api': False
|
68
80
|
}
|
69
81
|
|
70
82
|
def __parse(self, value):
|