This commit adds comprehensive support for using SiliconFlow's TTS API as an alternative to OpenAI, including: Features: - Configurable API base URL (Settings > API Base URL) - TTS model selection dropdown (CosyVoice2-0.5B, OpenAI compatible models) - Dynamic voice options based on selected model - Editable voice dropdown (Combobox) supporting custom voice IDs - Automatic voice formatting for SiliconFlow (model:voice format) - Debug logging for troubleshooting API calls - Warning for incorrect base URL format Changes: - utils/settings_manager.py: Added api_base_url and tts_model settings - utils/text_to_mic.py: - Added get_available_tts_models() for model options - Added get_siliconflow_voices() for SiliconFlow voices - Added change_api_base_url() method with validation - Added TTS model dropdown in GUI - Converted voice dropdown to Combobox for typing support - Added on_voice_exit() for validation - Updated API call to use selected model and formatted voice - text-to-mic-cli.py: Added OPENAI_API_BASE_URL and OPENAI_TTS_MODEL env var support - Readme.md: Updated documentation with SiliconFlow usage instructions Supported Models: - FunAudioLLM/CosyVoice2-0.5B (SiliconFlow - multi-language, emotional) - tts-1, tts-1-hd (OpenAI compatible) - gpt-4o-mini-tts (OpenAI default) SiliconFlow Voices (CosyVoice2-0.5B): - Male: alex, benjamin, charles, david - Female: anna, bella, claire, diana - Custom voices via voice ID entry Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
162 lines
5.1 KiB
Python
162 lines
5.1 KiB
Python
import openai
|
|
from openai import OpenAI
|
|
import pyaudio
|
|
import wave
|
|
import threading
|
|
from dotenv import load_dotenv
|
|
import os
|
|
|
|
# Load environment variables from .env file
|
|
load_dotenv()
|
|
|
|
# Set up your OpenAI API key from the environment variable
|
|
api_key = os.getenv('OPENAI_API_KEY')
|
|
api_base_url = os.getenv('OPENAI_API_BASE_URL', '').strip()
|
|
|
|
# Create client with custom base URL if provided
|
|
if api_base_url:
|
|
client = OpenAI(api_key=api_key, base_url=api_base_url)
|
|
else:
|
|
client = OpenAI(api_key=api_key)
|
|
|
|
def list_audio_devices():
|
|
p = pyaudio.PyAudio()
|
|
print("Available audio devices:")
|
|
info = p.get_host_api_info_by_index(0)
|
|
num_devices = info.get('deviceCount')
|
|
# List all available devices, and mark output devices
|
|
for i in range(0, num_devices):
|
|
if p.get_device_info_by_index(i).get('maxOutputChannels') > 0:
|
|
print(f"Device index {i}: {p.get_device_info_by_index(i).get('name')}")
|
|
p.terminate()
|
|
|
|
|
|
def play_saved_audio(file_path, device_index=None):
|
|
# Open the saved audio file
|
|
wf = wave.open(file_path, 'rb')
|
|
|
|
print(f"Playing audio to device {device_index}")
|
|
|
|
# Setup PyAudio
|
|
p = pyaudio.PyAudio()
|
|
|
|
try:
|
|
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
|
|
channels=wf.getnchannels(),
|
|
rate=wf.getframerate(),
|
|
output=True,
|
|
output_device_index=device_index)
|
|
data = wf.readframes(1024)
|
|
while data:
|
|
stream.write(data)
|
|
data = wf.readframes(1024)
|
|
except Exception as e:
|
|
print(f"Error playing audio on device {device_index}: {e}")
|
|
finally:
|
|
stream.stop_stream()
|
|
stream.close()
|
|
wf.close()
|
|
p.terminate()
|
|
|
|
#Plays to multiple device indexes at the same time
|
|
def play_audio_multiplexed(file_paths, device_indices):
|
|
p = pyaudio.PyAudio()
|
|
streams = []
|
|
|
|
# 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=device_index)
|
|
streams.append((stream, wf))
|
|
|
|
# Play interleaved
|
|
active_streams = len(streams)
|
|
while active_streams > 0:
|
|
for stream, wf in streams:
|
|
data = wf.readframes(1024)
|
|
if data:
|
|
stream.write(data)
|
|
else:
|
|
stream.stop_stream()
|
|
stream.close()
|
|
wf.close()
|
|
active_streams -= 1
|
|
|
|
p.terminate()
|
|
|
|
def stream_audio_to_virtual_mic(text, voice="fable", model=None, device_index=None, device_index_2=None):
|
|
# Get model from environment variable or use default
|
|
if model is None:
|
|
model = os.getenv('OPENAI_TTS_MODEL', 'tts-1')
|
|
|
|
response = client.audio.speech.create(
|
|
model=model,
|
|
voice=voice,
|
|
input=text,
|
|
response_format='wav'
|
|
)
|
|
|
|
#This can either stream to one device index at a time, or, via multiplexing
|
|
#it can stream to two similtaneously to prevent lag playing in sequence
|
|
if device_index_2 is not None:
|
|
file_path_1 = "output1.wav"
|
|
file_path_2 = "output2.wav"
|
|
response.stream_to_file(file_path_1)
|
|
response.stream_to_file(file_path_2)
|
|
play_audio_multiplexed([file_path_1, file_path_2], [device_index, device_index_2])
|
|
else:
|
|
file_path_1 = "output1.wav"
|
|
response.stream_to_file(file_path_1)
|
|
play_saved_audio(file_path_1, device_index)
|
|
|
|
return "";
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
|
|
arglen = len(sys.argv)
|
|
|
|
if arglen < 2:
|
|
print("Usage: python script.py 'text to convert'")
|
|
print("Environment variables:")
|
|
print(" OPENAI_API_KEY - Your API key (required)")
|
|
print(" OPENAI_API_BASE_URL - Custom API base URL (optional)")
|
|
print(" OPENAI_TTS_MODEL - TTS model to use (default: tts-1)")
|
|
print("")
|
|
print("Example models:")
|
|
print(" - tts-1 (OpenAI standard)")
|
|
print(" - tts-1-hd (OpenAI high quality)")
|
|
print(" - gpt-4o-mini-tts (OpenAI)")
|
|
print(" - FunAudioLLM/CosyVoice2-0.5B (SiliconFlow)")
|
|
print("")
|
|
print("For SiliconFlow voices with CosyVoice2:")
|
|
print(" The voice will be auto-formatted as: FunAudioLLM/CosyVoice2-0.5B:alex")
|
|
sys.exit(1)
|
|
|
|
print(f"arg count {arglen}")
|
|
|
|
# Get TTS model from environment
|
|
tts_model = os.getenv('OPENAI_TTS_MODEL', 'tts-1')
|
|
print(f"Using TTS model: {tts_model}")
|
|
|
|
if arglen == 4:
|
|
device_index = int(sys.argv[2])
|
|
device_index_2 = int(sys.argv[3])
|
|
elif arglen == 3:
|
|
device_index = int(sys.argv[2])
|
|
device_index_2 = None
|
|
else:
|
|
list_audio_devices()
|
|
device_index = int(input("Enter the device index: "))
|
|
device_index_2 = None
|
|
|
|
|
|
stream_audio_to_virtual_mic(sys.argv[1], voice="fable", model=tts_model, device_index=device_index,device_index_2=device_index_2)
|