add hotkey support

This commit is contained in:
Andrew Ward
2024-05-05 17:40:21 +01:00
parent f8c7b38c51
commit 960fd8c572
2 changed files with 115 additions and 19 deletions

View File

@@ -63,6 +63,9 @@ To get this script working you will need to install the following on the relevan
* pip install python-dotenv
* pip install wave
* pip install pydub
* pip install keyboard
* pip install pystray
* pip install playsound
### Mac
@@ -77,6 +80,7 @@ To get this script working you will need to install the following on the relevan
* pip3 install wave
* pip3 install pydub
# Making an installer
## Windows

View File

@@ -1,12 +1,17 @@
import tkinter as tk
import platform
from tkinter import ttk, messagebox, simpledialog, Menu
import os
import threading
import pyaudio
import wave
import webbrowser
import json
import keyboard
import pygame
from pystray import Icon as icon, MenuItem as item, Menu as menu
from PIL import Image, ImageDraw
from tkinter import ttk, messagebox, simpledialog, Menu
from openai import OpenAI
from dotenv import load_dotenv
from pathlib import Path
@@ -55,13 +60,14 @@ class Application(tk.Tk):
self.create_menu()
self.initialize_gui()
self.setup_hotkeys()
def show_version(self):
instruction_window = tk.Toplevel(self)
instruction_window.title("App Version")
instruction_window.geometry("300x150") # Width x Height
instructions = """Version 1.0.6\n\n App by Scorchsoft.com"""
instructions = """Version 1.0.7\n\n App by Scorchsoft.com"""
tk.Label(instruction_window, text=instructions, justify=tk.LEFT, wraplength=280).pack(padx=10, pady=10)
@@ -96,10 +102,50 @@ class Application(tk.Tk):
help_menu.add_command(label="How to Use", command=self.show_instructions)
help_menu.add_command(label="Terms of Use and Licence", command=self.show_terms_of_use)
help_menu.add_command(label="Version", command=self.show_version)
help_menu.add_command(label="Hotkey Instructions", command=self.show_hotkey_instructions)
def setup_hotkeys(self):
# Register global hotkeys
keyboard.add_hotkey('ctrl+shift+r', lambda: self.hotkey_record_trigger())
keyboard.add_hotkey('ctrl+shift+s', lambda: self.hotkey_stop_trigger() )
keyboard.add_hotkey('ctrl+shift+l', lambda: self.hotkey_play_last_audio_trigger() )
def hotkey_play_last_audio_trigger(self):
if hasattr(self, 'last_audio_file'):
self.play_last_audio()
else:
self.play_sound('assets/no-last-audio.wav')
def hotkey_stop_trigger(self):
self.play_sound('assets/wrong-short.wav')
if self.recording:
self.stop_recording(auto_play=False)
# Sounds from https://mixkit.co/free-sound-effects/notification/
def hotkey_record_trigger(self):
if self.recording:
self.play_sound('assets/mixkit-message-pop-alert-2354.mp3')
self.submit_text()
else:
input_device_index = self.input_device_index.get() # Assuming input_device_index is a StringVar
input_device_id = self.available_input_devices.get(input_device_index)
if input_device_id is None:
self.play_sound('assets/please-select-input.wav')
return
self.play_sound('assets/mixkit-message-pop-alert-2354.mp3')
self.toggle_recording()
def play_sound(self, sound_file):
pygame.init()
pygame.mixer.init()
sound = pygame.mixer.Sound(sound_file)
sound.play()
def initialize_gui(self):
@@ -155,8 +201,8 @@ class Application(tk.Tk):
self.record_button = ttk.Button(main_frame, text="Record Mic", command=self.toggle_recording)
self.record_button.grid(column=0, row=6, sticky=tk.W + tk.E, pady=(0, 20), padx=(0, 10)) # Left padding to separate buttons
submit_button = ttk.Button(main_frame, text="Play Audio", style="Green.TButton", command=self.submit_text )
submit_button.grid(column=1, row=6, sticky=tk.W + tk.E, pady=(0, 20), padx=(10, 0)) # Right padding to separate buttons
self.submit_button = ttk.Button(main_frame, text="Play Audio", style="Green.TButton", command=self.submit_text )
self.submit_button.grid(column=1, row=6, sticky=tk.W + tk.E, pady=(0, 20), padx=(10, 0)) # Right padding to separate buttons
@@ -181,7 +227,26 @@ class Application(tk.Tk):
def show_hotkey_instructions(self):
instruction_window = tk.Toplevel(self)
instruction_window.title("Hotkey Instructions")
instruction_window.geometry("400x300") # Width x Height
instructions = """How to use Hotkeys
ctrl+shift+r
This starts a recording, then converts to text and plays when you press this hotkey again.
ctrl+shift+s
If you are recording, you can press this hotkey to stop recording without playing
ctrl+shift+l
This replays the last audio clip played
"""
tk.Label(instruction_window, text=instructions, justify=tk.LEFT, wraplength=380).pack(padx=10, pady=10)
# Add a button to close the window
ttk.Button(instruction_window, text="Close", command=instruction_window.destroy).pack(pady=(10, 0))
def show_instructions(self):
instruction_window = tk.Toplevel(self)
@@ -343,15 +408,30 @@ https://www.scorchsoft.com/blog/text-to-mic-for-meetings/
return Path(filename) # Default to current directory for non-macOS systems
def submit_text(self):
def submit_text(self, play_text = None):
text = self.text_input.get("1.0", tk.END).strip()
selected_voice = self.voice_var.get()
print(f"submit text self recording: {self.recording}")
if self.recording:
print("Stopping recording")
self.stop_recording(auto_play = True)
else:
print("Submitting text")
self.submit_text_helper(play_text = play_text)
def submit_text_helper(self, play_text = None):
if play_text is None:
#Load from GUI if play text not set
text = self.text_input.get("1.0", tk.END).strip()
else:
text = play_text
if not text:
messagebox.showinfo("Error", "Please enter some text to synthesize.")
return
selected_voice = self.voice_var.get()
# Convert device names to indices
primary_index = self.available_devices.get(self.device_index.get(), None)
secondary_index = self.available_devices.get(self.device_index_2.get(), None) if self.device_index_2.get() != "None" else None
@@ -403,9 +483,6 @@ https://www.scorchsoft.com/blog/text-to-mic-for-meetings/
# Open all files and start all streams
for file_path, device_index in zip(file_paths, device_indices):
try:
# Ensure the file_path is a string when opening the file
wf = wave.open(str(file_path), 'rb')
@@ -532,11 +609,11 @@ https://www.scorchsoft.com/blog/text-to-mic-for-meetings/
finally:
p.terminate()
def toggle_recording(self):
def toggle_recording(self, auto_play=False):
if not self.recording:
self.start_recording()
else:
self.stop_recording()
self.stop_recording(auto_play)
def stop_recording_btn_change(self, btn_text):
self.record_button.config(text=btn_text)
@@ -569,6 +646,8 @@ https://www.scorchsoft.com/blog/text-to-mic-for-meetings/
try:
self.recording = True
self.record_button.config(text="Stop and Insert", style='Recording.TButton')
self.submit_button.config(text="Stop and Play", style='Recording.TButton')
self.frames = []
self.p = pyaudio.PyAudio()
@@ -587,7 +666,7 @@ https://www.scorchsoft.com/blog/text-to-mic-for-meetings/
messagebox.showerror("Recording Error", f"Failed to record audio: {str(e)}")
self.stop_recording(True)
def stop_recording(self, cancel_save=False):
def stop_recording(self, cancel_save=False, auto_play=False):
self.recording = False
if self.record_thread:
self.record_thread.join()
@@ -600,12 +679,13 @@ https://www.scorchsoft.com/blog/text-to-mic-for-meetings/
self.p.terminate()
if cancel_save==False:
self.save_recording()
self.save_recording(auto_play=auto_play)
self.record_button.config(text="Record Mic", style='TButton') # Revert to default style
self.submit_button.config(text="Play", style='Green.TButton') # Revert to default style
def save_recording(self):
def save_recording(self, auto_play = False):
file_path = "output.wav"
wf = wave.open(file_path, 'wb')
wf.setnchannels(1)
@@ -615,12 +695,12 @@ https://www.scorchsoft.com/blog/text-to-mic-for-meetings/
wf.close()
print("Recording saved.")
self.after(0, self.transcribe_audio, file_path)
self.after(0, self.transcribe_audio, file_path, auto_play)
def transcribe_audio(self, file_path):
def transcribe_audio(self, file_path, auto_play = False):
try:
with open(str(file_path), "rb") as audio_file:
transcription = self.client.audio.transcriptions.create(
@@ -628,7 +708,6 @@ https://www.scorchsoft.com/blog/text-to-mic-for-meetings/
model="whisper-1",
response_format="verbose_json"
)
settings = self.load_settings()
@@ -641,13 +720,20 @@ https://www.scorchsoft.com/blog/text-to-mic-for-meetings/
if auto_apply_ai:
print("applying ai")
self.apply_ai(transcription.text)
play_text = self.apply_ai(transcription.text)
else:
print("outputting without ai")
#This prevents issues with trying to upload TK after thread operations
#whcih can cause crashes with no error displayed
self.text_input.delete("1.0", tk.END) # Clear existing text
self.text_input.insert("1.0", transcription.text) # Insert new text
play_text = transcription.text
if auto_play:
#self.submit_text(play_text = playtext)#
print(f"Triggering auto play with: {play_text} ")
self.submit_text_helper(play_text = play_text)
# TODO: PLAY THE TEXT IMMEDIATELY
print("Transcription Complete: The audio has been transcribed and the text has been placed in the input area.")
#messagebox.showinfo("Transcription Complete", "The audio has been transcribed and the text has been placed in the input area.")
@@ -762,6 +848,12 @@ https://www.scorchsoft.com/blog/text-to-mic-for-meetings/
self.text_input.delete("1.0", tk.END)
self.text_input.insert("1.0", response.choices[0].message.content)
return_text = response.choices[0].message.content
else:
return_text = text
return return_text
if __name__ == "__main__":
app = Application()