From d45876fa8b7827eaadb8bc43ad6d072c28eb6743 Mon Sep 17 00:00:00 2001 From: Andrew Ward Date: Thu, 2 May 2024 13:40:22 +0100 Subject: [PATCH] EXPERIMENT: Added more specific path locations for mac to see if this stops it erroring --- text-to-mic.py | 108 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 88 insertions(+), 20 deletions(-) diff --git a/text-to-mic.py b/text-to-mic.py index 66382eb..4d7e2f3 100644 --- a/text-to-mic.py +++ b/text-to-mic.py @@ -1,4 +1,5 @@ import tkinter as tk +import platform from tkinter import ttk, messagebox, simpledialog, Menu import os import pyaudio @@ -6,6 +7,8 @@ import wave import webbrowser from openai import OpenAI from dotenv import load_dotenv +from pathlib import Path + # Load environment variables @@ -209,23 +212,63 @@ https://www.scorchsoft.com/blog/text-to-mic-for-meetings/ + def get_app_support_path_mac(): + home = Path.home() + app_support_path = home / 'Library' / 'Application Support' / 'scorchsoft-text-to-mic' + app_support_path.mkdir(parents=True, exist_ok=True) # Ensure directory exists + return app_support_path + + def save_api_key_mac(api_key): + env_path = get_app_support_path() / '.env' + with open(env_path, 'w') as f: + f.write(f"OPENAI_API_KEY={api_key}\n") + # Consider manually loading this .env file into your environment as needed + def load_api_key_mac(): + env_path = get_app_support_path() / '.env' + if env_path.exists(): + with open(env_path, 'r') as f: + for line in f: + if line.startswith('OPENAI_API_KEY'): + return line.strip().split('=')[1] + return None + def get_api_key(self): + # First, try to load the API key from environment variables or local file api_key = os.getenv("OPENAI_API_KEY") - if not api_key: # No API key found in the environment - self.show_instructions() # Show the "How to Use" modal after setting the key - api_key = simpledialog.askstring("API Key", "Enter your OpenAI API Key:", parent=self) - if api_key: - self.save_api_key(api_key) - messagebox.showinfo("API Key Set", "The OpenAI API Key has been updated successfully.") - + if not api_key: # Check for macOS and use the macOS-specific method + if platform.system() == 'Darwin': # Darwin is the system name for macOS + api_key = self.load_api_key_mac() + + # If no API key is found, prompt the user + if not api_key: + self.show_instructions() # Show the "How to Use" modal after setting the key + api_key = simpledialog.askstring("API Key", "Enter your OpenAI API Key:", parent=self) + if api_key: + try: + if platform.system() == 'Darwin': + self.save_api_key_mac(api_key) + else: + self.save_api_key(api_key) + messagebox.showinfo("API Key Set", "The OpenAI API Key has been updated successfully.") + except Exception as e: + messagebox.showerror("Error", f"Failed to save API key: {str(e)}") + return api_key + def save_api_key(self, api_key): - with open('.env', 'w') as f: - f.write(f"OPENAI_API_KEY={api_key}\n") - load_dotenv() + try: + if platform.system() == 'Darwin': + self.save_api_key_mac(api_key) + else: + with open('.env', 'w') as f: + f.write(f"OPENAI_API_KEY={api_key}\n") + load_dotenv() # Reload environment to include the new API key + except Exception as e: + messagebox.showerror("Error", f"Failed to save API key: {str(e)}") + def get_audio_devices(self): p = pyaudio.PyAudio() @@ -236,6 +279,13 @@ https://www.scorchsoft.com/blog/text-to-mic-for-meetings/ devices[info['name']] = i p.terminate() return devices + + def get_audio_file_path(self, filename): + if platform.system() == 'Darwin': # Check if the OS is macOS + return self.get_app_support_path_mac() / filename + else: + return Path(filename) # Default to current directory for non-macOS systems + def submit_text(self): @@ -264,8 +314,9 @@ https://www.scorchsoft.com/blog/text-to-mic-for-meetings/ input=text, response_format='wav' ) - self.last_audio_file = "last_output.wav" - response.stream_to_file(self.last_audio_file) + + self.last_audio_file = self.get_audio_file_path("last_output.wav") + response.stream_to_file(str(self.last_audio_file)) #Play to either two or a single stream if primary_index and secondary_index != "None" and secondary_index is not None: @@ -287,12 +338,30 @@ https://www.scorchsoft.com/blog/text-to-mic-for-meetings/ try: # Open all files and start all streams for file_path, device_index in zip(file_paths, device_indices): - wf = wave.open(file_path, 'rb') - stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), - channels=wf.getnchannels(), - rate=wf.getframerate(), - output=True, - output_device_index=int(device_index)) + + + + try: + # Ensure the file_path is a string when opening the file + wf = wave.open(str(file_path), 'rb') + except FileNotFoundError: + messagebox.showerror("File Not Found", f"Could not find audio file: {file_path}") + continue # Skip this iteration and proceed with other files if any + except wave.Error as e: + messagebox.showerror("Wave Error", f"Error reading audio file: {file_path}. Error: {str(e)}") + continue + + try: + stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), + channels=wf.getnchannels(), + rate=wf.getframerate(), + output=True, + output_device_index=int(device_index)) + except Exception as e: + messagebox.showerror("Stream Creation Error", f"Failed to create audio stream for device index {device_index}: {str(e)}") + wf.close() + continue + streams.append((stream, wf)) # Play interleaved @@ -308,6 +377,7 @@ https://www.scorchsoft.com/blog/text-to-mic-for-meetings/ wf.close() streams.remove((stream, wf)) active_streams -= 1 + except Exception as e: messagebox.showerror("Playback Error", f"Error during multiplexed playback: {e}") finally: @@ -316,8 +386,6 @@ https://www.scorchsoft.com/blog/text-to-mic-for-meetings/ def play_last_audio(self): - - if hasattr(self, 'last_audio_file'): 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