diff --git a/app/app.py b/app/app.py index a15ffe2..650e5c6 100644 --- a/app/app.py +++ b/app/app.py @@ -137,8 +137,10 @@ def send_to_ide(): code = request.get_json().get("code_snippet") unescaped_code = html.unescape(code) if utils.send_code_snippet_to_ide(filename, unescaped_code): + utils.playsound_notification("success.mp3") return "success" else: + utils.playsound_notification("error.mp3") return "fail" diff --git a/app/extract_text.py b/app/extract_text.py index d3b864a..02aaf4c 100644 --- a/app/extract_text.py +++ b/app/extract_text.py @@ -26,12 +26,14 @@ def extract_code_at_timestamp(filename: str, timestamp: float) -> str: :param timestamp: Time stamp of the frame to extract :return: Formatted code as a string """ + utils.playsound_notification("capture.mp3") frame = ExtractText.extract_frame_at_timestamp(filename, timestamp) if frame is not None: extracted_text = pytesseract.image_to_string(frame) logging.info(f"Successfully extracted code from frame @ {timestamp}s in file {filename}") return ExtractText.format_raw_ocr_string(extracted_text) else: + utils.playsound_notification("capture_fail_tone.wav") logging.error(f"Unable to extract code from frame @ {timestamp}s in file {filename}") return "ERROR" @@ -50,6 +52,7 @@ def format_raw_ocr_string(extracted_text: str) -> str: formatted_text = formatted_text.replace("```", "") if config("Formatting", "remove_language_name"): formatted_text = formatted_text.replace(language, "", 1) + utils.playsound_notification("success.mp3") return formatted_text @staticmethod diff --git a/app/static/audio/capture.mp3 b/app/static/audio/capture.mp3 new file mode 100644 index 0000000..52aa2a0 Binary files /dev/null and b/app/static/audio/capture.mp3 differ diff --git a/app/static/audio/error.mp3 b/app/static/audio/error.mp3 new file mode 100644 index 0000000..6a86ef0 Binary files /dev/null and b/app/static/audio/error.mp3 differ diff --git a/app/static/audio/success.mp3 b/app/static/audio/success.mp3 new file mode 100644 index 0000000..a0f2e2d Binary files /dev/null and b/app/static/audio/success.mp3 differ diff --git a/app/utils.py b/app/utils.py index cb3190a..3d050a3 100644 --- a/app/utils.py +++ b/app/utils.py @@ -8,17 +8,34 @@ import time import cv2 from json import JSONDecodeError -from typing import Union, Optional, Any, List, Dict +from typing import Union, Optional, Any, List import openai import pytesseract from pytube import YouTube from pytube.exceptions import RegexMatchError from configparser import ConfigParser +from playsound import playsound + + +def playsound_notification(audio_file): + """ + Play a notification sound. Requires the playsound package to be installed. + :args: audio_file: Path to the audio_file in the app/static/audio folder + """ + file_path = os.getcwd() + file_path += "//static/audio/" + file_name = audio_file + file_path = os.path.join(file_path, file_name) + print(file_path) + if audio_file is not None: + playsound(file_path) + FILE_PATH = Path(__file__).resolve() # Absolute Path of utils.py APP_DIR = FILE_PATH.parent PROJ_ROOT = APP_DIR.parent + def config(section: str = None, option: str = None) -> Union[ConfigParser, str]: """ Loads config variables from file and returns either specified variable or parser object. If attempting to @@ -28,7 +45,6 @@ def config(section: str = None, option: str = None) -> Union[ConfigParser, str]: :param option: [Optional] Key/option of value to retrieve :return: Return string or ConfigParser object """ - if (section is None) != (option is None): raise SyntaxError("section AND option parameters OR no parameters must be passed to function config()") parser = ConfigParser() @@ -113,14 +129,11 @@ def directory_append_slash(directory: str) -> str: :param directory: The directory path to which a trailing slash will be appended, if missing. :return: The directory path with a trailing slash appended, if necessary. """ - # Check if directory already have trailing slash if directory.endswith(('/', '\\')): return directory - # Append trailing slash - directory += '\\' if os.name == 'nt' else '/' - + directory += '\\' if os.name == 'nt' else '/' return directory @@ -130,18 +143,14 @@ def get_vid_save_path() -> str: :return: file path as string """ vid_download_path = config("UserSettings", "video_save_path") - # Set default output path for video download path if vid_download_path == "output_path": default_path = PROJ_ROOT / 'out' / 'videos' if not default_path.exists(): default_path.mkdir(parents=True, exist_ok=True) - default_path = str(default_path) return directory_append_slash(default_path) - vid_download_path = str(Path(vid_download_path)) - return directory_append_slash(vid_download_path) @@ -152,16 +161,12 @@ def get_output_path() -> str: """ output_path = config("UserSettings", "capture_output_path") # Set default output path for code files - if output_path == "output_path": default_path = PROJ_ROOT / 'out' if not default_path.exists(): default_path.mkdir(parents=True, exist_ok=True) - default_path = str(default_path) - return directory_append_slash(default_path) - output_path = str(Path(output_path)) return directory_append_slash(output_path) @@ -311,7 +316,6 @@ def add_video_to_user_data(filename: str, video_title: str, video_hash: str, you user_data = read_user_data() if user_data is None: return - video_path = str(Path(get_vid_save_path(), f'{filename}').resolve()) video_capture = cv2.VideoCapture(video_path) if not video_capture.isOpened(): @@ -326,12 +330,10 @@ def add_video_to_user_data(filename: str, video_title: str, video_hash: str, you return thumbnail = str(int(time.time())) + ".png" # Check if img dir exists if not create - static_dir = APP_DIR / 'static' img_dir = static_dir / 'img' if not img_dir.exists(): img_dir.mkdir(parents=True, exist_ok=True) - cv2.imwrite(str(img_dir / f'{thumbnail}'), frame) new_video = { "video_hash": video_hash, @@ -480,7 +482,6 @@ def delete_video_from_userdata(filename: str) -> None: if current_video["filename"] == filename: all_videos.remove(current_video) break - with (APP_DIR / 'data' / 'userdata.json').open('w') as json_data: json.dump(user_data, json_data, indent=4) @@ -501,7 +502,6 @@ def update_configuration(new_values_dict) -> None: if isinstance(value, bool) or isinstance(value, int): value = str(value) config_file.set(section, key, value) - # save the file with (APP_DIR / 'config.ini').open('w') as config_file_save: config_file.write(config_file_save) diff --git a/requirements.txt b/requirements.txt index 232a124..06f3d8f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,6 +18,7 @@ openai==0.28.1 opencv-python==4.8.1.78 packaging==23.2 Pillow==10.1.0 +playsound==1.2.2 pydantic==2.4.2 pydantic_core==2.10.1 pytesseract==0.3.10 @@ -28,4 +29,4 @@ typing_extensions==4.8.0 Werkzeug==3.0.1 pytest==7.4.3 pytest-cov==4.1.0 -pytest-mock==3.12.0 +pytest-mock==3.12.0 \ No newline at end of file diff --git a/tests/test_utils.py b/tests/test_utils.py index 82106dc..b6b8612 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -119,25 +119,25 @@ def test_hash_string(): def test_get_output_path(mocker): project_root = str(utils.PROJ_ROOT) default_path = utils.directory_append_slash(os.path.join(project_root, 'out')) - + test_output_paths = { "output_path": default_path, } - + assert os.name in ['nt', 'posix'] - + # Windows if os.name == 'nt': test_output_paths["c:\\users\\program files\\app"] = "c:\\users\\program files\\app\\" test_output_paths["videos\\my_videos\\"] = "videos\\my_videos\\" - + # Linux or macOS (Note: GitHub Runner is using ubuntu) else: assert os.name == 'posix' users_dir = 'home' if 'home' in os.path.expanduser("~") else 'Users' test_output_paths[f"/{users_dir}/program_files/app"] = f"/{users_dir}/program_files/app/" test_output_paths["videos/my_videos/"] = "videos/my_videos/" - + for paths in test_output_paths: mocker.patch("app.utils.config", return_value=paths) assert utils.get_output_path() == test_output_paths[paths] @@ -156,3 +156,21 @@ def test_file_already_exists_false(mocker): def test_file_already_exists_no_user_data(mocker): mocker.patch("app.utils.read_user_data", return_value=None) assert not utils.file_already_exists("4aj3sdl5a4k2sjd091u091j") + + +def test_audio_file_exists_in_audio_file_directory(): + file_path = os.getcwd() + file_path += "//static/audio/" + edit_path = file_path.replace('tests', 'app') + audio_file = 'success.mp3' + file_path = os.path.join(edit_path, audio_file) + assert os.path.exists(file_path) + + +def test_audio_file_does_not_exist_in_audio_file_directory(): + file_path = os.getcwd() + file_path += "//static/audio/" + edit_path = file_path.replace('tests', 'app') + audio_file = 'non_existent_file.mp3' + file_path = os.path.join(edit_path, audio_file) + assert not os.path.exists(file_path)