Semi working google voice
This commit is contained in:
parent
a6c0739f27
commit
8f68505ff4
3 changed files with 164 additions and 16 deletions
|
@ -6,16 +6,18 @@ This server controls all hugveys and the processing of their narratives. It expo
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from pandas.conftest import ip
|
||||||
|
import threading
|
||||||
import yaml
|
import yaml
|
||||||
import zmq
|
import zmq
|
||||||
|
from zmq.asyncio import Context
|
||||||
|
|
||||||
from hugvey import panopticon
|
from hugvey import panopticon
|
||||||
from hugvey.voice.streamer import AudioStreamer
|
|
||||||
from hugvey.voice.player import Player
|
|
||||||
from hugvey.communication import getTopic, zmqSend, zmqReceive
|
from hugvey.communication import getTopic, zmqSend, zmqReceive
|
||||||
from pandas.conftest import ip
|
from hugvey.voice.google import GoogleVoiceClient
|
||||||
from zmq.asyncio import Context
|
from hugvey.voice.player import Player
|
||||||
import threading
|
from hugvey.voice.streamer import AudioStreamer
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger("command")
|
logger = logging.getLogger("command")
|
||||||
|
@ -29,6 +31,7 @@ class CentralCommand(object):
|
||||||
self.isRunning = threading.Event()
|
self.isRunning = threading.Event()
|
||||||
self.hugveys = {}
|
self.hugveys = {}
|
||||||
self.ctx = Context.instance()
|
self.ctx = Context.instance()
|
||||||
|
self.hugveyLock = asyncio.Lock()
|
||||||
|
|
||||||
|
|
||||||
def loadConfig(self, filename):
|
def loadConfig(self, filename):
|
||||||
|
@ -69,7 +72,7 @@ class CentralCommand(object):
|
||||||
|
|
||||||
s.close()
|
s.close()
|
||||||
|
|
||||||
def instantiateHugvey(self, hugvey_id, msg):
|
async def instantiateHugvey(self, hugvey_id, msg):
|
||||||
'''
|
'''
|
||||||
Start a HugveyState, according to a show_yourself reply
|
Start a HugveyState, according to a show_yourself reply
|
||||||
|
|
||||||
|
@ -78,13 +81,20 @@ class CentralCommand(object):
|
||||||
'host': socket.gethostname(),
|
'host': socket.gethostname(),
|
||||||
'ip': self.getIp(),
|
'ip': self.getIp(),
|
||||||
'''
|
'''
|
||||||
# def startHugvey():
|
async with self.hugveyLock: # lock to prevent duplicates on creation
|
||||||
|
if not hugvey_id in self.hugveys:
|
||||||
|
logger.info(f'Instantiate hugvey #{hugvey_id}')
|
||||||
h = HugveyState(hugvey_id, self)
|
h = HugveyState(hugvey_id, self)
|
||||||
h.config(msg['host'],msg['ip'])
|
h.config(msg['host'],msg['ip'])
|
||||||
|
self.hugveys[hugvey_id] = h
|
||||||
thread = threading.Thread(target=h.start)
|
thread = threading.Thread(target=h.start, name=f"hugvey#{hugvey_id}")
|
||||||
thread.start()
|
thread.start()
|
||||||
# self.tasks['hv{}'.format(hugvey_id)] = self.loop.create_task(h.start())
|
else:
|
||||||
|
logger.info(f'Reconfigure hugvey #{hugvey_id}')
|
||||||
|
# (re)configure exisitng hugveys
|
||||||
|
h.config(msg['host'],msg['ip'])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def eventListener(self):
|
async def eventListener(self):
|
||||||
s = self.ctx.socket(zmq.SUB)
|
s = self.ctx.socket(zmq.SUB)
|
||||||
|
@ -103,7 +113,7 @@ class CentralCommand(object):
|
||||||
elif hugvey_id not in self.hugveys:
|
elif hugvey_id not in self.hugveys:
|
||||||
if msg['event'] == 'connection':
|
if msg['event'] == 'connection':
|
||||||
# Create a hugvey
|
# Create a hugvey
|
||||||
self.instantiateHugvey(hugvey_id, msg)
|
await self.instantiateHugvey(hugvey_id, msg)
|
||||||
else:
|
else:
|
||||||
logger.warning("Message from uninstantiated Hugvey {}".format(hugvey_id))
|
logger.warning("Message from uninstantiated Hugvey {}".format(hugvey_id))
|
||||||
logger.debug("Message contains: {}".format(msg))
|
logger.debug("Message contains: {}".format(msg))
|
||||||
|
@ -135,20 +145,27 @@ class HugveyState(object):
|
||||||
self.command = command
|
self.command = command
|
||||||
self.logger = logging.getLogger(f"hugvey{self.id}")
|
self.logger = logging.getLogger(f"hugvey{self.id}")
|
||||||
self.loop = asyncio.new_event_loop()
|
self.loop = asyncio.new_event_loop()
|
||||||
|
self.isConfigured = False
|
||||||
|
|
||||||
def config(self, hostname, ip):
|
def config(self, hostname, ip):
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.hostname = hostname
|
self.hostname = hostname
|
||||||
self.logger.info(f"Hugvey {self.id} at {self.ip}, host: {self.hostname}")
|
self.logger.info(f"Hugvey {self.id} at {self.ip}, host: {self.hostname}")
|
||||||
|
|
||||||
|
if self.isConfigured == True:
|
||||||
|
# a reconfiguration/reconnection
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.isConfigured = True
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
# stop on isRunning.is_set() or wait()
|
# stop on isRunning.is_set() or wait()
|
||||||
# self.loop.create_task(self.startAudio())
|
# self.loop.create_task(self.startAudioProcessing())
|
||||||
tasks = asyncio.gather(self.startAudio(), loop=self.loop)
|
tasks = asyncio.gather(self.startAudioProcessing(), loop=self.loop)
|
||||||
self.loop.run_until_complete(tasks)
|
self.loop.run_until_complete(tasks)
|
||||||
# asyncio.run_coroutine_threadsafe(self._start(), self.loop)
|
# asyncio.run_coroutine_threadsafe(self._start(), self.loop)
|
||||||
|
|
||||||
async def startAudio(self):
|
async def startAudioProcessing(self):
|
||||||
'''
|
'''
|
||||||
Start the audio streamer service
|
Start the audio streamer service
|
||||||
'''
|
'''
|
||||||
|
@ -163,4 +180,13 @@ class HugveyState(object):
|
||||||
player = Player(self.command.config['voice']['src_rate'], self.command.config['voice']['out_rate'])
|
player = Player(self.command.config['voice']['src_rate'], self.command.config['voice']['out_rate'])
|
||||||
streamer.addConsumer(player)
|
streamer.addConsumer(player)
|
||||||
|
|
||||||
|
self.logger.info("Start Speech")
|
||||||
|
google = GoogleVoiceClient(
|
||||||
|
hugvey_id=self.id,
|
||||||
|
src_rate=self.command.config['voice']['src_rate'],
|
||||||
|
credential_file=self.command.config['voice']['google_credentials'],
|
||||||
|
language_code='en-US'
|
||||||
|
)
|
||||||
|
streamer.addConsumer(google)
|
||||||
|
|
||||||
await streamer.run()
|
await streamer.run()
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import pyaudio
|
||||||
|
import re
|
||||||
|
import socket
|
||||||
|
import os
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
from google.cloud.speech import enums
|
||||||
|
from google.cloud.speech import types
|
||||||
|
from google.auth.credentials import Credentials
|
||||||
|
|
||||||
|
from google.cloud import speech
|
||||||
|
import asyncio
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger("voice")
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleVoiceClient(object):
|
||||||
|
def __init__(self, hugvey_id, src_rate, credential_file, language_code = "en_GB"):
|
||||||
|
self.src_rate = src_rate
|
||||||
|
self.language_code = language_code
|
||||||
|
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = credential_file
|
||||||
|
|
||||||
|
# Create a thread-safe buffer of audio data
|
||||||
|
self.buffer = queue.Queue()
|
||||||
|
self.isRunning = False
|
||||||
|
self.target_rate = 16000
|
||||||
|
self.cv_laststate = None
|
||||||
|
|
||||||
|
self.task = threading.Thread(target=self.run, name=f"voice-hv{hugvey_id}" + str(uuid.uuid1()))
|
||||||
|
self.task.setDaemon(True)
|
||||||
|
self.task.start()
|
||||||
|
|
||||||
|
def generator(self):
|
||||||
|
while self.isRunning:
|
||||||
|
yield self.buffer.get()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.isRunning = True
|
||||||
|
|
||||||
|
while self.isRunning:
|
||||||
|
try:
|
||||||
|
self.speech_client = speech.SpeechClient()
|
||||||
|
config = types.RecognitionConfig(
|
||||||
|
encoding=enums.RecognitionConfig.AudioEncoding.LINEAR16,
|
||||||
|
sample_rate_hertz=self.src_rate,
|
||||||
|
language_code=self.language_code)
|
||||||
|
self.streaming_config = types.StreamingRecognitionConfig(
|
||||||
|
config=config,
|
||||||
|
interim_results=True)
|
||||||
|
|
||||||
|
audio_generator = self.generator()
|
||||||
|
requests = (types.StreamingRecognizeRequest(audio_content=content)
|
||||||
|
for content in audio_generator)
|
||||||
|
responses = self.speech_client.streaming_recognize(
|
||||||
|
self.streaming_config, requests)
|
||||||
|
|
||||||
|
logger.info("Starting voice loop")
|
||||||
|
for response in responses:
|
||||||
|
if not response.results:
|
||||||
|
continue
|
||||||
|
"""Iterates through server responses and prints them.
|
||||||
|
|
||||||
|
The responses passed is a generator that will block until a response
|
||||||
|
is provided by the server.
|
||||||
|
|
||||||
|
Each response may contain multiple results, and each result may contain
|
||||||
|
multiple alternatives; for details, see https://goo.gl/tjCPAU. Here we
|
||||||
|
print only the transcription for the top alternative of the top result.
|
||||||
|
|
||||||
|
In this case, responses are provided for interim results as well. If the
|
||||||
|
response is an interim one, print a line feed at the end of it, to allow
|
||||||
|
the next result to overwrite it, until the response is a final one. For the
|
||||||
|
final one, print a newline to preserve the finalized transcription.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The `results` list is consecutive. For streaming, we only care about
|
||||||
|
# the first result being considered, since once it's `is_final`, it
|
||||||
|
# moves on to considering the next utterance.
|
||||||
|
result = response.results[0]
|
||||||
|
if not result.alternatives:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Display the transcription of the top alternative.
|
||||||
|
transcript = result.alternatives[0].transcript
|
||||||
|
|
||||||
|
# logger.debug("Text: ".format(transcript))
|
||||||
|
|
||||||
|
if not result.is_final:
|
||||||
|
logger.debug(f"Text: {transcript}")
|
||||||
|
else:
|
||||||
|
logger.info(f"Text: {transcript}")
|
||||||
|
|
||||||
|
if not self.isRunning:
|
||||||
|
logger.warn("Stopping voice loop")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(f"Crashed Google Voice: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def receive(self, chunk):
|
||||||
|
if not self.task.isAlive():
|
||||||
|
raise Exception("Voice thread died")
|
||||||
|
|
||||||
|
if self.src_rate == self.target_rate:
|
||||||
|
data = chunk
|
||||||
|
else:
|
||||||
|
data, self.cv_laststate = audioop.ratecv(chunk, 2, 1, self.src_rate, self.target_rate, self.cv_laststate)
|
||||||
|
self.buffer.put_nowait(data)
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
self.isRunning = False
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,4 +6,5 @@ voice:
|
||||||
out_rate: 44100
|
out_rate: 44100
|
||||||
port: 4444
|
port: 4444
|
||||||
chunk: 2972
|
chunk: 2972
|
||||||
|
google_credentials: "/home/ruben/Documents/Projecten/2018/Hugvey/test_googlespeech/My First Project-0c7833e0d5fa.json"
|
||||||
hugveys: 3
|
hugveys: 3
|
Loading…
Reference in a new issue