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.
@@ -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.info(f"GeminiAIManager initialized with model: {model_name}")
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.info(f"GroqAIManager initialized with model: {self.model_name}")
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.info("Error caught while trying to get AI prompt result. Check logs for more details.")
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 GeminiAI, get_ai_prompt_result
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
- prev_screenshot = ffmpeg.get_screenshot(video_path, ffmpeg.get_screenshot_time(video_path, selected_lines[0].prev if selected_lines else game_line.prev))
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: {fileabspath} in external Program: {get_config().audio.external_tool}")
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.info(f"last mined line: {util.get_last_mined_line()}, current sentence: {get_sentence(last_card)}")
275
- logger.info(f"use previous audio: {use_prev_audio}")
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:
@@ -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.info(f"Screenshot saved to: {output_image}")
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, vad_beginning=None, vad_end=None, doing_multi_line=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
- logger.info("Calculating screenshot time for line: " + str(game_line.text))
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 vad_beginning and vad_end and not doing_multi_line:
72
- logger.debug("Using VAD to determine screenshot time")
73
- screenshot_time_from_beginning = line_timestamp_in_video + vad_end - 0.1
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.info(f"Extracting {get_config().audio.extension} from video")
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.info(f"Re-encoding {codec} to {get_config().audio.extension}")
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.info(
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.info(f"Audio trimmed and saved to {trimmed_audio}")
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.info(f"Re-encode running with settings: {user_ffmpeg_options}")
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, should_update_audio, vad_trimmed_audio, vad_beginning, vad_end = VideoToAudioHandler.get_audio(
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
- should_update_audio = False
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, vad_beginning=vad_beginning, vad_end=vad_end, doing_multi_line=bool(selected_lines))
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=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
- elif get_config().features.notify_on_update and should_update_audio:
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
- should_update_audio = True
185
- vad_beginning, vad_end = 0, 0
192
+ result = VADResult(False, 0, 0)
186
193
  if get_config().vad.do_vad_postprocessing:
187
- should_update_audio, vad_beginning, vad_end = do_vad_processing(get_config().vad.selected_vad_model, trimmed_audio, vad_trimmed_audio)
188
- if not should_update_audio:
189
- should_update_audio, vad_beginning, vad_end = do_vad_processing(get_config().vad.selected_vad_model, trimmed_audio,
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 should_update_audio and get_config().vad.add_audio_on_no_results:
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
- should_update_audio = True
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, should_update_audio, vad_trimmed_audio, vad_beginning, vad_end
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 texts can be either saved to system clipboard, appended to a text file or sent via a websocket.
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='Specifies where to read input images from. Can be either "clipboard", "websocket", "unixsocket" (on macOS/Linux), "screencapture", or a path to a directory.')
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='Specifies where to save recognized texts to. Can be either "clipboard", "websocket", or a path to a text file.')
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='Specifies a 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')
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='Specifies a 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')
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='Specifies 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).')
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='Specifies the delay (in seconds) between screenshots when reading with screen capture.')
37
- parser.add_argument('-sw', '--screen_capture_only_active_windows', action='store_true', default=argparse.SUPPRESS,
38
- help="When reading with screen capture and screen_capture_area is a window name, specifies whether to only target the window while it's active.")
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, specifies a 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')
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):