Working python version that streams to two different audio streams at the same time
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
.env
|
||||
*.wav
|
||||
*.wav
|
||||
*.mp3
|
||||
23
Readme.md
23
Readme.md
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
134
text-to-mic.py
134
text-to-mic.py
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user