Implement combos to switch/pause from other windows, update version
This commit is contained in:
@@ -22,6 +22,8 @@ class Config:
|
|||||||
'delay_secs': 0.5,
|
'delay_secs': 0.5,
|
||||||
'websocket_port': 7331,
|
'websocket_port': 7331,
|
||||||
'notifications': False,
|
'notifications': False,
|
||||||
|
'combo_pause': '',
|
||||||
|
'combo_engine_switch': '',
|
||||||
'screen_capture_monitor': 1,
|
'screen_capture_monitor': 1,
|
||||||
'screen_capture_coords': '',
|
'screen_capture_coords': '',
|
||||||
'screen_capture_delay_secs': 3,
|
'screen_capture_delay_secs': 3,
|
||||||
|
|||||||
91
owocr/run.py
91
owocr/run.py
@@ -158,23 +158,24 @@ class TextFiltering:
|
|||||||
return text, orig_text
|
return text, orig_text
|
||||||
|
|
||||||
|
|
||||||
def user_input_thread_run(engine_instances, engine_keys):
|
def pause_handler(is_combo=True):
|
||||||
def _terminate_handler(user_input):
|
|
||||||
global terminated
|
|
||||||
logger.info('Terminated!')
|
|
||||||
terminated = True
|
|
||||||
|
|
||||||
def _pause_handler(user_input):
|
|
||||||
global paused
|
global paused
|
||||||
global just_unpaused
|
global just_unpaused
|
||||||
if paused:
|
if paused:
|
||||||
logger.info('Unpaused!')
|
message = 'Unpaused!'
|
||||||
just_unpaused = True
|
just_unpaused = True
|
||||||
else:
|
else:
|
||||||
logger.info('Paused!')
|
message = 'Paused!'
|
||||||
|
|
||||||
|
if is_combo:
|
||||||
|
notification.title = message
|
||||||
|
notification.message = ''
|
||||||
|
notification.send(block=False)
|
||||||
|
logger.info(message)
|
||||||
paused = not paused
|
paused = not paused
|
||||||
|
|
||||||
def _engine_change_handler(user_input):
|
|
||||||
|
def engine_change_handler(user_input='s', is_combo=True):
|
||||||
global engine_index
|
global engine_index
|
||||||
old_engine_index = engine_index
|
old_engine_index = engine_index
|
||||||
|
|
||||||
@@ -187,8 +188,20 @@ def user_input_thread_run(engine_instances, engine_keys):
|
|||||||
engine_index = engine_keys.index(user_input.lower())
|
engine_index = engine_keys.index(user_input.lower())
|
||||||
|
|
||||||
if engine_index != old_engine_index:
|
if engine_index != old_engine_index:
|
||||||
|
new_engine_name = engine_instances[engine_index].readable_name
|
||||||
|
if is_combo:
|
||||||
|
notification.title = f'Switched to {new_engine_name}'
|
||||||
|
notification.message = ''
|
||||||
|
notification.send(block=False)
|
||||||
engine_color = config.get_general('engine_color')
|
engine_color = config.get_general('engine_color')
|
||||||
logger.opt(ansi=True).info(f'Switched to <{engine_color}>{engine_instances[engine_index].readable_name}</{engine_color}>!')
|
logger.opt(ansi=True).info(f'Switched to <{engine_color}>{new_engine_name}</{engine_color}>!')
|
||||||
|
|
||||||
|
|
||||||
|
def user_input_thread_run():
|
||||||
|
def _terminate_handler():
|
||||||
|
global terminated
|
||||||
|
logger.info('Terminated!')
|
||||||
|
terminated = True
|
||||||
|
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
import msvcrt
|
import msvcrt
|
||||||
@@ -197,11 +210,11 @@ def user_input_thread_run(engine_instances, engine_keys):
|
|||||||
try:
|
try:
|
||||||
user_input = user_input_bytes.decode()
|
user_input = user_input_bytes.decode()
|
||||||
if user_input.lower() in 'tq':
|
if user_input.lower() in 'tq':
|
||||||
_terminate_handler(user_input)
|
_terminate_handler()
|
||||||
elif user_input.lower() == 'p':
|
elif user_input.lower() == 'p':
|
||||||
_pause_handler(user_input)
|
pause_handler(False)
|
||||||
else:
|
else:
|
||||||
_engine_change_handler(user_input)
|
engine_change_handler(user_input, False)
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@@ -213,11 +226,11 @@ def user_input_thread_run(engine_instances, engine_keys):
|
|||||||
while not terminated:
|
while not terminated:
|
||||||
user_input = sys.stdin.read(1)
|
user_input = sys.stdin.read(1)
|
||||||
if user_input.lower() in 'tq':
|
if user_input.lower() in 'tq':
|
||||||
_terminate_handler(user_input)
|
_terminate_handler()
|
||||||
elif user_input.lower() == 'p':
|
elif user_input.lower() == 'p':
|
||||||
_pause_handler(user_input)
|
pause_handler(False)
|
||||||
else:
|
else:
|
||||||
_engine_change_handler(user_input)
|
engine_change_handler(user_input, False)
|
||||||
finally:
|
finally:
|
||||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||||
|
|
||||||
@@ -290,7 +303,8 @@ def are_images_identical(img1, img2):
|
|||||||
return (img1.shape == img2.shape) and (img1 == img2).all()
|
return (img1.shape == img2.shape) and (img1 == img2).all()
|
||||||
|
|
||||||
|
|
||||||
def process_and_write_results(engine_instance, img_or_path, write_to, enable_filtering, last_text, filtering):
|
def process_and_write_results(img_or_path, write_to, notifications, enable_filtering, last_text, filtering):
|
||||||
|
engine_instance = engine_instances[engine_index]
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
res, text = engine_instance(img_or_path)
|
res, text = engine_instance(img_or_path)
|
||||||
t1 = time.time()
|
t1 = time.time()
|
||||||
@@ -302,9 +316,7 @@ def process_and_write_results(engine_instance, img_or_path, write_to, enable_fil
|
|||||||
text, orig_text = filtering(text, last_text)
|
text, orig_text = filtering(text, last_text)
|
||||||
text = post_process(text)
|
text = post_process(text)
|
||||||
logger.opt(ansi=True).info(f'Text recognized in {t1 - t0:0.03f}s using <{engine_color}>{engine_instance.readable_name}</{engine_color}>: {text}')
|
logger.opt(ansi=True).info(f'Text recognized in {t1 - t0:0.03f}s using <{engine_color}>{engine_instance.readable_name}</{engine_color}>: {text}')
|
||||||
if config.get_general('notifications'):
|
if notifications:
|
||||||
notification = Notify()
|
|
||||||
notification.application_name = 'owocr'
|
|
||||||
notification.title = 'Text recognized:'
|
notification.title = 'Text recognized:'
|
||||||
notification.message = text
|
notification.message = text
|
||||||
notification.send(block=False)
|
notification.send(block=False)
|
||||||
@@ -342,6 +354,8 @@ def run(read_from=None,
|
|||||||
ignore_flag=None,
|
ignore_flag=None,
|
||||||
delete_images=None,
|
delete_images=None,
|
||||||
notifications=None,
|
notifications=None,
|
||||||
|
combo_pause=None,
|
||||||
|
combo_engine_switch=None,
|
||||||
screen_capture_monitor=None,
|
screen_capture_monitor=None,
|
||||||
screen_capture_coords=None,
|
screen_capture_coords=None,
|
||||||
screen_capture_delay_secs=None,
|
screen_capture_delay_secs=None,
|
||||||
@@ -362,6 +376,8 @@ def run(read_from=None,
|
|||||||
:param ignore_flag: Process flagged clipboard images (images that are copied to the clipboard with the *ocr_ignore* string).
|
:param ignore_flag: Process flagged clipboard images (images that are copied to the clipboard with the *ocr_ignore* string).
|
||||||
:param delete_images: Delete image files after processing when reading from a directory.
|
:param delete_images: Delete image files after processing when reading from a directory.
|
||||||
:param notifications: Show an operating system notification with the detected text.
|
:param notifications: Show an operating system notification with the detected text.
|
||||||
|
:param combo_pause: Specifies a combo to wait on for pausing the program. As an example: "<ctrl>+<shift>+p". To be used with combo_engine_switch. The list of keys can be found here: https://pynput.readthedocs.io/en/latest/keyboard.html#pynput.keyboard.Key
|
||||||
|
:param combo_engine_switch: 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
|
||||||
:param screen_capture_monitor: Specifies monitor to target when reading with screen capture.
|
:param screen_capture_monitor: Specifies monitor to target when reading with screen capture.
|
||||||
:param screen_capture_coords: Specifies area to target when reading with screen capture. Can be either empty (whole screen), a set of coordinates (x,y,width,height) or a window name (the first matching window title will be used).
|
:param screen_capture_coords: Specifies area to target when reading with screen capture. Can be either empty (whole screen), a set of coordinates (x,y,width,height) or a window name (the first matching window title will be used).
|
||||||
:param screen_capture_delay_secs: Specifies the delay (in seconds) between screenshots when reading with screen capture.
|
:param screen_capture_delay_secs: Specifies the delay (in seconds) between screenshots when reading with screen capture.
|
||||||
@@ -381,6 +397,8 @@ def run(read_from=None,
|
|||||||
if config.downloaded_config:
|
if config.downloaded_config:
|
||||||
logger.info(f'A default config file has been downloaded to {config.config_path}')
|
logger.info(f'A default config file has been downloaded to {config.config_path}')
|
||||||
|
|
||||||
|
global engine_instances
|
||||||
|
global engine_keys
|
||||||
engine_instances = []
|
engine_instances = []
|
||||||
config_engines = []
|
config_engines = []
|
||||||
engine_keys = []
|
engine_keys = []
|
||||||
@@ -413,6 +431,7 @@ def run(read_from=None,
|
|||||||
global tmp_paused
|
global tmp_paused
|
||||||
global just_unpaused
|
global just_unpaused
|
||||||
global first_pressed
|
global first_pressed
|
||||||
|
global notification
|
||||||
terminated = False
|
terminated = False
|
||||||
paused = pause_at_startup
|
paused = pause_at_startup
|
||||||
just_unpaused = True
|
just_unpaused = True
|
||||||
@@ -422,8 +441,10 @@ def run(read_from=None,
|
|||||||
engine_color = config.get_general('engine_color')
|
engine_color = config.get_general('engine_color')
|
||||||
delay_secs = config.get_general('delay_secs')
|
delay_secs = config.get_general('delay_secs')
|
||||||
screen_capture_on_combo = False
|
screen_capture_on_combo = False
|
||||||
|
notification = Notify()
|
||||||
|
notification.application_name = 'owocr'
|
||||||
|
|
||||||
user_input_thread = threading.Thread(target=user_input_thread_run, args=(engine_instances, engine_keys), daemon=True)
|
user_input_thread = threading.Thread(target=user_input_thread_run, daemon=True)
|
||||||
user_input_thread.start()
|
user_input_thread.start()
|
||||||
|
|
||||||
if read_from == 'websocket' or write_to == 'websocket':
|
if read_from == 'websocket' or write_to == 'websocket':
|
||||||
@@ -528,14 +549,22 @@ def run(read_from=None,
|
|||||||
|
|
||||||
logger.opt(ansi=True).info(f"Reading from directory {read_from} using <{engine_color}>{engine_instances[engine_index].readable_name}</{engine_color}>{' (paused)' if paused else ''}")
|
logger.opt(ansi=True).info(f"Reading from directory {read_from} using <{engine_color}>{engine_instances[engine_index].readable_name}</{engine_color}>{' (paused)' if paused else ''}")
|
||||||
|
|
||||||
|
key_combos = {}
|
||||||
if screen_capture_on_combo:
|
if screen_capture_on_combo:
|
||||||
tmp_paused_listener = keyboard.GlobalHotKeys({
|
key_combos[screen_capture_combo] = on_screenshot_combo
|
||||||
screen_capture_combo: on_screenshot_combo})
|
if any(x != '' for x in [combo_pause, combo_engine_switch]):
|
||||||
|
if any(x == '' for x in [combo_pause, combo_engine_switch]):
|
||||||
|
raise ValueError('both combo_pause and combo_engine_switch must be specified')
|
||||||
|
key_combos[combo_pause] = pause_handler
|
||||||
|
key_combos[combo_engine_switch] = engine_change_handler
|
||||||
|
|
||||||
|
if len(key_combos) > 0:
|
||||||
|
key_combo_listener = keyboard.GlobalHotKeys(key_combos)
|
||||||
else:
|
else:
|
||||||
tmp_paused_listener = keyboard.Listener(
|
key_combo_listener = keyboard.Listener(
|
||||||
on_press=on_key_press,
|
on_press=on_key_press,
|
||||||
on_release=on_key_release)
|
on_release=on_key_release)
|
||||||
tmp_paused_listener.start()
|
key_combo_listener.start()
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
while not terminated:
|
while not terminated:
|
||||||
@@ -548,7 +577,7 @@ def run(read_from=None,
|
|||||||
else:
|
else:
|
||||||
if not paused and not tmp_paused:
|
if not paused and not tmp_paused:
|
||||||
img = Image.open(io.BytesIO(item))
|
img = Image.open(io.BytesIO(item))
|
||||||
process_and_write_results(engine_instances[engine_index], img, write_to, False, '', None)
|
process_and_write_results(img, write_to, notifications, False, '', None)
|
||||||
elif read_from == 'clipboard':
|
elif read_from == 'clipboard':
|
||||||
process_clipboard = False
|
process_clipboard = False
|
||||||
if windows_clipboard_polling:
|
if windows_clipboard_polling:
|
||||||
@@ -594,7 +623,7 @@ def run(read_from=None,
|
|||||||
process_clipboard = True
|
process_clipboard = True
|
||||||
|
|
||||||
if process_clipboard:
|
if process_clipboard:
|
||||||
process_and_write_results(engine_instances[engine_index], img, write_to, False, '', None)
|
process_and_write_results(img, write_to, notifications, False, '', None)
|
||||||
|
|
||||||
just_unpaused = False
|
just_unpaused = False
|
||||||
|
|
||||||
@@ -611,7 +640,7 @@ def run(read_from=None,
|
|||||||
if take_screenshot and screencapture_window_visible:
|
if take_screenshot and screencapture_window_visible:
|
||||||
sct_img = sct.grab(sct_params)
|
sct_img = sct.grab(sct_params)
|
||||||
img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
|
img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
|
||||||
res = process_and_write_results(engine_instances[engine_index], img, write_to, True, last_text, filtering)
|
res = process_and_write_results(img, write_to, notifications, True, last_text, filtering)
|
||||||
if res != '':
|
if res != '':
|
||||||
last_text = res
|
last_text = res
|
||||||
delay = screen_capture_delay_secs
|
delay = screen_capture_delay_secs
|
||||||
@@ -634,7 +663,7 @@ def run(read_from=None,
|
|||||||
except (UnidentifiedImageError, OSError) as e:
|
except (UnidentifiedImageError, OSError) as e:
|
||||||
logger.warning(f'Error while reading file {path}: {e}')
|
logger.warning(f'Error while reading file {path}: {e}')
|
||||||
else:
|
else:
|
||||||
process_and_write_results(engine_instances[engine_index], img, write_to, False, '', None)
|
process_and_write_results(img, write_to, notifications, False, '', None)
|
||||||
img.close()
|
img.close()
|
||||||
if delete_images:
|
if delete_images:
|
||||||
Path.unlink(path)
|
Path.unlink(path)
|
||||||
@@ -649,4 +678,4 @@ def run(read_from=None,
|
|||||||
windows_clipboard_thread.join()
|
windows_clipboard_thread.join()
|
||||||
elif read_from == 'screencapture' and screencapture_window_mode:
|
elif read_from == 'screencapture' and screencapture_window_mode:
|
||||||
target_window.watchdog.stop()
|
target_window.watchdog.stop()
|
||||||
tmp_paused_listener.stop()
|
key_combo_listener.stop()
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
;notifications = False
|
;notifications = False
|
||||||
;ignore_flag = False
|
;ignore_flag = False
|
||||||
;delete_images = False
|
;delete_images = False
|
||||||
|
;note: this specifies a combo to wait on for pausing the program. As an example: <ctrl>+<shift>+p. To be used with combo_engine_switch. The list of keys can be found here: https://pynput.readthedocs.io/en/latest/keyboard.html#pynput.keyboard.Key
|
||||||
|
;combo_pause = <ctrl>+<shift>+p
|
||||||
|
;note: this 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
|
||||||
|
;combo_engine_switch = <ctrl>+<shift>+a
|
||||||
;screen_capture_monitor = 2
|
;screen_capture_monitor = 2
|
||||||
;note: screen_capture_coords can be empty (whole screen), have a set of coordinates (x,y,width,height) or a window name (the first matching window title will be used)
|
;note: screen_capture_coords can be empty (whole screen), have a set of coordinates (x,y,width,height) or a window name (the first matching window title will be used)
|
||||||
;screen_capture_coords =
|
;screen_capture_coords =
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -5,7 +5,7 @@ long_description = (Path(__file__).parent / "README.md").read_text('utf-8')
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="owocr",
|
name="owocr",
|
||||||
version='1.4',
|
version='1.5',
|
||||||
description="Japanese OCR",
|
description="Japanese OCR",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
|
|||||||
Reference in New Issue
Block a user