Skip to content
This repository has been archived by the owner on Dec 9, 2024. It is now read-only.

Feature: Notification Sound Effects #21

Merged
merged 19 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4fdd149
Installed playsound package and implimented function to call audio file.
Rumsie-Grimshaw Jun 9, 2024
73646af
Added docstring for playsound_notification function.
Rumsie-Grimshaw Jun 9, 2024
be44b15
Modified function to include users absolutepath to audiofiles.
Rumsie-Grimshaw Jun 9, 2024
c8ff026
Added notification sounds to send_to_ide().
Rumsie-Grimshaw Jun 9, 2024
a5e2f4e
Moved playsound_notification() to utils.py. Added notification sound …
Rumsie-Grimshaw Jun 9, 2024
d7fc1b1
Added playsound_notifications to extract_code_at_timestamp.
Rumsie-Grimshaw Jun 9, 2024
e7fd560
Moved the notification function to utils.
Rumsie-Grimshaw Jun 9, 2024
9956397
Moved notification sound to adjust for loading delay between sound an…
Rumsie-Grimshaw Jun 9, 2024
11c9cb1
Removed hard coded test notification on startup. fixes #20.
Rumsie-Grimshaw Jun 9, 2024
9cc8206
Removed playsound version 1.3.0 from requirments due to using a previ…
Rumsie-Grimshaw Jun 9, 2024
b2cc688
Applied fix for flake-8 linting fix error.
Rumsie-Grimshaw Jun 9, 2024
303a0c1
Final linting fix for build error.
Rumsie-Grimshaw Jun 9, 2024
bc94387
Refactored project folder into file_path string construction for soun…
Rumsie-Grimshaw Jun 11, 2024
4fde641
Merged updates from upstream repo.
Rumsie-Grimshaw Jun 11, 2024
8ad8166
Added mp3 files to be played when calling soundplay_noticiation.
Rumsie-Grimshaw Jun 11, 2024
29451f0
Added tests to verify audio file pathing is constructed correctly and…
Rumsie-Grimshaw Jun 11, 2024
b9c7bf7
Build errors / linting fixes.
Rumsie-Grimshaw Jun 11, 2024
9e42fad
Linting fixes.
Rumsie-Grimshaw Jun 11, 2024
1771df2
final linting fix
Rumsie-Grimshaw Jun 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"


Expand Down
3 changes: 3 additions & 0 deletions app/extract_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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
Expand Down
Binary file added app/static/audio/capture.mp3
Binary file not shown.
Binary file added app/static/audio/error.mp3
Binary file not shown.
Binary file added app/static/audio/success.mp3
Binary file not shown.
38 changes: 19 additions & 19 deletions app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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


Expand All @@ -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)


Expand All @@ -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)

Expand Down Expand Up @@ -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():
Expand All @@ -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,
Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
28 changes: 23 additions & 5 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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)
Loading