fix config issue, add args parse, cleanup sys tray

This commit is contained in:
Dobromir Popov 2024-09-10 15:08:01 +03:00
parent 452561fb5b
commit 8de60d7739
4 changed files with 77 additions and 19 deletions

1
.gitignore vendored
View File

@ -15,3 +15,4 @@ web/.node-persist/*
agent-mAId/output.wav agent-mAId/output.wav
agent-mAId/build/* agent-mAId/build/*
agent-mAId/dist/main.exe agent-mAId/dist/main.exe
agent-mAId/output.wav

View File

@ -16,9 +16,12 @@ import time
import json5 import json5
import wave import wave
import pyperclip import pyperclip
import argparse
import atexit
# # Load configuration from config.json # # Load configuration from config.json
DEFAULT_CONFIG = { DEFAULT_CONFIG = {
"api_key": "xxx",
"kb_key": "ctrl", "kb_key": "ctrl",
"mouse_btn": "left", "mouse_btn": "left",
"model": "distil-whisper-large-v3-en", "model": "distil-whisper-large-v3-en",
@ -26,20 +29,29 @@ DEFAULT_CONFIG = {
"action": "type" # type, copy "action": "type" # type, copy
} }
def load_config(): def parse_args():
"""Parse command line arguments for config file."""
parser = argparse.ArgumentParser(description='Run the AI transcription app.')
parser.add_argument(
'--config', type=str, help='Path to config file', default=None
)
return parser.parse_args()
def load_config(config_path=None):
"""Load the configuration file, adjusting for PyInstaller's temp path when bundled.""" """Load the configuration file, adjusting for PyInstaller's temp path when bundled."""
config = DEFAULT_CONFIG.copy() # Start with default configuration config = DEFAULT_CONFIG.copy() # Start with default configuration
try: try:
# Determine if the script is running as a PyInstaller bundle if config_path is None:
if getattr(sys, 'frozen', False): # Determine if the script is running as a PyInstaller bundle
# If running in a bundle, use the temp path where PyInstaller extracts files if getattr(sys, 'frozen', False):
config_path = os.path.join(sys._MEIPASS, 'config.json') # If running in a bundle, use the temp path where PyInstaller extracts files
else: config_path = os.path.join(sys._MEIPASS, 'config.json')
# If running in development (normal execution), use the local directory else:
config_path = os.path.join(os.path.dirname(__file__), 'config.json') # If running in development (normal execution), use the local directory
config_path = os.path.join(os.path.dirname(__file__), 'config.json')
print('Trying to load config from:', config_path) print(f'Trying to load config from: {config_path}')
with open(config_path, 'r') as config_file: with open(config_path, 'r') as config_file:
loaded_config = json5.load(config_file) loaded_config = json5.load(config_file)
# Update the default config with any values from config.json # Update the default config with any values from config.json
@ -47,6 +59,7 @@ def load_config():
except FileNotFoundError as ex: except FileNotFoundError as ex:
print("Config file not found, using defaults." + ex.strerror) print("Config file not found, using defaults." + ex.strerror)
raise ex
except json5.JSONDecodeError as ex: except json5.JSONDecodeError as ex:
print("Error decoding config file, using defaults." + ex.msg) print("Error decoding config file, using defaults." + ex.msg)
except Exception as e: except Exception as e:
@ -55,7 +68,13 @@ def load_config():
return config return config
# Load the config # Load the config
config = load_config() # config = load_config()
# Parse command line arguments
args = parse_args()
# Load the config from the specified path or default location
config = load_config(args.config)
# Extract API key and button from the config file # Extract API key and button from the config file
API_KEY = config['api_key'] API_KEY = config['api_key']
KB_KEY = config['kb_key'] KB_KEY = config['kb_key']
@ -63,6 +82,7 @@ MOUSE_BTN = config['mouse_btn']
MODEL = config['model'] MODEL = config['model']
POST_TRANSCRIBE = config['action'] POST_TRANSCRIBE = config['action']
# Constants # Constants
AUTO_START_PATH = os.path.expanduser(r"~\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup") # For autostart AUTO_START_PATH = os.path.expanduser(r"~\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup") # For autostart
@ -131,7 +151,8 @@ def transcribe_audio(memory_stream):
end_time = time.time() end_time = time.time()
transcription_time = end_time - start_time transcription_time = end_time - start_time
print(f"Transcription took: {transcription_time:.2f} seconds.") print(f"Transcription took: {transcription_time:.2f} seconds. Result: {transcription.text}")
log_transcription_time(transcription_time)
return transcription.text return transcription.text
@ -150,16 +171,32 @@ def add_to_autostart():
shell.ShellExecuteW(None, "runas", "cmd.exe", f'/C mklink "{shortcut_path}" "{script_path}"', None, 1) shell.ShellExecuteW(None, "runas", "cmd.exe", f'/C mklink "{shortcut_path}" "{script_path}"', None, 1)
print("App added to autostart.") print("App added to autostart.")
def quit_app(icon): icon = None # Global variable to store the tray icon object
"""Quit the tray application.""" def cleanup_and_exit():
icon.stop() """Clean up the tray icon and exit the application."""
global icon
if icon:
print("Stopping and removing tray icon...")
icon.stop() # Stop the tray icon to remove it from the tray
sys.exit() sys.exit()
def setup_tray_icon(): def setup_tray_icon():
global icon
"""Setup system tray icon and menu.""" """Setup system tray icon and menu."""
#icon_image = Image.new('RGB', (64, 64), color=(255, 0, 0)) # Red icon as an example if getattr(sys, 'frozen', False):
icon_image = Image.open('mic.webp') # If running as a bundle, use the temp path where PyInstaller extracts files
icon_path = os.path.join(sys._MEIPASS, 'mic.webp')
else:
# If running in development (normal execution), use the local directory
icon_path = os.path.join(os.path.dirname(__file__), 'mic.webp')
try:
# Load the tray icon
icon_image = Image.open(icon_path)
except FileNotFoundError:
print(f"Icon file not found at {icon_path}")
icon_image = Image.new('RGB', (64, 64), color=(255, 0, 0)) # Red icon as an example
return
menu = ( menu = (
item('Register to Autostart', add_to_autostart), item('Register to Autostart', add_to_autostart),
@ -169,10 +206,26 @@ def setup_tray_icon():
icon = pystray.Icon("mAId", icon_image, menu=pystray.Menu(*menu)) icon = pystray.Icon("mAId", icon_image, menu=pystray.Menu(*menu))
icon.run() icon.run()
# Ensure the tray icon is removed when the app exits
atexit.register(cleanup_and_exit)
response_times = [] response_times = []
ma_window_size = 10 # Moving average over the last 10 responses ma_window_size = 10 # Moving average over the last 10 responses
def log_transcription_time(transcription_time):
"""Logs the transcription time and updates the moving average."""
global response_times
# Add the transcription time to the list
response_times.append(transcription_time)
# If the number of logged times exceeds the window size, remove the oldest entry
if len(response_times) > ma_window_size:
response_times.pop(0)
# Calculate and print the moving average
moving_average = sum(response_times) / len(response_times)
print(f"Moving Average of Transcription Time (last {ma_window_size} responses): {moving_average:.2f} seconds.")
def main_loop(): def main_loop():
"""Continuously listen for key or mouse press and transcribe audio.""" """Continuously listen for key or mouse press and transcribe audio."""
@ -201,7 +254,10 @@ def main_loop():
pyperclip.copy(transcribed_text) pyperclip.copy(transcribed_text)
print("Transcribed text copied to clipboard.") print("Transcribed text copied to clipboard.")
if __name__ == "__main__": if __name__ == "__main__":
# Start the tray icon in a separate thread so it doesn't block the main functionality # Start the tray icon in a separate thread so it doesn't block the main functionality
tray_thread = threading.Thread(target=setup_tray_icon) tray_thread = threading.Thread(target=setup_tray_icon)
tray_thread.daemon = True tray_thread.daemon = True

View File

@ -5,7 +5,7 @@ a = Analysis(
['main.py'], ['main.py'],
pathex=[], pathex=[],
binaries=[], binaries=[],
datas=[], datas=[('config.json', '.'), ('mic.webp', '.')],
hiddenimports=[], hiddenimports=[],
hookspath=[], hookspath=[],
hooksconfig={}, hooksconfig={},
@ -14,6 +14,7 @@ a = Analysis(
noarchive=False, noarchive=False,
optimize=0, optimize=0,
) )
pyz = PYZ(a.pure) pyz = PYZ(a.pure)
exe = EXE( exe = EXE(

Binary file not shown.