Working python version that streams to two different audio streams at the same time

This commit is contained in:
Andrew Ward
2024-05-01 14:50:04 +01:00
parent b0f142d655
commit 13cbf1b4eb
3 changed files with 121 additions and 39 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.env .env
*.wav *.wav
*.mp3

View File

@@ -1,21 +1,32 @@
#Readme #Readme
This script uses OpenAI to convert text to speech, and then speak that speech over a virtual microphone
Install VB-Cable:
https://vb-audio.com/Cable/
This sets up a virtual microphone that we can use to sent text to speech audio to. Then, when you join a meeting, such as a google meeting, you can select this virtual cable to hear the audio being sent on the channel.
#Dependencies
##Windows To get this script working you will need to install the following on the relevant operating system
pip install numpy ###Windows
pip install sounddevice
pip install pyaudio
pip install python-dotenv pip install python-dotenv
pip install wave
pip install pydub
##Mac ###Mac
pip install numpy brew install portaudio
pip install sounddevice
pip install python-dotenv pip install python-dotenv
pip install wave
pip install pydub

View File

@@ -1,8 +1,8 @@
import openai import openai
from openai import OpenAI from openai import OpenAI
import numpy as np import pyaudio
import sounddevice as sd import wave
import soundfile as sf import threading
from dotenv import load_dotenv from dotenv import load_dotenv
import os import os
@@ -13,50 +13,120 @@ load_dotenv()
client = OpenAI(api_key=os.getenv('OPENAI_API_KEY')) client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
def list_audio_devices(): def list_audio_devices():
p = pyaudio.PyAudio()
print("Available audio devices:") print("Available audio devices:")
devices = sd.query_devices() info = p.get_host_api_info_by_index(0)
for index, device in enumerate(devices): num_devices = info.get('deviceCount')
if device['max_output_channels'] > 0: # List all available devices, and mark output devices
print(f"Device index {index}: {device['name']}") 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 stream_audio_to_virtual_mic(text, voice="fable", device_index=None):
# Check if device_index is provided, if not, prompt for it def play_saved_audio(file_path, device_index=None):
if device_index is None: # Open the saved audio file
device_index = int(input("Enter the device index: ")) wf = wave.open(file_path, 'rb')
# Create audio stream from text input 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", device_index=None, device_index_2=None):
response = client.audio.speech.create( response = client.audio.speech.create(
model="tts-1", model="tts-1",
voice=voice, voice=voice,
input=text 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)
if device_index is None: return "";
device_index = int(input("Enter the device index: "))
# Load the binary audio content into a numpy array
audio_data = np.frombuffer(response.content, dtype=np.int16)
# Set the samplerate assumed from OpenAI's API (check documentation for exact rate)
sample_rate = 22050 # or 44100, depending on the API's output format
# Play audio
sd.play(audio_data, sample_rate, device=device_index)
sd.wait() # Wait until the audio has finished playing
sf.write('captured_audio.wav', audio_data, sample_rate)
if __name__ == "__main__": if __name__ == "__main__":
import sys import sys
if len(sys.argv) < 2:
arglen = len(sys.argv)
if arglen < 2:
print("Usage: python script.py 'text to convert'") print("Usage: python script.py 'text to convert'")
sys.exit(1) sys.exit(1)
list_audio_devices() print(f"arg count {arglen}")
device_index = int(input("Enter the device index: "))
stream_audio_to_virtual_mic(sys.argv[1], voice="fable", device_index=device_index) 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", device_index=device_index,device_index_2=device_index_2)