From a80ab31f911c87ca500b566163e23d6776192d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 18 Sep 2023 00:42:31 +0200 Subject: [PATCH 01/10] ocr: only force tesseract path on Windows and macOS On Linux and other *nix, tesseract is available in the PATH (and rarely in /usr/local). --- ocr.py | 3 ++- tools.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ocr.py b/ocr.py index cb696e4..ef3e350 100644 --- a/ocr.py +++ b/ocr.py @@ -48,4 +48,5 @@ def tesseract_ocr(image, text_orientation): return result tesseract_cmd, platform_name = path_to_tesseract() -pytesseract.pytesseract.tesseract_cmd = tesseract_cmd \ No newline at end of file +if tesseract_cmd is not None: + pytesseract.pytesseract.tesseract_cmd = tesseract_cmd diff --git a/tools.py b/tools.py index c3b1547..856c2be 100644 --- a/tools.py +++ b/tools.py @@ -30,9 +30,9 @@ def path_to_ffmpeg_folder(): def path_to_tesseract(): exec_data = {"Windows": str(Path(WIN_TESSERACT_DIR, "tesseract.exe")), "Darwin": str(Path(OSX_TESSERACT_DIR, "bin", "tesseract")), - "Linux": "/usr/local/bin/tesseract"} + } platform_name = platform.system() # E.g. 'Windows' - return exec_data[platform_name], platform_name + return exec_data.get(platform_name), platform_name def get_tessdata_dir(): platform_name = platform.system() From 893c5e3703c9dc4ecf1fc42cc6edfb2ba75ea782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 18 Sep 2023 00:43:53 +0200 Subject: [PATCH 02/10] tools: remove unused platform_name from return value --- ocr.py | 2 +- tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ocr.py b/ocr.py index ef3e350..a7ca2ac 100644 --- a/ocr.py +++ b/ocr.py @@ -47,6 +47,6 @@ def tesseract_ocr(image, text_orientation): result = pytesseract.image_to_string(image, config=custom_config, lang=language) return result -tesseract_cmd, platform_name = path_to_tesseract() +tesseract_cmd = path_to_tesseract() if tesseract_cmd is not None: pytesseract.pytesseract.tesseract_cmd = tesseract_cmd diff --git a/tools.py b/tools.py index 856c2be..1d54f90 100644 --- a/tools.py +++ b/tools.py @@ -32,7 +32,7 @@ def path_to_tesseract(): "Darwin": str(Path(OSX_TESSERACT_DIR, "bin", "tesseract")), } platform_name = platform.system() # E.g. 'Windows' - return exec_data.get(platform_name), platform_name + return exec_data.get(platform_name) def get_tessdata_dir(): platform_name = platform.system() From 44efc6bb65c84dff4c1a0753e63f9cdd3ef54560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 18 Sep 2023 00:44:35 +0200 Subject: [PATCH 03/10] ankiconnect: fix incorrect error message --- ankiconnect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ankiconnect.py b/ankiconnect.py index c8e4b63..a93d8fc 100644 --- a/ankiconnect.py +++ b/ankiconnect.py @@ -23,7 +23,7 @@ def invoke(action, params): if len(response) != 2: return 'Error: Response has an unexpected number of fields' if 'error' not in response: - return 'Error: Response has an unexpected number of fields' + return 'Error: Response is missing required error field' if 'result' not in response: return 'Error: Response is missing required result field' if response['error'] is not None: From 8b5da96af456308e079dc56f6c5854a78594463d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 18 Sep 2023 00:51:35 +0200 Subject: [PATCH 04/10] audio: only force FFmpeg path on Windows and macOS Altering the defaults only makes sense when there is a custom path specified. --- audio.py | 9 ++++++--- tools.py | 5 +---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/audio.py b/audio.py index 7730d19..aeb26b2 100644 --- a/audio.py +++ b/audio.py @@ -4,13 +4,16 @@ import wave import os import platform +from pathlib import Path from shutil import copyfile import eel from config import r_config, LOG_CONFIG -from tools import path_to_ffmpeg, path_to_ffmpeg_folder +from tools import path_to_ffmpeg -AudioSegment.ffmpeg = path_to_ffmpeg() -os.environ["PATH"] += os.pathsep + path_to_ffmpeg_folder() +ffmpeg_path = path_to_ffmpeg() +if ffmpeg_path is not None: + AudioSegment.ffmpeg = ffmpeg_path + os.environ["PATH"] += os.pathsep + str(Path(ffmpeg_path).parent) # User config device exists? use config device, if not check if (1) valid, use (1), if not no audio @eel.expose diff --git a/tools.py b/tools.py index 1d54f90..b280d93 100644 --- a/tools.py +++ b/tools.py @@ -22,10 +22,7 @@ def path_to_ffmpeg(): return str(Path(bundle_dir, "resources", "bin", "win", "ffmpeg", "ffmpeg.exe")) elif platform_name == 'Darwin': return str(Path(bundle_dir, "resources", "bin", "mac", "ffmpeg", "ffmpeg")) - return '' - -def path_to_ffmpeg_folder(): - return str(Path(path_to_ffmpeg()).parent) + return None def path_to_tesseract(): exec_data = {"Windows": str(Path(WIN_TESSERACT_DIR, "tesseract.exe")), From 7735fcc50f8994cd7d1fb02a3184ab97b122f92f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 18 Sep 2023 00:58:18 +0200 Subject: [PATCH 05/10] remove a few unused imports --- audio.py | 2 -- game2text.py | 2 +- recordaudio.py | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/audio.py b/audio.py index aeb26b2..1919daa 100644 --- a/audio.py +++ b/audio.py @@ -1,11 +1,9 @@ from pydub import AudioSegment from pydub.playback import play import pyaudio -import wave import os import platform from pathlib import Path -from shutil import copyfile import eel from config import r_config, LOG_CONFIG from tools import path_to_ffmpeg diff --git a/game2text.py b/game2text.py index 841f11a..00c5fce 100644 --- a/game2text.py +++ b/game2text.py @@ -4,7 +4,7 @@ from ocr import detect_and_log from translate import multi_translate from hotkeys import hotkey_map -from util import RepeatedTimer, create_directory_if_not_exists, get_default_browser_name, get_PID_list, format_output, remove_duplicate_characters, remove_spaces +from util import RepeatedTimer, create_directory_if_not_exists, get_default_browser_name, get_PID_list, format_output from textractor import Textractor from tools import path_to_textractor, open_folder_textractor_path from audio import get_recommended_device_index diff --git a/recordaudio.py b/recordaudio.py index 09eee4b..9ffb8dc 100644 --- a/recordaudio.py +++ b/recordaudio.py @@ -4,7 +4,6 @@ import os import platform from audio import valid_output_device, convert_audio -from config import r_config, LOG_CONFIG class RecordThread(threading.Thread): def __init__(self, deviceIndex=-1, frames=512): From 2e7506a5376521b5fa1c2fdc941e45cb01f4133d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Sun, 17 Sep 2023 21:24:59 +0200 Subject: [PATCH 06/10] Bump all requirements --- requirements.txt | 123 +++++++++++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 51 deletions(-) diff --git a/requirements.txt b/requirements.txt index b748bb9..0a5b848 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,57 +1,78 @@ -altgraph==0.17 +altgraph==0.17.3 +anyio==4.0.0 appdirs==1.4.4 -bottle==0.12.19 +bottle==0.12.25 bottle-websocket==0.2.9 -cachetools==4.1.1 -certifi==2020.11.8 -cffi==1.14.4 -chardet==3.0.4 -click==7.1.2 -distlib==0.3.1 -Eel==0.15.3 -filelock==3.0.12 -future==0.18.2 +cachetools==5.3.1 +certifi==2023.7.22 +cffi==1.15.1 +chardet==5.2.0 +charset-normalizer==3.2.0 +click==8.1.7 +cryptography==41.0.3 +Cython==3.0.2 +dartsclone==0.10.2 +dill==0.3.7 +distlib==0.3.7 +Eel==0.16.0 +evdev==1.6.1 +filelock==3.12.4 +future==0.18.3 fuzzywuzzy==0.18.0 -h11==0.9.0 -h2==3.2.0 -hpack==3.0.0 -hstspreload==2020.11.21 -httpcore==0.9.1 -httpx==0.13.3 -hyperframe==5.2.0 -idna==2.10 -itsdangerous==1.1.0 -Jinja2==2.11.2 -MarkupSafe==1.1.1 -numpy==1.19.3 -opencv-python==4.4.0.46 -parse==1.19.0 -pefile==2019.4.18 -Pillow==8.0.1 -psutil==5.8.0 -PyAudio==0.2.11 -pycparser==2.20 +gevent==23.9.1 +gevent-websocket==0.10.1 +greenlet==3.0.0rc3 +h11==0.14.0 +h2==4.1.0 +hpack==4.0.0 +hstspreload==2023.1.1 +httpcore==0.18.0 +httpx==0.25.0 +hyperframe==6.0.1 +idna==3.4 +itsdangerous==2.1.2 +Jinja2==3.1.2 +Levenshtein==0.21.1 +lxml==4.9.3 +MarkupSafe==2.1.3 +multiprocess==0.70.15 +numpy==1.26.0 +opencv-python==4.8.0.76 +packaging==23.1 +parse==1.19.1 +pathos==0.3.1 +pefile==2023.2.7 +Pillow==10.0.1 +pox==0.3.3 +ppft==1.7.6.7 +psutil==5.9.5 +PyAudio==0.2.13 +pycparser==2.21 pydub==0.25.1 -pyinstaller==4.1 -pyinstaller-hooks-contrib==2020.10 -pynput==1.6.8 -pyparsing==2.4.7 -pyperclip==1.8.1 -pytesseract==0.3.6 -python-xlib==0.29 -python-Levenshtein==0.12.2 -pywin32-ctypes==0.2.0 -PyYAML==5.4.1 -requests==2.25.0 -rfc3986==1.4.0 -six==1.15.0 -SudachiDict-small==20201223.post1 -SudachiPy==0.5.2 -sniffio==1.2.0 +PyExecJS==1.5.1 +pyinstaller==5.13.2 +pyinstaller-hooks-contrib==2023.8 +pynput==1.7.6 +pyparsing==3.1.1 +pyperclip==1.8.2 +pytesseract==0.3.10 +python-Levenshtein==0.21.1 +python-xlib==0.33 +pywin32-ctypes==0.2.2 +PyYAML==6.0.1 +rapidfuzz==3.3.0 +requests==2.31.0 +rfc3986==2.0.0 +six==1.16.0 +sniffio==1.3.0 +sortedcontainers==2.4.0 +SudachiDict-small==20230711 +SudachiPy==0.6.7 tk==0.1.0 -translators==5.7.1 -urllib3==1.26.2 -Werkzeug==1.0.1 +tqdm==4.66.1 +translators==5.8.3 +urllib3==2.0.4 +Werkzeug==2.3.7 whichcraft==0.6.1 -zope.event==4.5.0 -zope.interface==5.2.0 +zope.event==5.0 +zope.interface==6.0 From 4be32f8473809a248987e19dbabe8c2177be8bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Sun, 17 Sep 2023 21:24:22 +0200 Subject: [PATCH 07/10] Add pyproject.toml This helps with the configuration of reformatting tools such as black and isort, as well as helping the popular pyright static analyzer discover the environemnt and source code. --- pyproject.toml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f4b571c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,10 @@ +[tool.black] +line-length = 120 + +[tool.isort] +profile = "black" +line_length = 120 + +[tool.pyright] +venv = "venv" +venvPath = "." From 9f8b8d2437f57af2e8dc4038d615deafc3a69f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 18 Sep 2023 01:17:52 +0200 Subject: [PATCH 08/10] Reformat all files with black --- ankiconnect.py | 107 +++++++++++++------------ audio.py | 21 +++-- clipboard.py | 4 +- config.py | 69 ++++++++-------- dictionary.py | 57 ++++++++------ game2text.py | 134 +++++++++++++++++++++---------- gamescript.py | 73 +++++++++-------- hotkeys.py | 8 +- imageprofile.py | 31 +++++--- logger.py | 206 +++++++++++++++++++++++++++++------------------- ocr.py | 25 +++--- ocr_space.py | 36 +++++---- recordaudio.py | 51 ++++++------ textractor.py | 86 ++++++++++---------- tools.py | 59 ++++++++------ translate.py | 72 +++++++++-------- util.py | 106 ++++++++++++++----------- 17 files changed, 663 insertions(+), 482 deletions(-) diff --git a/ankiconnect.py b/ankiconnect.py index a93d8fc..d63587a 100644 --- a/ankiconnect.py +++ b/ankiconnect.py @@ -6,37 +6,39 @@ from logger import AUDIO_LOG_PATH from config import r_config, ANKI_CONFIG from dictionary import get_jpod_audio_base64 -from tools import bundle_dir +from tools import bundle_dir -ANKI_MODELS_FILENAME = 'ankimodels.yaml' +ANKI_MODELS_FILENAME = "ankimodels.yaml" + +NOTE_SCREENSHOT = "screenshot" -NOTE_SCREENSHOT = 'screenshot' def request(action, params): - return {'action': action, 'params': params, 'version': 6} + return {"action": action, "params": params, "version": 6} + @eel.expose def invoke(action, params): try: - requestJson = json.dumps(request(action, params)).encode('utf-8') - response = json.load(urllib.request.urlopen(urllib.request.Request('http://localhost:8765', requestJson))) + requestJson = json.dumps(request(action, params)).encode("utf-8") + response = json.load(urllib.request.urlopen(urllib.request.Request("http://localhost:8765", requestJson))) if len(response) != 2: - return 'Error: Response has an unexpected number of fields' - if 'error' not in response: - return 'Error: Response is missing required error field' - if 'result' not in response: - return 'Error: Response is missing required result field' - if response['error'] is not None: - return 'Error:' + response['error'] - return response['result'] + return "Error: Response has an unexpected number of fields" + if "error" not in response: + return "Error: Response is missing required error field" + if "result" not in response: + return "Error: Response is missing required result field" + if response["error"] is not None: + return "Error:" + response["error"] + return response["result"] except: - return 'Error: Failed to connect to Anki.' + return "Error: Failed to connect to Anki." def get_anki_models(): - filename = str(Path(bundle_dir, 'anki', ANKI_MODELS_FILENAME)) + filename = str(Path(bundle_dir, "anki", ANKI_MODELS_FILENAME)) ankiModels = [] - with open(filename, 'r') as stream: + with open(filename, "r") as stream: try: ankiModels = yaml.safe_load(stream) return ankiModels @@ -45,17 +47,20 @@ def get_anki_models(): return ankiModels + def update_anki_models(ankiModels): # save ankimodels - with open(str(Path(bundle_dir, 'anki', ANKI_MODELS_FILENAME)), 'w') as outfile: + with open(str(Path(bundle_dir, "anki", ANKI_MODELS_FILENAME)), "w") as outfile: yaml.dump(ankiModels, outfile, sort_keys=False, default_flow_style=False) return outfile.name + def fetch_anki_fields(model_names): for model_name in model_names: - field_names = invoke('modelFieldNames', {'modelName': model_name}) + field_names = invoke("modelFieldNames", {"modelName": model_name}) eel.setAnkiFields(model_name, field_names)() + def create_anki_note(note_data): field_value_map = eel.getFieldValueMap()() fields = {} @@ -63,54 +68,52 @@ def create_anki_note(note_data): audio_fields = [] word_audio_fields = [] for field in field_value_map: - value = field_value_map[field].replace(' ', '').lower() + value = field_value_map[field].replace(" ", "").lower() if value in note_data: - if (value == 'screenshot'): + if value == "screenshot": picture_fields.append(field) - elif (value == 'audio'): + elif value == "audio": audio_fields.append(field) - elif (value == 'wordaudio'): + elif value == "wordaudio": word_audio_fields.append(field) else: fields[field] = note_data[value] note_params = { - 'note': { - 'deckName': r_config(ANKI_CONFIG, 'deck'), - 'modelName': r_config(ANKI_CONFIG, 'model'), - 'fields': fields, - 'options': { - "allowDuplicate": True - }, - "tags": r_config(ANKI_CONFIG, 'cardtags').split() + "note": { + "deckName": r_config(ANKI_CONFIG, "deck"), + "modelName": r_config(ANKI_CONFIG, "model"), + "fields": fields, + "options": {"allowDuplicate": True}, + "tags": r_config(ANKI_CONFIG, "cardtags").split(), } } - if (picture_fields): + if picture_fields: # TODO: check to see if image is already in file and add from path picture_params = { - 'filename': note_data['filename'] + '.' + note_data['imagetype'], - 'fields': picture_fields, - 'data': note_data['screenshot'] + "filename": note_data["filename"] + "." + note_data["imagetype"], + "fields": picture_fields, + "data": note_data["screenshot"], } - note_params['note']['picture'] = [picture_params] - if (audio_fields): + note_params["note"]["picture"] = [picture_params] + if audio_fields: audio_params = { - 'filename': note_data['audio'], - 'fields': audio_fields, - 'path': str(Path(AUDIO_LOG_PATH, note_data['folder'], note_data['audio'])) + "filename": note_data["audio"], + "fields": audio_fields, + "path": str(Path(AUDIO_LOG_PATH, note_data["folder"], note_data["audio"])), } - note_params['note']['audio'] = [audio_params] - if (word_audio_fields): - word_audio_url = note_data['wordaudio'] - kana = word_audio_url.split('kana=')[1] + note_params["note"]["audio"] = [audio_params] + if word_audio_fields: + word_audio_url = note_data["wordaudio"] + kana = word_audio_url.split("kana=")[1] word_audio_params = { - 'filename': kana + '_' + note_data['filename'] + '.mp3', - 'fields': word_audio_fields, - 'data': get_jpod_audio_base64(word_audio_url) + "filename": kana + "_" + note_data["filename"] + ".mp3", + "fields": word_audio_fields, + "data": get_jpod_audio_base64(word_audio_url), } - if 'audio' in note_params['note']: - note_params['note']['audio'].append(word_audio_params) + if "audio" in note_params["note"]: + note_params["note"]["audio"].append(word_audio_params) else: - note_params['note']['audio'] = [word_audio_params] - result = invoke('addNote', note_params) + note_params["note"]["audio"] = [word_audio_params] + result = invoke("addNote", note_params) print(result) - return result \ No newline at end of file + return result diff --git a/audio.py b/audio.py index 1919daa..0f53209 100644 --- a/audio.py +++ b/audio.py @@ -13,7 +13,8 @@ AudioSegment.ffmpeg = ffmpeg_path os.environ["PATH"] += os.pathsep + str(Path(ffmpeg_path).parent) - # User config device exists? use config device, if not check if (1) valid, use (1), if not no audio + +# User config device exists? use config device, if not check if (1) valid, use (1), if not no audio @eel.expose def get_recommended_device_index(audio_host): config_device_name = r_config(LOG_CONFIG, "logaudiodevice") @@ -26,9 +27,10 @@ def get_recommended_device_index(audio_host): return default_device_index return -1 + def get_default_device_index(): p = pyaudio.PyAudio() - #Set default to first in list or ask Windows + # Set default to first in list or ask Windows try: default_device_index = p.get_default_input_device_info() except IOError: @@ -37,7 +39,8 @@ def get_default_device_index(): p.terminate() return info["index"] -#Select Device + +# Select Device @eel.expose def get_audio_objects(): p = pyaudio.PyAudio() @@ -46,20 +49,22 @@ def get_audio_objects(): info = p.get_device_info_by_index(i) audio_host = p.get_host_api_info_by_index(info["hostApi"])["name"] if valid_output_device(info["index"]): - audio_objects.setdefault(audio_host,[]).append({info["index"]: info["name"]}) + audio_objects.setdefault(audio_host, []).append({info["index"]: info["name"]}) p.terminate() return audio_objects + def get_audio_device_index_by_name(audio_host, device_name): p = pyaudio.PyAudio() for i in range(0, p.get_device_count()): info = p.get_device_info_by_index(i) - if (info["name"] == device_name and p.get_host_api_info_by_index(info["hostApi"])["name"] == audio_host): + if info["name"] == device_name and p.get_host_api_info_by_index(info["hostApi"])["name"] == audio_host: p.terminate() return info["index"] p.terminate() return -1 + def valid_output_device(deviceIndex): if not isinstance(deviceIndex, int): return False @@ -68,7 +73,7 @@ def valid_output_device(deviceIndex): p = pyaudio.PyAudio() device_info = p.get_device_info_by_index(deviceIndex) is_input = device_info["maxInputChannels"] > 0 - is_windows = (platform.system() == 'Windows') + is_windows = platform.system() == "Windows" is_wasapi = (p.get_host_api_info_by_index(device_info["hostApi"])["name"]).find("WASAPI") != -1 p.terminate() if is_input: @@ -78,12 +83,14 @@ def valid_output_device(deviceIndex): return True return True + def play_audio_from_file(file): filename, file_extension = os.path.splitext(file) song = AudioSegment.from_file(file, file_extension[1:]) play(song) return song.duration_seconds + def convert_audio(in_file, out_file): filename, file_extension = os.path.splitext(out_file) - AudioSegment.from_mp3(in_file).export(out_file, format=file_extension[1:]) \ No newline at end of file + AudioSegment.from_mp3(in_file).export(out_file, format=file_extension[1:]) diff --git a/clipboard.py b/clipboard.py index af6bcc0..a5bd8c5 100644 --- a/clipboard.py +++ b/clipboard.py @@ -3,14 +3,16 @@ previous_text = "" + def clipboard_to_output(): global previous_text if not previous_text: previous_text = eel.getOutputText()() if previous_text != pyperclip.paste(): - parsed_output = pyperclip.paste().replace('\r\n', ' ') + parsed_output = pyperclip.paste().replace("\r\n", " ") eel.updateOutput(parsed_output)() previous_text = pyperclip.paste() + def text_to_clipboard(text): pyperclip.copy(text) diff --git a/config.py b/config.py index f7347d6..05f9536 100644 --- a/config.py +++ b/config.py @@ -2,79 +2,80 @@ import os import platform -OCR_CONFIG = 'OCRCONFIG' -TRANSLATION_CONFIG = 'TRANSLATIONCONFIG' -APPERANCE_CONFIG = 'APPEARANCE' -APP_CONFIG = 'APPCONFIG' -ANKI_CONFIG = 'ANKICONFIG' -LOG_CONFIG = 'LOGCONFIG' -SCRIPT_MATCH_CONFIG = 'SCRIPTMATCHCONFIG' -TEXTHOOKER_CONFIG = 'TEXTHOOKERCONFIG' -HOTKEYS_CONFIG = '$OS_HOTKEYS' -PATHS_CONFIG = 'PATHS' - -OS_STRING = '$OS' - -#Get the configparser object +OCR_CONFIG = "OCRCONFIG" +TRANSLATION_CONFIG = "TRANSLATIONCONFIG" +APPERANCE_CONFIG = "APPEARANCE" +APP_CONFIG = "APPCONFIG" +ANKI_CONFIG = "ANKICONFIG" +LOG_CONFIG = "LOGCONFIG" +SCRIPT_MATCH_CONFIG = "SCRIPTMATCHCONFIG" +TEXTHOOKER_CONFIG = "TEXTHOOKERCONFIG" +HOTKEYS_CONFIG = "$OS_HOTKEYS" +PATHS_CONFIG = "PATHS" + +OS_STRING = "$OS" + +# Get the configparser object # config_object = ConfigParser() -#Path for config file -config_file = os.path.join(os.path.dirname(__file__), 'config.ini') +# Path for config file +config_file = os.path.join(os.path.dirname(__file__), "config.ini") + def get_platform_for_section(section): - platform_names_to_config_os_name = { - 'Windows': 'WINDOWS', - 'Darwin': 'MAC', - 'Linux': 'LINUX' - } + platform_names_to_config_os_name = {"Windows": "WINDOWS", "Darwin": "MAC", "Linux": "LINUX"} platform_name = platform.system() return section.replace(OS_STRING, platform_names_to_config_os_name[platform_name]) + def r_config(section, key): if OS_STRING in section: section = get_platform_for_section(section) - #Read config.ini file + # Read config.ini file config_object = ConfigParser() - config_object.read(config_file, encoding='utf-8') + config_object.read(config_file, encoding="utf-8") - #Get the password + # Get the password section = config_object[section] return section[key] + def r_config_section(section): if OS_STRING in section: section = get_platform_for_section(section) config_object = ConfigParser() - config_object.read(config_file, encoding='utf-8') + config_object.read(config_file, encoding="utf-8") section = dict(config_object[section]) return section + def r_config_all(): config_object = ConfigParser() - config_object.read(config_file, encoding='utf-8') + config_object.read(config_file, encoding="utf-8") section_dict = {} for section in config_object: - if 'WINDOWS' in section or 'MAC' in section or 'LINUX' in section: + if "WINDOWS" in section or "MAC" in section or "LINUX" in section: continue section_dict[section] = dict(config_object[section]) # Platform specific config section_dict[HOTKEYS_CONFIG] = dict(config_object[get_platform_for_section(HOTKEYS_CONFIG)]) return section_dict + def w_config(section, to_update_dict): if OS_STRING in section: section = get_platform_for_section(section) - #Read config.ini file + # Read config.ini file config_object = ConfigParser() - config_object.read("config.ini", encoding='utf-8') + config_object.read("config.ini", encoding="utf-8") - #Get the USERINFO section + # Get the USERINFO section section = config_object[section] - #Update the key value + # Update the key value for key, value in to_update_dict.items(): section[key] = value - #Write changes back to file - with open('config.ini', 'w', encoding='utf-8') as conf: - config_object.write(conf) \ No newline at end of file + # Write changes back to file + with open("config.ini", "w", encoding="utf-8") as conf: + config_object.write(conf) diff --git a/dictionary.py b/dictionary.py index be764d3..4e52605 100644 --- a/dictionary.py +++ b/dictionary.py @@ -1,4 +1,4 @@ -import zipfile +import zipfile import json import requests import base64 @@ -13,46 +13,51 @@ pitch_dictionary_map = {} # Sudachi Parser -tokenizer_obj = dictionary.Dictionary(dict_type='small').create() +tokenizer_obj = dictionary.Dictionary(dict_type="small").create() mode = tokenizer.Tokenizer.SplitMode.A -DICTIONARY_PATH = Path(bundle_dir, 'resources', 'dictionaries') +DICTIONARY_PATH = Path(bundle_dir, "resources", "dictionaries") + def get_local_dictionaries(): - files = glob.glob(str(DICTIONARY_PATH) + '/*.zip') + files = glob.glob(str(DICTIONARY_PATH) + "/*.zip") return [Path(file).stem for file in files] + def load_dictionary_by_path(dictionary_path): output_map = {} - archive = zipfile.ZipFile(dictionary_path, 'r') + archive = zipfile.ZipFile(dictionary_path, "r") result = list() for file in archive.namelist(): - if file.startswith('term'): + if file.startswith("term"): with archive.open(file) as f: - data = f.read() + data = f.read() d = json.loads(data.decode("utf-8")) result.extend(d) for entry in result: - if (entry[0] in output_map): - output_map[entry[0]].append(entry) + if entry[0] in output_map: + output_map[entry[0]].append(entry) else: - output_map[entry[0]] = [entry] # Using headword as key for finding the dictionary entry + output_map[entry[0]] = [entry] # Using headword as key for finding the dictionary entry return output_map + def load_all_dictionaries(): - default_dictionary = r_config(ANKI_CONFIG, 'anki_dictionary') + default_dictionary = r_config(ANKI_CONFIG, "anki_dictionary") load_dictionary(default_dictionary) # pitch_dictionary_map = load_sdictionary(str(Path(bundle_dir, 'dictionaries', 'kanjium_pitch_accents.zip'))) + def load_dictionary(dictionary_name): global dictionary_map - dictionary_path = Path(DICTIONARY_PATH, dictionary_name + '.zip') + dictionary_path = Path(DICTIONARY_PATH, dictionary_name + ".zip") if dictionary_path: dictionary_map = load_dictionary_by_path(str(dictionary_path)) else: - print('failed to find path for dictionary') + print("failed to find path for dictionary") + def look_up(word): word = word.replace(" ", "") @@ -61,15 +66,13 @@ def look_up(word): word = m.dictionary_form() if word not in dictionary_map: return None - result = [{ - 'headword': entry[0], - 'reading': entry[1], - 'tags': entry[2], - 'glossary_list': entry[5], - 'sequence': entry[6] - } for entry in dictionary_map[word]] + result = [ + {"headword": entry[0], "reading": entry[1], "tags": entry[2], "glossary_list": entry[5], "sequence": entry[6]} + for entry in dictionary_map[word] + ] return result + def get_jpod_audio(url): try: requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning) @@ -78,24 +81,28 @@ def get_jpod_audio(url): except: return None + def validate_jpod_audio_url(url): jpod_audio = get_jpod_audio(url) if jpod_audio: - return len(jpod_audio.content) != 52288 # invalid audio + return len(jpod_audio.content) != 52288 # invalid audio else: return False + def get_jpod_audio_url(kanji, kana): - url = 'https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kanji={}&kana={}'.format(kanji, kana) - return url if (validate_jpod_audio_url(url)) else '' + url = "https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kanji={}&kana={}".format(kanji, kana) + return url if (validate_jpod_audio_url(url)) else "" + def get_jpod_audio_base64(url): jpod_audio = get_jpod_audio(url) if jpod_audio: - return 'data:audio/mp3;base64,' + str(base64.b64encode(jpod_audio.content)) + return "data:audio/mp3;base64," + str(base64.b64encode(jpod_audio.content)) else: return None + # load_all_dictionaries() # a = look_up('好') # print(a) @@ -112,4 +119,4 @@ def get_jpod_audio_base64(url): # 'pitches': pitch['pitches'] # } # else: -# return None \ No newline at end of file +# return None diff --git a/game2text.py b/game2text.py index 00c5fce..0254453 100644 --- a/game2text.py +++ b/game2text.py @@ -26,14 +26,17 @@ # Set web files folder and optionally specify which file types to check for eel.expose() # *Default allowed_extensions are: ['.js', '.html', '.txt', '.htm', '.xhtml'] + def close(page, sockets): if not sockets: - os._exit(0) + os._exit(0) + -@eel.expose +@eel.expose def recognize_image(engine, image, orientation): return detect_and_log(engine, image, orientation, session_start_time, get_time_string(), audio_recorder) + @eel.expose def log_output(text): log_id = get_time_string() @@ -41,40 +44,49 @@ def log_output(text): log_media(session_start_time, log_id, audio_recorder) return log_id + @eel.expose def export_image_filter_profile(profile): return export_image_profile(profile) + @eel.expose def load_image_filter_profiles(): return load_image_profiles() + @eel.expose def open_image_filter_profile(): return open_image_profile() + @eel.expose def load_game_text_scripts(): return load_game_scripts() + @eel.expose def open_game_text_script(): return open_game_script() + @eel.expose def update_main_window_text(text): is_log_text = False eel.updateOutput(text, is_log_text)() + @eel.expose def update_log_window_text(log_id, text): update_log_text(log_id, session_start_time, text) - eel.updateLogDataById(log_id, {'text': text})() + eel.updateLogDataById(log_id, {"text": text})() + -@eel.expose +@eel.expose def translate(text): return multi_translate(text) + @eel.expose def monitor_clipboard(): if clipboard_timer.is_running: @@ -82,18 +94,20 @@ def monitor_clipboard(): else: clipboard_timer.start() + @eel.expose def start_manual_recording(request_time, session_start_time): global manual_audio_recorder global manual_audio_file_path if manual_audio_recorder.is_recording(): stop_manual_recording() - file_name = request_time + '.' + r_config(LOG_CONFIG, 'logaudiotype') + file_name = request_time + "." + r_config(LOG_CONFIG, "logaudiotype") manual_audio_file_path = str(Path(AUDIO_LOG_PATH, session_start_time, file_name)) - device_index = get_recommended_device_index(r_config(LOG_CONFIG, 'logaudiohost')) + device_index = get_recommended_device_index(r_config(LOG_CONFIG, "logaudiohost")) manual_audio_recorder = RecordThread(device_index, int(r_config(LOG_CONFIG, "logaudioframes"))) manual_audio_recorder.start() + @eel.expose def stop_manual_recording(): if manual_audio_recorder.is_recording(): @@ -103,7 +117,8 @@ def stop_manual_recording(): if audio_exists: file_name = os.path.basename(manual_audio_file_path) return file_name - return '' + return "" + @eel.expose def restart_audio_recording(device_index): @@ -111,76 +126,99 @@ def restart_audio_recording(device_index): audio_recorder = RecordThread(device_index, int(r_config(LOG_CONFIG, "logaudioframes"))) audio_recorder.start() + @eel.expose def copy_text_to_clipboard(text): text_to_clipboard(text) + @eel.expose def read_config(section, key): return r_config(section, key) + @eel.expose def read_config_all(): return r_config_all() + @eel.expose def update_config(section, d): return w_config(section, d) + @eel.expose def invoke_anki(action, params={}): return invoke(action, params) + @eel.expose def get_anki_card_models(): return get_anki_models() + @eel.expose def fetch_anki_fields_by_modals(model_names): fetch_anki_fields_thread = threading.Thread(target=fetch_anki_fields, args=((model_names,))) fetch_anki_fields_thread.start() + @eel.expose def update_anki_card_models(ankiModels): return update_anki_models(ankiModels) + @eel.expose def create_note(note_data): return create_anki_note(note_data) + @eel.expose def set_dictionary(dictionary): load_dictionary(dictionary) + @eel.expose def get_jpod_url(kanji, kana): return get_jpod_audio_url(kanji=kanji, kana=kana) + @eel.expose def get_dictionaries(): return get_local_dictionaries() + @eel.expose def look_up_dictionary(word): return look_up(word) + @eel.expose def get_path_to_textractor(): return path_to_textractor() + @eel.expose def open_folder_for_textractor(): return open_folder_textractor_path() + @eel.expose def get_PIDs(): return get_PID_list() + @eel.expose def attach_process(pids): - textractor_thread = threading.Thread(target=start_textractor, args=[pids,]) + textractor_thread = threading.Thread( + target=start_textractor, + args=[ + pids, + ], + ) textractor_thread.start() + @eel.expose def detach_process(pids): try: @@ -188,8 +226,9 @@ def detach_process(pids): for pid in pids: textractor.detach(pid) except Exception as e: - print('error', str(e)) - return 'Error: failed to detach processes' + str(e) + print("error", str(e)) + return "Error: failed to detach processes" + str(e) + def start_textractor(pids): try: @@ -199,8 +238,9 @@ def start_textractor(pids): textractor.attach_multiple(pids) textractor.read() except Exception as e: - print('error', str(e)) - return 'Error: failed to attach processes' + str(e) + print("error", str(e)) + return "Error: failed to attach processes" + str(e) + @eel.expose def hook_code(code, pids): @@ -210,68 +250,80 @@ def hook_code(code, pids): textractor.hook(code, pid) except Exception as e: print(e) - return 'Error: failed to hook code' + return "Error: failed to hook code" + def monitor_textractor(output_objects): texthooker_config = r_config_section(TEXTHOOKER_CONFIG) output_objects = format_output( - output_objects=output_objects, - remove_repeat_mode=texthooker_config['remove_repeat_mode'].lower(), - is_remove_duplicates= texthooker_config['remove_duplicates'] == 'true', - is_remove_spaces=texthooker_config['remove_spaces'] == 'true') + output_objects=output_objects, + remove_repeat_mode=texthooker_config["remove_repeat_mode"].lower(), + is_remove_duplicates=texthooker_config["remove_duplicates"] == "true", + is_remove_spaces=texthooker_config["remove_spaces"] == "true", + ) eel.textractorPipe(output_objects) + @eel.expose def open_new_window(html_file, height=900, width=600): - eel.start(html_file, - close_callback=close, - mode=get_default_browser_name() if r_config(APP_CONFIG, "browser") == 'default' else r_config(APP_CONFIG, "browser"), - host=r_config(APP_CONFIG, "host"), - size=(width, height), - port = int(r_config(APP_CONFIG, "port"))) + eel.start( + html_file, + close_callback=close, + mode=get_default_browser_name() + if r_config(APP_CONFIG, "browser") == "default" + else r_config(APP_CONFIG, "browser"), + host=r_config(APP_CONFIG, "host"), + size=(width, height), + port=int(r_config(APP_CONFIG, "port")), + ) return + def run_eel(): - eel.init('web', allowed_extensions=['.js', '.html', '.map']) - browser_mode = get_default_browser_name() if r_config(APP_CONFIG, "browser") == 'default' else r_config(APP_CONFIG, "browser") - paths_config = r_config_section('PATHS') - if 'browser' in paths_config: - eel.browsers.set_path(browser_mode, paths_config['browser']) - eel.start('index.html', - close_callback=close, - mode=browser_mode, - host=r_config(APP_CONFIG, "host"), - port=int(r_config(APP_CONFIG, "port")) + eel.init("web", allowed_extensions=[".js", ".html", ".map"]) + browser_mode = ( + get_default_browser_name() if r_config(APP_CONFIG, "browser") == "default" else r_config(APP_CONFIG, "browser") ) + paths_config = r_config_section("PATHS") + if "browser" in paths_config: + eel.browsers.set_path(browser_mode, paths_config["browser"]) + eel.start( + "index.html", + close_callback=close, + mode=browser_mode, + host=r_config(APP_CONFIG, "host"), + port=int(r_config(APP_CONFIG, "port")), + ) + main_thread = threading.Thread(target=run_eel, args=()) main_thread.start() # Thread to load dictionaries -dictionary_thread = threading.Thread(target=load_all_dictionaries, args=()) +dictionary_thread = threading.Thread(target=load_all_dictionaries, args=()) dictionary_thread.start() # Thread to record audio continuously -logaudiohost = r_config(LOG_CONFIG, 'logaudiohost') +logaudiohost = r_config(LOG_CONFIG, "logaudiohost") recommended_audio_device_index = get_recommended_device_index(logaudiohost) logaudioframes = int(r_config(LOG_CONFIG, "logaudioframes")) audio_recorder = RecordThread(recommended_audio_device_index, logaudioframes) is_log_audio = r_config(LOG_CONFIG, "logaudio").lower() == "true" # TODO: Fix input overflowed error if start logging audio on Mac automatically at launch -is_mac = (platform.system() == 'Darwin') +is_mac = platform.system() == "Darwin" if is_mac: - is_log_audio = False - w_config(LOG_CONFIG, {"logaudio": 'false'}) + is_log_audio = False + w_config(LOG_CONFIG, {"logaudio": "false"}) if is_log_audio and recommended_audio_device_index != -1: audio_recorder.start() # Thread to manually record audio manual_audio_recorder = RecordThread(recommended_audio_device_index, int(r_config(LOG_CONFIG, "logaudioframes"))) -manual_audio_file_path = '' +manual_audio_file_path = "" # Thread to export clipboard text continuously clipboard_timer = RepeatedTimer(1, clipboard_to_output) -clipboard_timer.stop() # stop the initial timer +clipboard_timer.stop() # stop the initial timer with keyboard.GlobalHotKeys(hotkey_map) as listener: - listener.join() \ No newline at end of file + listener.join() diff --git a/gamescript.py b/gamescript.py index 7ba6a8c..717c585 100644 --- a/gamescript.py +++ b/gamescript.py @@ -7,37 +7,40 @@ from config import r_config, w_config, LOG_CONFIG, SCRIPT_MATCH_CONFIG from tools import bundle_dir -GAME_SCRIPT_PATH = Path(bundle_dir, 'gamescripts') -MATCH_LIMIT= int(r_config(SCRIPT_MATCH_CONFIG, 'match_limit')) -CONFIDENCE_THRESHOLD= int(r_config(SCRIPT_MATCH_CONFIG, 'confidence_threshold')) +GAME_SCRIPT_PATH = Path(bundle_dir, "gamescripts") +MATCH_LIMIT = int(r_config(SCRIPT_MATCH_CONFIG, "match_limit")) +CONFIDENCE_THRESHOLD = int(r_config(SCRIPT_MATCH_CONFIG, "confidence_threshold")) REGIONAL_SCAN_AREA = 100 -current_gamescript = '' +current_gamescript = "" gamescript_dict = {} last_match_line = None + def open_game_script(): root = Tk() root.withdraw() - file = askopenfile(initialdir=str(GAME_SCRIPT_PATH), filetypes = (("TXT files","*.txt"),("all files","*.*")), defaultextension=".txt") + file = askopenfile( + initialdir=str(GAME_SCRIPT_PATH), + filetypes=(("TXT files", "*.txt"), ("all files", "*.*")), + defaultextension=".txt", + ) if not file: return try: - new_file = str(Path(GAME_SCRIPT_PATH, Path(file.name).stem + '.txt')) + new_file = str(Path(GAME_SCRIPT_PATH, Path(file.name).stem + ".txt")) copyfile(file.name, new_file) - w_config(LOG_CONFIG, {'gamescriptfile': file.name}) + w_config(LOG_CONFIG, {"gamescriptfile": file.name}) except: - print('File not selected') + print("File not selected") file.close() root.destroy() return Path(file.name).stem + def load_game_scripts(): - files = glob.glob(str(Path(GAME_SCRIPT_PATH, '*.txt'))) - return [{ - 'name': Path(file).stem, - 'path': str(Path(file)) - } for file in files] + files = glob.glob(str(Path(GAME_SCRIPT_PATH, "*.txt"))) + return [{"name": Path(file).stem, "path": str(Path(file))} for file in files] def get_regional_scan_lines(): @@ -53,40 +56,42 @@ def get_regional_scan_lines(): if upper > len(gamescript_dict): upper = len(gamescript_dict) lines = {} - print('lower',lower) - print('upper', upper) + print("lower", lower) + print("upper", upper) for index in range(lower, upper): lines[index] = gamescript_dict[index] return lines + def init_gamescript(gamescript): - if (Path(gamescript).is_file()): - global current_gamescript - current_gamescript = gamescript - with open(gamescript, 'r', encoding='utf-8') as f: - global gamescript_dict - gamescript_dict = {index: line.strip() for index, line in enumerate(f)} - f.close() + if Path(gamescript).is_file(): + global current_gamescript + current_gamescript = gamescript + with open(gamescript, "r", encoding="utf-8") as f: + global gamescript_dict + gamescript_dict = {index: line.strip() for index, line in enumerate(f)} + f.close() + def add_matching_script_to_logs(gamescript, logs): - if (gamescript != current_gamescript): - init_gamescript(gamescript) - - if (gamescript_dict is None): + if gamescript != current_gamescript: + init_gamescript(gamescript) + + if gamescript_dict is None: init_gamescript(gamescript) lines = get_regional_scan_lines() for log in logs: - matches = process.extract(log['text'], lines, limit=MATCH_LIMIT) - log['matches'] = matches - if (matches): - if (matches[0][1] > CONFIDENCE_THRESHOLD): + matches = process.extract(log["text"], lines, limit=MATCH_LIMIT) + log["matches"] = matches + if matches: + if matches[0][1] > CONFIDENCE_THRESHOLD: global last_match_line last_match_line = matches[0][2] else: - if (last_match_line): # Regional scan failed - last_match_line = None + if last_match_line: # Regional scan failed + last_match_line = None add_matching_script_to_logs(gamescript, logs) - last_match_line = None - return logs \ No newline at end of file + last_match_line = None + return logs diff --git a/hotkeys.py b/hotkeys.py index 224416e..b04ea83 100644 --- a/hotkeys.py +++ b/hotkeys.py @@ -1,17 +1,21 @@ import eel from config import r_config, HOTKEYS_CONFIG + def refresh_ocr_hotkey(): eel.refreshOCR() + def add_to_anki_hotkey(): eel.addActiveCardToAnki() + def record_audio_hotkey(): eel.resetAudioRecording() + hotkey_map = { r_config(HOTKEYS_CONFIG, "refresh_ocr"): refresh_ocr_hotkey, r_config(HOTKEYS_CONFIG, "add_to_anki"): add_to_anki_hotkey, - r_config(HOTKEYS_CONFIG, "record_audio"): record_audio_hotkey -} \ No newline at end of file + r_config(HOTKEYS_CONFIG, "record_audio"): record_audio_hotkey, +} diff --git a/imageprofile.py b/imageprofile.py index 4053eb6..302786f 100644 --- a/imageprofile.py +++ b/imageprofile.py @@ -5,13 +5,18 @@ from tkinter.filedialog import asksaveasfile, askopenfile from tools import bundle_dir -IMAGE_PROFILE_PATH = Path(bundle_dir, 'profiles') +IMAGE_PROFILE_PATH = Path(bundle_dir, "profiles") + def open_image_profile(): root = Tk() root.withdraw() - yam_file = '' - file = askopenfile(initialdir=str(IMAGE_PROFILE_PATH), filetypes = (("YAML files","*.yaml"),("all files","*.*")), defaultextension=".yaml") + yam_file = "" + file = askopenfile( + initialdir=str(IMAGE_PROFILE_PATH), + filetypes=(("YAML files", "*.yaml"), ("all files", "*.*")), + defaultextension=".yaml", + ) try: yam_file = yaml.safe_load(file) except yaml.YAMLError as exc: @@ -24,27 +29,33 @@ def open_image_profile(): def export_image_profile(profile): root = Tk() root.withdraw() - file = asksaveasfile(initialdir=str(IMAGE_PROFILE_PATH), mode='w', filetypes = (("YAML files","*.yaml"),("all files","*.*")), defaultextension=".yaml") - if file is None: # asksaveasfile return `None` if dialog closed with "cancel". + file = asksaveasfile( + initialdir=str(IMAGE_PROFILE_PATH), + mode="w", + filetypes=(("YAML files", "*.yaml"), ("all files", "*.*")), + defaultextension=".yaml", + ) + if file is None: # asksaveasfile return `None` if dialog closed with "cancel". return - with open(file.name, 'w') as outfile: + with open(file.name, "w") as outfile: yaml.dump(profile, outfile, sort_keys=False, default_flow_style=False) file.close() root.destroy() return file.name + def load_image_profiles(): - files = glob.glob(str(Path(IMAGE_PROFILE_PATH, '*.yaml'))) + files = glob.glob(str(Path(IMAGE_PROFILE_PATH, "*.yaml"))) profiles = [] for file in files: - with open(file, 'r') as stream: + with open(file, "r") as stream: try: yam_file = yaml.safe_load(stream) - yam_file['name'] = Path(file).stem + yam_file["name"] = Path(file).stem profiles.append(yam_file) except yaml.YAMLError as exc: print(exc) - return profiles \ No newline at end of file + return profiles diff --git a/logger.py b/logger.py index d81c939..dc865e2 100644 --- a/logger.py +++ b/logger.py @@ -15,53 +15,59 @@ from gamescript import add_matching_script_to_logs from tools import bundle_dir -TEXT_LOG_PATH = Path(bundle_dir, 'logs', 'text') -IMAGE_LOG_PATH = Path(bundle_dir, 'logs', 'images') -AUDIO_LOG_PATH = Path(bundle_dir, 'logs', 'audio') +TEXT_LOG_PATH = Path(bundle_dir, "logs", "text") +IMAGE_LOG_PATH = Path(bundle_dir, "logs", "images") +AUDIO_LOG_PATH = Path(bundle_dir, "logs", "audio") game_script_matcher = None + def get_time_string(): - return time.strftime('%Y%m%d-%H%M%S') + return time.strftime("%Y%m%d-%H%M%S") + def parse_time_string(time_string): - return datetime.strptime(time_string, '%Y%m%d-%H%M%S') + return datetime.strptime(time_string, "%Y%m%d-%H%M%S") + def get_hours_string(datetime_object): - return datetime.strftime(datetime_object, '%I:%M%p') + return datetime.strftime(datetime_object, "%I:%M%p") + -def log_text(start_time,request_time, text): - parsed_text = text.replace('\n', '') - if (len(parsed_text) < 1): +def log_text(start_time, request_time, text): + parsed_text = text.replace("\n", "") + if len(parsed_text) < 1: return - filename = '{}/{}.txt'.format(TEXT_LOG_PATH, start_time) + filename = "{}/{}.txt".format(TEXT_LOG_PATH, start_time) create_directory_if_not_exists(filename) - with open(filename, 'a', encoding='utf-8', newline='') as f: - if(os.path.getsize(filename) > 0): - f.write('{}{}, {}'.format('\n', request_time, parsed_text)) + with open(filename, "a", encoding="utf-8", newline="") as f: + if os.path.getsize(filename) > 0: + f.write("{}{}, {}".format("\n", request_time, parsed_text)) else: - f.write('{}, {}'.format(request_time, parsed_text)) + f.write("{}, {}".format(request_time, parsed_text)) f.close() - + + def log_media(session_start_time, request_time, audio_recorder): - is_log_images = r_config(LOG_CONFIG, 'logimages').lower() == 'true' - is_log_audio = r_config(LOG_CONFIG, 'logaudio').lower() == 'true' - audio_duration = float(r_config(LOG_CONFIG, 'logaudioduration')) + is_log_images = r_config(LOG_CONFIG, "logimages").lower() == "true" + is_log_audio = r_config(LOG_CONFIG, "logaudio").lower() == "true" + audio_duration = float(r_config(LOG_CONFIG, "logaudioduration")) if is_log_audio: - file_name = request_time + '.' + r_config(LOG_CONFIG, 'logaudiotype') + file_name = request_time + "." + r_config(LOG_CONFIG, "logaudiotype") audio_file_path = str(Path(AUDIO_LOG_PATH, session_start_time, file_name)) create_directory_if_not_exists(audio_file_path) audio_recorder.stop_recording(audio_file_path, audio_duration) eel.restartAudioRecording()() if is_log_images: - image_extension = r_config(LOG_CONFIG, 'logimagetype') - file_name = request_time + '.' + image_extension + image_extension = r_config(LOG_CONFIG, "logimagetype") + file_name = request_time + "." + image_extension full_image_path = str(Path(IMAGE_LOG_PATH, session_start_time, file_name)) - thread = threading.Thread(target = log_video_image, args=[full_image_path]) + thread = threading.Thread(target=log_video_image, args=[full_image_path]) thread.start() else: insert_newest_log_without_image() + def log_video_image(image_path): create_directory_if_not_exists(image_path) base64_image = eel.getVideoImage()() @@ -75,42 +81,48 @@ def get_image_type(log_id, folder_name): path = Path(IMAGE_LOG_PATH, folder_name) if not path.is_dir(): return None - file_name = next((f for f in os.listdir(path) if re.match('{}.(?:jpg|jpeg|png|tiff|webp)$'.format(log_id), f)), None) + file_name = next( + (f for f in os.listdir(path) if re.match("{}.(?:jpg|jpeg|png|tiff|webp)$".format(log_id), f)), None + ) if not file_name: return None - return Path(file_name).suffix.split('.')[1] + return Path(file_name).suffix.split(".")[1] + def get_base64_image_with_log(log_id, folder_name): - imagePath = str(Path(IMAGE_LOG_PATH, folder_name, log_id + '.png')) + imagePath = str(Path(IMAGE_LOG_PATH, folder_name, log_id + ".png")) path = Path(IMAGE_LOG_PATH, folder_name) if not path.is_dir(): return None - file_name = next((f for f in os.listdir(path) if re.match('{}.(?:jpg|jpeg|png|tiff|webp)$'.format(log_id), f)), None) + file_name = next( + (f for f in os.listdir(path) if re.match("{}.(?:jpg|jpeg|png|tiff|webp)$".format(log_id), f)), None + ) if not file_name: return None - with open('{}/{}'.format(path, file_name), 'rb') as image_file: - base64_bytes = base64.b64encode(image_file.read()) - base64_image_string = base64_bytes.decode('utf-8') + with open("{}/{}".format(path, file_name), "rb") as image_file: + base64_bytes = base64.b64encode(image_file.read()) + base64_image_string = base64_bytes.decode("utf-8") return base64_image_string + @eel.expose def show_logs(): - last_session_max_log_size = int(r_config(LOG_CONFIG, 'lastsessionmaxlogsize')) + last_session_max_log_size = int(r_config(LOG_CONFIG, "lastsessionmaxlogsize")) saved_logs = get_logs(limit=last_session_max_log_size) if len(saved_logs) > 0: # Workaround to fix the problem first image data is not transferred to log window - image_data_list = eel.getCachedScreenshots()() + image_data_list = eel.getCachedScreenshots()() if image_data_list: for log in saved_logs: - if log['id'] in image_data_list.keys(): + if log["id"] in image_data_list.keys(): # Remove cache if file is saved - if log['image']: - eel.removeCachedScreenshot(log['id'])() + if log["image"]: + eel.removeCachedScreenshot(log["id"])() # Get image from cache else: - image_data = image_data_list[log['id']] - log['image'] = image_data['base64ImageString'] - log['image_type'] = image_data['imageType'] + image_data = image_data_list[log["id"]] + log["image"] = image_data["base64ImageString"] + log["image_type"] = image_data["imageType"] return saved_logs @@ -120,36 +132,43 @@ def text_to_log(text, file_path): image = get_base64_image_with_log(log_id=log_id, folder_name=Path(file_path).stem) image_type = get_image_type(log_id=log_id, folder_name=Path(file_path).stem) log = { - 'id': log_id, - 'file': Path(file_path).name, - 'folder': Path(file_path).stem, - 'image': image, - 'image_type': image_type, - 'audio': get_audio_file_name(log_id, Path(file_path).stem), - 'hours': get_hours_string(date), - 'text': text[16:] + "id": log_id, + "file": Path(file_path).name, + "folder": Path(file_path).stem, + "image": image, + "image_type": image_type, + "audio": get_audio_file_name(log_id, Path(file_path).stem), + "hours": get_hours_string(date), + "text": text[16:], } return log + def add_gamescript_to_logs(logs): - gamescript = r_config(LOG_CONFIG, 'gamescriptfile',) - if (gamescript): - if (Path(gamescript).is_file()): + gamescript = r_config( + LOG_CONFIG, + "gamescriptfile", + ) + if gamescript: + if Path(gamescript).is_file(): logs = add_matching_script_to_logs(gamescript, logs) for log in logs: - if ('matches' in log): - eel.updateLogDataById(log['id'], {'matches': log['matches'], 'autoMatch': True, 'isMatched': False})() + if "matches" in log: + eel.updateLogDataById( + log["id"], {"matches": log["matches"], "autoMatch": True, "isMatched": False} + )() return - + + def get_logs(limit=0): output = [] if not os.path.exists(TEXT_LOG_PATH): return [] - list_of_files = glob.glob(str(TEXT_LOG_PATH) + '/*.txt') + list_of_files = glob.glob(str(TEXT_LOG_PATH) + "/*.txt") if len(list_of_files) < 1: return [] latest_file = max(list_of_files, key=os.path.getctime) - with open(latest_file, 'r', encoding='utf-8') as f: + with open(latest_file, "r", encoding="utf-8") as f: for line in f: log = text_to_log(line, latest_file) output.append(log) @@ -159,21 +178,22 @@ def get_logs(limit=0): # Start another thread to match logs to game script is_matching = eel.isMatchingScript()() if is_matching: - thread = threading.Thread(target = add_gamescript_to_logs, args=[output]) + thread = threading.Thread(target=add_gamescript_to_logs, args=[output]) thread.start() return output + def get_latest_log(): log = {} if not os.path.exists(TEXT_LOG_PATH): return {} - list_of_files = glob.glob(str(TEXT_LOG_PATH) + '/*.txt') + list_of_files = glob.glob(str(TEXT_LOG_PATH) + "/*.txt") if len(list_of_files) < 1: return {} latest_file = max(list_of_files, key=os.path.getctime) if not latest_file: return None - with open(latest_file, 'r', encoding='utf-8') as f: + with open(latest_file, "r", encoding="utf-8") as f: for line in f: pass last_line = line @@ -182,46 +202,53 @@ def get_latest_log(): # Start another thread to match log to game script is_matching = eel.isMatchingScript()() if is_matching: - thread = threading.Thread(target = add_gamescript_to_logs, args=[[log],]) + thread = threading.Thread( + target=add_gamescript_to_logs, + args=[ + [log], + ], + ) thread.start() return log + @eel.expose def delete_log(log_id, folder_name): - filename = '{}/{}.txt'.format(TEXT_LOG_PATH, folder_name) - if (Path(filename).is_file()): - temp_filename = '{}/temp.txt'.format(TEXT_LOG_PATH) + filename = "{}/{}.txt".format(TEXT_LOG_PATH, folder_name) + if Path(filename).is_file(): + temp_filename = "{}/temp.txt".format(TEXT_LOG_PATH) # lines = [] - with open(filename, "r", encoding='utf-8') as file: + with open(filename, "r", encoding="utf-8") as file: lines = file.readlines() - with open(temp_filename, "w", encoding='utf-8') as new_file: - newLines = [line.rstrip('\r\n') for line in lines if line[:15] != log_id] + with open(temp_filename, "w", encoding="utf-8") as new_file: + newLines = [line.rstrip("\r\n") for line in lines if line[:15] != log_id] for line in newLines: if line != newLines[0]: - new_file.write('\n') + new_file.write("\n") new_file.write(line) # Remove original file and rename the temporary as the original one os.remove(filename) os.rename(temp_filename, filename) return - return + return + @eel.expose def update_log_text(log_id, folder_name, text): - parsed_text = text.replace('\n', '') - if (len(parsed_text) < 1): + parsed_text = text.replace("\n", "") + if len(parsed_text) < 1: return - filename = '{}/{}.txt'.format(TEXT_LOG_PATH, folder_name) - if (Path(filename).is_file()): - temp_filename = '{}/temp.txt'.format(TEXT_LOG_PATH) - with codecs.open(filename, 'r', encoding='utf-8') as fi, \ - codecs.open(temp_filename, 'w', encoding='utf-8') as fo: - + filename = "{}/{}.txt".format(TEXT_LOG_PATH, folder_name) + if Path(filename).is_file(): + temp_filename = "{}/temp.txt".format(TEXT_LOG_PATH) + with codecs.open(filename, "r", encoding="utf-8") as fi, codecs.open( + temp_filename, "w", encoding="utf-8" + ) as fo: for line in fi: line_id = line[:15] - if (line_id == log_id): - fo.write('{}, {}'.format(log_id, parsed_text)) + if line_id == log_id: + fo.write("{}, {}".format(log_id, parsed_text)) else: fo.write(line) @@ -230,43 +257,56 @@ def update_log_text(log_id, folder_name, text): os.rename(temp_filename, filename) return return - + + def insert_newest_log_with_image(base64_image_string, image_type): log = get_latest_log() - log['image'] = base64_image_string - log['image_type'] = image_type + log["image"] = base64_image_string + log["image_type"] = image_type eel.addLogs([log])() + def insert_newest_log_without_image(): eel.addLogs([get_latest_log()])() + @eel.expose def play_log_audio(file_name, folder_name): file = Path(AUDIO_LOG_PATH, folder_name, file_name) if file: return play_audio_from_file(str(file)) + @eel.expose def delete_audio_file(log_id, folder_name): file = get_audio_file_name(log_id, folder_name) - if (file): + if file: full_path = Path(AUDIO_LOG_PATH, folder_name, file) os.remove(full_path) return True else: return False + def get_audio_file_name(log_id, folder_name): path = Path(AUDIO_LOG_PATH, folder_name) if not path.is_dir(): return None - file_name = next((f for f in os.listdir(path) if re.match('{}.(?:wav|mp3|mp4|ogg|wma|aac|aiff|flv|m4a|flac)$'.format(log_id), f)), None) + file_name = next( + ( + f + for f in os.listdir(path) + if re.match("{}.(?:wav|mp3|mp4|ogg|wma|aac|aiff|flv|m4a|flac)$".format(log_id), f) + ), + None, + ) # Temporary fix for Mac OS: change file type since mp3 hasn't finished converting - if platform.system() == 'Darwin': - file_name = log_id + '.' + r_config(LOG_CONFIG, 'logaudiotype') + if platform.system() == "Darwin": + file_name = log_id + "." + r_config(LOG_CONFIG, "logaudiotype") return file_name + # Middleman for selected main window to launch add card in log window @eel.expose def highlight_text_in_logs(text): - eel.showCardWithSelectedText(text)() \ No newline at end of file + eel.showCardWithSelectedText(text)() diff --git a/ocr.py b/ocr.py index a7ca2ac..ebf8a64 100644 --- a/ocr.py +++ b/ocr.py @@ -9,17 +9,20 @@ HORIZONTAL_TEXT_DETECTION = 6 VERTICAL_TEXT_DETECTON = 5 + def get_temp_image_path(): - return str(Path(bundle_dir,"logs", "images", "temp.png")) + return str(Path(bundle_dir, "logs", "images", "temp.png")) + -def detect_and_log(engine, cropped_image, text_orientation, session_start_time, request_time, audio_recorder): +def detect_and_log(engine, cropped_image, text_orientation, session_start_time, request_time, audio_recorder): result = image_to_text(engine, cropped_image, text_orientation) if result is not None: log_text(session_start_time, request_time, result) log_media(session_start_time, request_time, audio_recorder) - return {'id': request_time, 'result': result } + return {"id": request_time, "result": result} else: - return {'error': 'OCR Failed'} + return {"error": "OCR Failed"} + def image_to_text(engine, base64img, text_orientation): if engine == "OCR Space USA" or engine == "OCR Space EU": @@ -27,26 +30,30 @@ def image_to_text(engine, base64img, text_orientation): image_path = base64_to_image_path(base64img, get_temp_image_path()) language = r_config(OCR_CONFIG, "ocr_space_language") return ocr_space_file(filename=image_path, language=language, url=api_url) - else: + else: # default to tesseract image = base64_to_image(base64img, get_temp_image_path()) return tesseract_ocr(image, text_orientation) + def tesseract_ocr(image, text_orientation): language = r_config(OCR_CONFIG, "tesseract_language") psm = HORIZONTAL_TEXT_DETECTION # Add English Tessdata for legacy Tesseract (English is included in v4 Japanese trained data) - is_legacy_tesseract = r_config(OCR_CONFIG, "oem") == '0' + is_legacy_tesseract = r_config(OCR_CONFIG, "oem") == "0" if is_legacy_tesseract: - language += '+eng' + language += "+eng" # Manual Vertical Text Orientation - if (text_orientation == 'vertical'): + if text_orientation == "vertical": psm = VERTICAL_TEXT_DETECTON language += "_vert" - custom_config = r'{} --oem {} --psm {} -c preserve_interword_spaces=1 {}'.format(get_tessdata_dir(), r_config(OCR_CONFIG, "oem"), psm, r_config(OCR_CONFIG, "extra_options").strip('"')) + custom_config = r"{} --oem {} --psm {} -c preserve_interword_spaces=1 {}".format( + get_tessdata_dir(), r_config(OCR_CONFIG, "oem"), psm, r_config(OCR_CONFIG, "extra_options").strip('"') + ) result = pytesseract.image_to_string(image, config=custom_config, lang=language) return result + tesseract_cmd = path_to_tesseract() if tesseract_cmd is not None: pytesseract.pytesseract.tesseract_cmd = tesseract_cmd diff --git a/ocr_space.py b/ocr_space.py index 78e5681..2ada513 100644 --- a/ocr_space.py +++ b/ocr_space.py @@ -1,12 +1,12 @@ - import requests -OCRSPACE_API_KEY = "" # Contact owner for developer key +OCRSPACE_API_KEY = "" # Contact owner for developer key OCRSPACE_API_URL_USA = "https://apipro1.ocr.space/parse/image" OCRSPACE_API_URL_EU = "https://apipro2.ocr.space/parse/image" -def ocr_space_file(filename, overlay=False, api_key=OCRSPACE_API_KEY, language='jpn', url=OCRSPACE_API_URL_EU): - """ OCR.space API request with local file. + +def ocr_space_file(filename, overlay=False, api_key=OCRSPACE_API_KEY, language="jpn", url=OCRSPACE_API_URL_EU): + """OCR.space API request with local file. Python3.5 - not tested on 2.7 :param filename: Your file path & name. :param overlay: Is OCR.space overlay required in your response. @@ -19,18 +19,20 @@ def ocr_space_file(filename, overlay=False, api_key=OCRSPACE_API_KEY, language=' :return: Result in JSON format. """ - payload = {'isOverlayRequired': overlay, - 'apikey': api_key, - 'language': language, - } - with open(filename, 'rb') as f: - r = requests.post(url, - files={filename: f}, - data=payload, - ) + payload = { + "isOverlayRequired": overlay, + "apikey": api_key, + "language": language, + } + with open(filename, "rb") as f: + r = requests.post( + url, + files={filename: f}, + data=payload, + ) result = r.json() - if (result): - if (result["ParsedResults"]): - parsedText = result["ParsedResults"][0]['ParsedText'] - return " ".join(parsedText.splitlines()) # force output to one line + if result: + if result["ParsedResults"]: + parsedText = result["ParsedResults"][0]["ParsedText"] + return " ".join(parsedText.splitlines()) # force output to one line return "Error: OCR Failed" diff --git a/recordaudio.py b/recordaudio.py index 9ffb8dc..3c57673 100644 --- a/recordaudio.py +++ b/recordaudio.py @@ -5,6 +5,7 @@ import platform from audio import valid_output_device, convert_audio + class RecordThread(threading.Thread): def __init__(self, deviceIndex=-1, frames=512): threading.Thread.__init__(self) @@ -30,52 +31,58 @@ def run(self): is_wasapi = (p.get_host_api_info_by_index(device_info["hostApi"])["name"]).find("WASAPI") != -1 useloopback = is_wasapi and not is_input # Open stream - channelcount = device_info["maxInputChannels"] if (device_info["maxOutputChannels"] < device_info["maxInputChannels"]) else device_info["maxOutputChannels"] + channelcount = ( + device_info["maxInputChannels"] + if (device_info["maxOutputChannels"] < device_info["maxInputChannels"]) + else device_info["maxOutputChannels"] + ) try: stream_parameters = { - 'format': pyaudio.paInt16, - 'channels': channelcount, - 'rate': int(device_info["defaultSampleRate"]), - 'input': True, - 'frames_per_buffer': self.frames, - 'input_device_index': device_info["index"] + "format": pyaudio.paInt16, + "channels": channelcount, + "rate": int(device_info["defaultSampleRate"]), + "input": True, + "frames_per_buffer": self.frames, + "input_device_index": device_info["index"], } - is_windows = (platform.system() == 'Windows') + is_windows = platform.system() == "Windows" if is_windows: - stream_parameters['as_loopback'] = useloopback + stream_parameters["as_loopback"] = useloopback stream = p.open(**stream_parameters) - + # Start recording self.isRecording = True self.hasAudio = False while self.bRecord: - self.recorded_frames.append(stream.read(self.frames, exception_on_overflow = True)) + self.recorded_frames.append(stream.read(self.frames, exception_on_overflow=True)) self.hasAudio = True stream.stop_stream() stream.close() # Don't save file if duration is 0 - if (self.duration == 0): + if self.duration == 0: p.terminate() return file = self.audiofile filename, file_extension = os.path.splitext(file) - file_needs_conversion = file_extension != '.wav' + file_needs_conversion = file_extension != ".wav" if file_needs_conversion: - file = filename + '.wav' - waveFile = wave.open(file, 'wb') + file = filename + ".wav" + waveFile = wave.open(file, "wb") waveFile.setnchannels(channelcount) waveFile.setsampwidth(p.get_sample_size(pyaudio.paInt16)) waveFile.setframerate(int(device_info["defaultSampleRate"])) start_frame = 0 - trim_audio = (self.duration != -1) + trim_audio = self.duration != -1 if trim_audio: - start_frame = len(self.recorded_frames) - int(int(device_info["defaultSampleRate"]) / self.frames * self.duration) - waveFile.writeframes(b''.join(self.recorded_frames[start_frame:])) + start_frame = len(self.recorded_frames) - int( + int(device_info["defaultSampleRate"]) / self.frames * self.duration + ) + waveFile.writeframes(b"".join(self.recorded_frames[start_frame:])) waveFile.close() p.terminate() @@ -85,9 +92,9 @@ def run(self): os.remove(file) except Exception as e: - print('Error: cannot record audio with selected device', e) - - def stop_recording(self, audiofile='out.wav', duration = 10): + print("Error: cannot record audio with selected device", e) + + def stop_recording(self, audiofile="out.wav", duration=10): self.audiofile = audiofile self.duration = duration self.bRecord = False @@ -99,4 +106,4 @@ def is_recording(self): return self.isRecording def has_audio(self): - return self.hasAudio \ No newline at end of file + return self.hasAudio diff --git a/textractor.py b/textractor.py index c7e71b2..08d0ae9 100644 --- a/textractor.py +++ b/textractor.py @@ -1,26 +1,30 @@ from parse import parse + # from pathlib import Path import time + # import re import os, sys + # import threading from tools import path_to_wexpect from util import RepeatedTimer import platform -is_windows = (platform.system() == 'Windows') +is_windows = platform.system() == "Windows" if is_windows: import wexpect -os.environ['WEXPECT_LOGGER_LEVEL']='INFO' +os.environ["WEXPECT_LOGGER_LEVEL"] = "INFO" + class Textractor(object): - def __init__(self, executable_path, callback, lines='', encoding='utf-8', codec_errors='ignore'): + def __init__(self, executable_path, callback, lines="", encoding="utf-8", codec_errors="ignore"): self.spawn(executable_path=executable_path, encoding=encoding, codec_errors=codec_errors) - self.lines = '' + self.lines = "" self.callback = callback - self.format_string = '[{handle}:{pid}:{addr}:{ctx}:{ctx2}:{name}:{code}]{text}' - self.format_grouped_string = '{prev_text}[{handle}:{pid}:{addr}:{ctx}:{ctx2}:{name}:{code}]{text}' + self.format_string = "[{handle}:{pid}:{addr}:{ctx}:{ctx2}:{name}:{code}]{text}" + self.format_grouped_string = "{prev_text}[{handle}:{pid}:{addr}:{ctx}:{ctx2}:{name}:{code}]{text}" self.pids_to_attach = [] self.attached_pids = [] self.flush_delay = 1 @@ -33,7 +37,7 @@ def get_attached_pids(self): def spawn(self, executable_path, encoding, codec_errors): real_executable = sys.executable try: - is_compiled_with_pyinstaller = (sys._MEIPASS is not None) + is_compiled_with_pyinstaller = sys._MEIPASS is not None if is_compiled_with_pyinstaller: sys.executable = path_to_wexpect() except AttributeError: @@ -44,20 +48,20 @@ def spawn(self, executable_path, encoding, codec_errors): def handle_output(self): if self.lines: output_objects = self.format_output(self.lines) - self.lines = '' + self.lines = "" if output_objects: self.emit_lines(output_objects) self.check_if_attached(output_objects) - + def check_if_attached(self, output_objects): for output_object in output_objects: - if output_object['code'] == 'Console': - if 'pipe connected' in output_object['text']: + if output_object["code"] == "Console": + if "pipe connected" in output_object["text"]: self.attached_pids = self.pids_to_attach def emit_lines(self, output_objects): self.callback(output_objects) - + def read(self): while 1: try: @@ -66,8 +70,8 @@ def read(self): self.lines += new_line self.flush_thread.reset() except wexpect.wexpect_util.TIMEOUT: - print('timeout') - if (self.flush_thread.is_running): + print("timeout") + if self.flush_thread.is_running: self.flush_thread.stop() self.handle_output() time.sleep(1) @@ -76,7 +80,7 @@ def read(self): def attach_multiple(self, pids): if len(self.attached_pids) > 0: for pid in self.attached_pids: - print('detaching...') + print("detaching...") self.detach(pid) self.pids_to_attach = [] for pid in pids: @@ -84,13 +88,13 @@ def attach_multiple(self, pids): self.pids_to_attach.append(pid) def attach(self, pid): - self.process.sendline('attach -P' + pid) + self.process.sendline("attach -P" + pid) def detach(self, pid): - self.process.sendline('detach -P' + pid) + self.process.sendline("detach -P" + pid) def hook(self, code, pid): - self.process.sendline(code + ' -P' + pid) + self.process.sendline(code + " -P" + pid) def group_text_by_key(self, raw_list, key): hookMap = {} @@ -99,7 +103,7 @@ def group_text_by_key(self, raw_list, key): if item[key] not in hookMap: hookMap[item[key]] = item else: - hookMap[item[key]]['text'] += item['text'] + hookMap[item[key]]["text"] += item["text"] return list(hookMap.values()) def remove_repeat(self, raw_list, key): @@ -108,8 +112,8 @@ def remove_repeat(self, raw_list, key): if index == 0: new_list.append(raw_list[index]) else: - is_repeat = raw_list[index][key] == raw_list[index-1][key] - is_same_hook = raw_list[index]['code'] == raw_list[index-1]['code'] + is_repeat = raw_list[index][key] == raw_list[index - 1][key] + is_same_hook = raw_list[index]["code"] == raw_list[index - 1]["code"] if (not is_repeat) or (not is_same_hook): new_list.append(raw_list[index]) return new_list @@ -118,43 +122,43 @@ def remove_repeat(self, raw_list, key): # return text.replace('/[\x00-\xFF]+/g,', '') def format_output(self, line): - print('newline', line) - if 'Usage:' in line: + print("newline", line) + if "Usage:" in line: # First line of Textractor output can include Textractor console information - if '[' in line: - lines = line.split('[') + if "[" in line: + lines = line.split("[") output_objects = [] for i in range(1, len(lines)): - result = parse(self.format_string, '[' + lines[i]) - if (result): + result = parse(self.format_string, "[" + lines[i]) + if result: output_objects.append(result.named) return output_objects else: - return '' - if '[' in line: - line = ''.join(line.splitlines()) + return "" + if "[" in line: + line = "".join(line.splitlines()) result = parse(self.format_string, line) previous_result = result output_objects = [] - if (result): - line = result.named['text'] - while (parse(self.format_grouped_string, line)): + if result: + line = result.named["text"] + while parse(self.format_grouped_string, line): result = parse(self.format_grouped_string, line) - line = ''.join(line.splitlines()) - line = result.named['text'] + line = "".join(line.splitlines()) + line = result.named["text"] previous_object = previous_result.named - previous_object['text'] = result.named['prev_text'] + previous_object["text"] = result.named["prev_text"] output_objects.append(previous_object) previous_result = result output_objects.append(result.named) - print('what i got') + print("what i got") for o in output_objects: - print(o['code'] + ' --> ' + o['text']) + print(o["code"] + " --> " + o["text"]) - output_objects = self.group_text_by_key(output_objects, 'code') - output_objects = self.remove_repeat(output_objects, 'text') + output_objects = self.group_text_by_key(output_objects, "code") + output_objects = self.remove_repeat(output_objects, "text") return output_objects else: - return None \ No newline at end of file + return None diff --git a/tools.py b/tools.py index b280d93..4afcb64 100644 --- a/tools.py +++ b/tools.py @@ -6,67 +6,78 @@ from tkinter.filedialog import askopenfile try: - is_compiled_with_pyinstaller = (sys._MEIPASS is not None) + is_compiled_with_pyinstaller = sys._MEIPASS is not None if is_compiled_with_pyinstaller: bundle_dir = sys._MEIPASS except AttributeError: bundle_dir = os.path.dirname(os.path.abspath(__file__)) - + OSX_TESSERACT_VERSION = "4.1.1" WIN_TESSERACT_DIR = Path(bundle_dir, "resources", "bin", "win", "tesseract") OSX_TESSERACT_DIR = Path(bundle_dir, "resources", "bin", "mac", "tesseract", OSX_TESSERACT_VERSION) + def path_to_ffmpeg(): platform_name = platform.system() - if platform_name == 'Windows': + if platform_name == "Windows": return str(Path(bundle_dir, "resources", "bin", "win", "ffmpeg", "ffmpeg.exe")) - elif platform_name == 'Darwin': + elif platform_name == "Darwin": return str(Path(bundle_dir, "resources", "bin", "mac", "ffmpeg", "ffmpeg")) return None + def path_to_tesseract(): - exec_data = {"Windows": str(Path(WIN_TESSERACT_DIR, "tesseract.exe")), - "Darwin": str(Path(OSX_TESSERACT_DIR, "bin", "tesseract")), - } + exec_data = { + "Windows": str(Path(WIN_TESSERACT_DIR, "tesseract.exe")), + "Darwin": str(Path(OSX_TESSERACT_DIR, "bin", "tesseract")), + } platform_name = platform.system() # E.g. 'Windows' return exec_data.get(platform_name) + def get_tessdata_dir(): - platform_name = platform.system() - if platform_name == 'Darwin': - if r_config(OCR_CONFIG, "oem") == '0': + platform_name = platform.system() + if platform_name == "Darwin": + if r_config(OCR_CONFIG, "oem") == "0": # legacy tesseract - return '--tessdata-dir {}'.format(str(Path(OSX_TESSERACT_DIR, "share", "legacy", "tessdata"))) + return "--tessdata-dir {}".format(str(Path(OSX_TESSERACT_DIR, "share", "legacy", "tessdata"))) else: - return '--tessdata-dir {}'.format(str(Path(OSX_TESSERACT_DIR, "share", "tessdata"))) - elif platform_name == 'Windows': - if (r_config(OCR_CONFIG, "oem") == '0' and Path(WIN_TESSERACT_DIR, "tessdata-legacy").exists()): + return "--tessdata-dir {}".format(str(Path(OSX_TESSERACT_DIR, "share", "tessdata"))) + elif platform_name == "Windows": + if r_config(OCR_CONFIG, "oem") == "0" and Path(WIN_TESSERACT_DIR, "tessdata-legacy").exists(): # legacy tesseract by renaming tessdata folders os.rename(Path(WIN_TESSERACT_DIR, "tessdata"), Path(WIN_TESSERACT_DIR, "tessdata-new")) os.rename(Path(WIN_TESSERACT_DIR, "tessdata-legacy"), Path(WIN_TESSERACT_DIR, "tessdata")) - elif (r_config(OCR_CONFIG, "oem") != '0' and Path(WIN_TESSERACT_DIR, "tessdata-new").exists()): + elif r_config(OCR_CONFIG, "oem") != "0" and Path(WIN_TESSERACT_DIR, "tessdata-new").exists(): # revert to default tessdata folder - os.rename(Path(WIN_TESSERACT_DIR, "tessdata"), Path(WIN_TESSERACT_DIR, "tessdata-legacy")) + os.rename(Path(WIN_TESSERACT_DIR, "tessdata"), Path(WIN_TESSERACT_DIR, "tessdata-legacy")) os.rename(Path(WIN_TESSERACT_DIR, "tessdata-new"), Path(WIN_TESSERACT_DIR, "tessdata")) - return '' + return "" + def path_to_textractor(): - path = r_config('PATHS', 'textractor') - return path if path != 'default' else str(Path(bundle_dir, 'resources', 'bin', 'win', 'textractor', 'TextractorCLI.exe')) + path = r_config("PATHS", "textractor") + return ( + path + if path != "default" + else str(Path(bundle_dir, "resources", "bin", "win", "textractor", "TextractorCLI.exe")) + ) + def path_to_wexpect(): - return str(Path(bundle_dir, 'resources', 'bin', 'win', 'wexpect', 'wexpect.exe')) + return str(Path(bundle_dir, "resources", "bin", "win", "wexpect", "wexpect.exe")) + def open_folder_textractor_path(): root = Tk() root.withdraw() - file = askopenfile(filetypes = (("EXE files","*.exe"),("all files","*.*")), defaultextension=".exe") + file = askopenfile(filetypes=(("EXE files", "*.exe"), ("all files", "*.*")), defaultextension=".exe") if not file: return try: - w_config(PATHS_CONFIG, {'textractor': file.name}) + w_config(PATHS_CONFIG, {"textractor": file.name}) except: - print('File not selected') + print("File not selected") file.close() root.destroy() - return file.name \ No newline at end of file + return file.name diff --git a/translate.py b/translate.py index 0b30261..5ebc817 100644 --- a/translate.py +++ b/translate.py @@ -3,51 +3,57 @@ import time from config import r_config, TRANSLATION_CONFIG + def multi_translate(text): - service = r_config(TRANSLATION_CONFIG, 'translation_service') - if service == 'DeepL Translate': + service = r_config(TRANSLATION_CONFIG, "translation_service") + if service == "DeepL Translate": return deepl_translate(text) - elif service == 'Google Translate': + elif service == "Google Translate": return google_translate(text) else: - return 'Error: No Translation Service Available' + return "Error: No Translation Service Available" + def deepl_translate(text): text = text[:140] if len(text) > 140 else text response = requests.post( - "https://www2.deepl.com/jsonrpc", - json = { - "jsonrpc":"2.0", - "method": "LMT_handle_jobs", - "params": { - "jobs":[{ - "kind":"default", - "raw_en_sentence": text, - "raw_en_context_before":[], - "raw_en_context_after":[], - "preferred_num_beams":4, - "quality":"fast" - }], - "lang":{ - "user_preferred_langs":["EN"], - "source_lang_user_selected": r_config(TRANSLATION_CONFIG, "source_lang").upper() or "JA", - "target_lang": r_config(TRANSLATION_CONFIG, "target_lang").upper() or "EN" + "https://www2.deepl.com/jsonrpc", + json={ + "jsonrpc": "2.0", + "method": "LMT_handle_jobs", + "params": { + "jobs": [ + { + "kind": "default", + "raw_en_sentence": text, + "raw_en_context_before": [], + "raw_en_context_after": [], + "preferred_num_beams": 4, + "quality": "fast", + } + ], + "lang": { + "user_preferred_langs": ["EN"], + "source_lang_user_selected": r_config(TRANSLATION_CONFIG, "source_lang").upper() or "JA", + "target_lang": r_config(TRANSLATION_CONFIG, "target_lang").upper() or "EN", + }, + "priority": -1, + "commonJobParams": {}, + "timestamp": int(round(time.time() * 1000)), }, - "priority":-1, - "commonJobParams":{}, - "timestamp": int(round(time.time() * 1000)) + "id": 40890008, }, - "id": 40890008 - }) + ) output = response.json() if output is not None: - if 'result' in output: - return output['result']['translations'][0]['beams'][0]["postprocessed_sentence"] - if 'error' in output: - return 'Error: ' + output['error']['message'] - return 'Failed to Translate' + if "result" in output: + return output["result"]["translations"][0]["beams"][0]["postprocessed_sentence"] + if "error" in output: + return "Error: " + output["error"]["message"] + return "Failed to Translate" + def google_translate(text): # ts.preaccelerate() - result = ts.translate_text(text, 'google') - return result \ No newline at end of file + result = ts.translate_text(text, "google") + return result diff --git a/util.py b/util.py index 242920e..b35d4d1 100644 --- a/util.py +++ b/util.py @@ -7,19 +7,21 @@ import cv2 import eel from tools import bundle_dir + try: from winreg import HKEY_CURRENT_USER, OpenKey, QueryValueEx except ImportError: - if platform.system() == 'Windows': - print('failed to import winreg') + if platform.system() == "Windows": + print("failed to import winreg") + class RepeatedTimer(object): def __init__(self, interval, function, *args, **kwargs): - self._timer = None - self.interval = interval - self.function = function - self.args = args - self.kwargs = kwargs + self._timer = None + self.interval = interval + self.function = function + self.args = args + self.kwargs = kwargs self.is_running = False self.start() @@ -43,103 +45,111 @@ def reset(self): self.stop() self.start() + def create_directory_if_not_exists(filename): if not os.path.exists(os.path.dirname(filename)): try: os.makedirs(os.path.dirname(filename)) - except OSError as exc: # Guard against race condition + except OSError as exc: # Guard against race condition if exc.errno != errno.EEXIST: raise + @eel.expose def open_folder_by_relative_path(relative_path): - platform_name = platform.system() - if platform_name == 'Windows': + platform_name = platform.system() + if platform_name == "Windows": path = os.path.realpath(str(Path(bundle_dir, relative_path))) os.startfile(path) + def base64_to_image(base64string, path): image_path = base64_to_image_path(base64string, path) img = cv2.imread(image_path) return img + # Saves base64 image string and returns path def base64_to_image_path(base64string, path): with open(path, "wb") as fh: fh.write(base64.b64decode(base64string)) return path + def get_default_browser_name(): - platform_name = platform.system() - if platform_name == 'Windows': - with OpenKey(HKEY_CURRENT_USER, r"Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice") as key: - browser = QueryValueEx(key, 'Progid')[0] - browser_map = { - 'ChromeHTML': 'chrome', - 'FirefoxURL': 'chromium', - 'IE.HTTP': 'edge' - } + platform_name = platform.system() + if platform_name == "Windows": + with OpenKey( + HKEY_CURRENT_USER, r"Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice" + ) as key: + browser = QueryValueEx(key, "Progid")[0] + browser_map = {"ChromeHTML": "chrome", "FirefoxURL": "chromium", "IE.HTTP": "edge"} if browser in browser_map: return browser_map[browser] else: - return 'chromium' - return 'chrome' + return "chromium" + return "chrome" + def get_PID_list(): - processes = [proc.name() + ' ' + str(proc.pid) for proc in psutil.process_iter()] + processes = [proc.name() + " " + str(proc.pid) for proc in psutil.process_iter()] processes.sort() pids = [] for process in processes: - name = process.split(' ')[0] - pid = process.split(' ')[1] + name = process.split(" ")[0] + pid = process.split(" ")[1] if len(pids) == 0: - pids.append({'name': name, 'pids':[pid]}) - elif name != pids[-1]['name']: - pids.append({'name': name, 'pids':[pid]}) + pids.append({"name": name, "pids": [pid]}) + elif name != pids[-1]["name"]: + pids.append({"name": name, "pids": [pid]}) else: - pids[-1]['pids'].append(pid) + pids[-1]["pids"].append(pid) return pids + def remove_spaces(s): return "".join(s.split()) + def remove_duplicate_characters(sentence): chars = list(sentence) prev = None k = 0 - + for c in sentence: if prev != c: chars[k] = c prev = c k = k + 1 - - return ''.join(chars[:k]) + + return "".join(chars[:k]) + def quick_remove_repeated_phrases(s): - prefix_array=[] + prefix_array = [] for i in range(len(s)): prefix_array.append(s[:i]) - #stop at 1st element to avoid checking for the ' ' char + # stop at 1st element to avoid checking for the ' ' char for i in prefix_array[:1:-1]: - if s.count(i) > 1 : - #find where the next repetition starts - offset = s[len(i):].find(i) + if s.count(i) > 1: + # find where the next repetition starts + offset = s[len(i) :].find(i) - return s[:len(i)+offset] + return s[: len(i) + offset] break return s + def brute_remove_repeated_phrases(sentence): head = 1 while 1: - scan_sentence = sentence[head:len(sentence)] + scan_sentence = sentence[head : len(sentence)] prefix = sentence[0:head] if prefix in scan_sentence: - sentence = sentence.replace(prefix, '', 1) + sentence = sentence.replace(prefix, "", 1) head = 0 pass head += 1 @@ -147,19 +157,21 @@ def brute_remove_repeated_phrases(sentence): break return sentence + def quick_and_brute_remove_repeated_phrases(sentence): return brute_remove_repeated_phrases(quick_remove_repeated_phrases(sentence)) + def format_output(output_objects, remove_repeat_mode, is_remove_duplicates, is_remove_spaces): remove_repeat_dict = { - 'quick': quick_remove_repeated_phrases, - 'brute force': brute_remove_repeated_phrases, - 'quick + brute force': quick_and_brute_remove_repeated_phrases + "quick": quick_remove_repeated_phrases, + "brute force": brute_remove_repeated_phrases, + "quick + brute force": quick_and_brute_remove_repeated_phrases, } for output in output_objects: - output['text'] = output['text'].strip() + output["text"] = output["text"].strip() if remove_repeat_mode in remove_repeat_dict: - remove_repeat_dict[remove_repeat_mode](output['text']) - output['text'] = remove_duplicate_characters(output['text']) if is_remove_duplicates else output['text'] - output['text'] = remove_spaces(output['text']) if is_remove_spaces else output['text'] - return output_objects \ No newline at end of file + remove_repeat_dict[remove_repeat_mode](output["text"]) + output["text"] = remove_duplicate_characters(output["text"]) if is_remove_duplicates else output["text"] + output["text"] = remove_spaces(output["text"]) if is_remove_spaces else output["text"] + return output_objects From 443ea8337f7cda9665b68abb51a4631b9b54915c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 18 Sep 2023 01:18:20 +0200 Subject: [PATCH 09/10] Reformat all files with isort --- ankiconnect.py | 8 +++++--- audio.py | 10 ++++++---- clipboard.py | 2 +- config.py | 2 +- dictionary.py | 15 ++++++++------- game2text.py | 35 ++++++++++++++++++++--------------- gamescript.py | 6 ++++-- hotkeys.py | 3 ++- imageprofile.py | 6 ++++-- logger.py | 20 +++++++++++--------- ocr.py | 12 +++++++----- recordaudio.py | 10 ++++++---- textractor.py | 9 +++++---- tools.py | 6 ++++-- translate.py | 8 +++++--- util.py | 10 ++++++---- 16 files changed, 95 insertions(+), 67 deletions(-) diff --git a/ankiconnect.py b/ankiconnect.py index d63587a..568a917 100644 --- a/ankiconnect.py +++ b/ankiconnect.py @@ -1,11 +1,13 @@ import json import urllib.request +from pathlib import Path + import eel import yaml -from pathlib import Path -from logger import AUDIO_LOG_PATH -from config import r_config, ANKI_CONFIG + +from config import ANKI_CONFIG, r_config from dictionary import get_jpod_audio_base64 +from logger import AUDIO_LOG_PATH from tools import bundle_dir ANKI_MODELS_FILENAME = "ankimodels.yaml" diff --git a/audio.py b/audio.py index 0f53209..7472dd7 100644 --- a/audio.py +++ b/audio.py @@ -1,11 +1,13 @@ -from pydub import AudioSegment -from pydub.playback import play -import pyaudio import os import platform from pathlib import Path + import eel -from config import r_config, LOG_CONFIG +import pyaudio +from pydub import AudioSegment +from pydub.playback import play + +from config import LOG_CONFIG, r_config from tools import path_to_ffmpeg ffmpeg_path = path_to_ffmpeg() diff --git a/clipboard.py b/clipboard.py index a5bd8c5..eb37b32 100644 --- a/clipboard.py +++ b/clipboard.py @@ -1,5 +1,5 @@ -import pyperclip import eel +import pyperclip previous_text = "" diff --git a/config.py b/config.py index 05f9536..d02cd3a 100644 --- a/config.py +++ b/config.py @@ -1,6 +1,6 @@ -from configparser import ConfigParser import os import platform +from configparser import ConfigParser OCR_CONFIG = "OCRCONFIG" TRANSLATION_CONFIG = "TRANSLATIONCONFIG" diff --git a/dictionary.py b/dictionary.py index 4e52605..cdc0f61 100644 --- a/dictionary.py +++ b/dictionary.py @@ -1,13 +1,14 @@ -import zipfile +import base64 +import glob import json +import zipfile +from pathlib import Path + import requests -import base64 -from sudachipy import tokenizer -from sudachipy import dictionary +from sudachipy import dictionary, tokenizer + +from config import ANKI_CONFIG, r_config from tools import bundle_dir -from pathlib import Path -from config import r_config, ANKI_CONFIG -import glob dictionary_map = {} pitch_dictionary_map = {} diff --git a/game2text.py b/game2text.py index 0254453..3810f50 100644 --- a/game2text.py +++ b/game2text.py @@ -1,22 +1,27 @@ -import eel -import threading, os, platform, time +import os +import platform +import threading +import time from pathlib import Path -from ocr import detect_and_log -from translate import multi_translate -from hotkeys import hotkey_map -from util import RepeatedTimer, create_directory_if_not_exists, get_default_browser_name, get_PID_list, format_output -from textractor import Textractor -from tools import path_to_textractor, open_folder_textractor_path -from audio import get_recommended_device_index -from recordaudio import RecordThread + +import eel from pynput import keyboard + +from ankiconnect import create_anki_note, fetch_anki_fields, get_anki_models, invoke, update_anki_models +from audio import get_recommended_device_index from clipboard import clipboard_to_output, text_to_clipboard -from logger import get_time_string, log_text, log_media, update_log_text, AUDIO_LOG_PATH -from ankiconnect import invoke, get_anki_models, update_anki_models, create_anki_note, fetch_anki_fields -from imageprofile import export_image_profile, load_image_profiles, open_image_profile +from config import APP_CONFIG, LOG_CONFIG, TEXTHOOKER_CONFIG, r_config, r_config_all, r_config_section, w_config +from dictionary import get_jpod_audio_url, get_local_dictionaries, load_all_dictionaries, load_dictionary, look_up from gamescript import load_game_scripts, open_game_script -from dictionary import load_all_dictionaries, look_up, get_local_dictionaries, load_dictionary, get_jpod_audio_url -from config import r_config, r_config_all, r_config_section, w_config, APP_CONFIG, LOG_CONFIG, TEXTHOOKER_CONFIG +from hotkeys import hotkey_map +from imageprofile import export_image_profile, load_image_profiles, open_image_profile +from logger import AUDIO_LOG_PATH, get_time_string, log_media, log_text, update_log_text +from ocr import detect_and_log +from recordaudio import RecordThread +from textractor import Textractor +from tools import open_folder_textractor_path, path_to_textractor +from translate import multi_translate +from util import RepeatedTimer, create_directory_if_not_exists, format_output, get_default_browser_name, get_PID_list session_start_time = get_time_string() textractor = None diff --git a/gamescript.py b/gamescript.py index 717c585..c1bf413 100644 --- a/gamescript.py +++ b/gamescript.py @@ -1,10 +1,12 @@ import glob from pathlib import Path +from shutil import copyfile from tkinter import * from tkinter.filedialog import askopenfile -from shutil import copyfile + from fuzzywuzzy import process -from config import r_config, w_config, LOG_CONFIG, SCRIPT_MATCH_CONFIG + +from config import LOG_CONFIG, SCRIPT_MATCH_CONFIG, r_config, w_config from tools import bundle_dir GAME_SCRIPT_PATH = Path(bundle_dir, "gamescripts") diff --git a/hotkeys.py b/hotkeys.py index b04ea83..ead23ab 100644 --- a/hotkeys.py +++ b/hotkeys.py @@ -1,5 +1,6 @@ import eel -from config import r_config, HOTKEYS_CONFIG + +from config import HOTKEYS_CONFIG, r_config def refresh_ocr_hotkey(): diff --git a/imageprofile.py b/imageprofile.py index 302786f..8789116 100644 --- a/imageprofile.py +++ b/imageprofile.py @@ -1,8 +1,10 @@ -import yaml import glob from pathlib import Path from tkinter import * -from tkinter.filedialog import asksaveasfile, askopenfile +from tkinter.filedialog import askopenfile, asksaveasfile + +import yaml + from tools import bundle_dir IMAGE_PROFILE_PATH = Path(bundle_dir, "profiles") diff --git a/logger.py b/logger.py index dc865e2..b0e91d8 100644 --- a/logger.py +++ b/logger.py @@ -1,19 +1,21 @@ -import time -import os -import re -import eel -import glob import base64 import codecs -import threading +import glob +import os import platform -from pathlib import Path +import re +import threading +import time from datetime import datetime -from config import r_config, LOG_CONFIG -from util import create_directory_if_not_exists, base64_to_image_path +from pathlib import Path + +import eel + from audio import play_audio_from_file +from config import LOG_CONFIG, r_config from gamescript import add_matching_script_to_logs from tools import bundle_dir +from util import base64_to_image_path, create_directory_if_not_exists TEXT_LOG_PATH = Path(bundle_dir, "logs", "text") IMAGE_LOG_PATH = Path(bundle_dir, "logs", "images") diff --git a/ocr.py b/ocr.py index ebf8a64..5239569 100644 --- a/ocr.py +++ b/ocr.py @@ -1,10 +1,12 @@ -import pytesseract from pathlib import Path -from logger import log_text, log_media -from config import r_config, OCR_CONFIG + +import pytesseract + +from config import OCR_CONFIG, r_config +from logger import log_media, log_text +from ocr_space import OCRSPACE_API_URL_EU, OCRSPACE_API_URL_USA, ocr_space_file +from tools import bundle_dir, get_tessdata_dir, path_to_tesseract from util import base64_to_image, base64_to_image_path -from tools import path_to_tesseract, get_tessdata_dir, bundle_dir -from ocr_space import ocr_space_file, OCRSPACE_API_URL_USA, OCRSPACE_API_URL_EU HORIZONTAL_TEXT_DETECTION = 6 VERTICAL_TEXT_DETECTON = 5 diff --git a/recordaudio.py b/recordaudio.py index 3c57673..113d21a 100644 --- a/recordaudio.py +++ b/recordaudio.py @@ -1,9 +1,11 @@ -import threading -import pyaudio -import wave import os import platform -from audio import valid_output_device, convert_audio +import threading +import wave + +import pyaudio + +from audio import convert_audio, valid_output_device class RecordThread(threading.Thread): diff --git a/textractor.py b/textractor.py index 08d0ae9..d4233eb 100644 --- a/textractor.py +++ b/textractor.py @@ -1,15 +1,16 @@ -from parse import parse +# import re +import os +import platform +import sys # from pathlib import Path import time -# import re -import os, sys +from parse import parse # import threading from tools import path_to_wexpect from util import RepeatedTimer -import platform is_windows = platform.system() == "Windows" if is_windows: diff --git a/tools.py b/tools.py index 4afcb64..63d23d7 100644 --- a/tools.py +++ b/tools.py @@ -1,10 +1,12 @@ -import sys, os +import os import platform +import sys from pathlib import Path -from config import r_config, w_config, OCR_CONFIG, PATHS_CONFIG from tkinter import * from tkinter.filedialog import askopenfile +from config import OCR_CONFIG, PATHS_CONFIG, r_config, w_config + try: is_compiled_with_pyinstaller = sys._MEIPASS is not None if is_compiled_with_pyinstaller: diff --git a/translate.py b/translate.py index 5ebc817..90c31c3 100644 --- a/translate.py +++ b/translate.py @@ -1,7 +1,9 @@ -import translators as ts -import requests import time -from config import r_config, TRANSLATION_CONFIG + +import requests +import translators as ts + +from config import TRANSLATION_CONFIG, r_config def multi_translate(text): diff --git a/util.py b/util.py index b35d4d1..77df3d4 100644 --- a/util.py +++ b/util.py @@ -1,11 +1,13 @@ -from threading import Timer -from pathlib import Path +import base64 import os -import psutil import platform -import base64 +from pathlib import Path +from threading import Timer + import cv2 import eel +import psutil + from tools import bundle_dir try: From 67154ad16872a1cb55a87ef13b218eb6e84ef3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 18 Sep 2023 01:21:06 +0200 Subject: [PATCH 10/10] ci: add a GitHub action to check formatting --- .github/workflows/linters.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/linters.yml diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml new file mode 100644 index 0000000..9a09382 --- /dev/null +++ b/.github/workflows/linters.yml @@ -0,0 +1,27 @@ +name: Linters + +on: + pull_request: + branches: + - main + +jobs: + + isort: + name: isort + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: isort/isort-action@v1.1.0 + with: + configuration: "--check --diff" + + black: + name: black + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: psf/black@stable + with: + options: "--check --diff" + version: "~= 23.0"