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
*.wav
*.wav
*.mp3

View File

@@ -1,21 +1,32 @@
#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
pip install sounddevice
###Windows
pip install pyaudio
pip install python-dotenv
pip install wave
pip install pydub
##Mac
###Mac
pip install numpy
pip install sounddevice
brew install portaudio
pip install python-dotenv
pip install wave
pip install pydub

View File

@@ -1,8 +1,8 @@
import openai
from openai import OpenAI
import numpy as np
import sounddevice as sd
import soundfile as sf
import pyaudio
import wave
import threading
from dotenv import load_dotenv
import os
@@ -13,50 +13,120 @@ load_dotenv()
client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
def list_audio_devices():
p = pyaudio.PyAudio()
print("Available audio devices:")
devices = sd.query_devices()
for index, device in enumerate(devices):
if device['max_output_channels'] > 0:
print(f"Device index {index}: {device['name']}")
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 stream_audio_to_virtual_mic(text, voice="fable", device_index=None):
# Check if device_index is provided, if not, prompt for it
if device_index is None:
device_index = int(input("Enter the device index: "))
def play_saved_audio(file_path, device_index=None):
# Open the saved audio file
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(
model="tts-1",
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:
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)
return "";
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
arglen = len(sys.argv)
if arglen < 2:
print("Usage: python script.py 'text to convert'")
sys.exit(1)
list_audio_devices()
device_index = int(input("Enter the device index: "))
stream_audio_to_virtual_mic(sys.argv[1], voice="fable", device_index=device_index)
print(f"arg count {arglen}")
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)