Semi working google voice

This commit is contained in:
Ruben van de Ven 2019-01-17 20:28:55 +01:00
parent a6c0739f27
commit 8f68505ff4
3 changed files with 164 additions and 16 deletions

View file

@ -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
h = HugveyState(hugvey_id, self) if not hugvey_id in self.hugveys:
h.config(msg['host'],msg['ip']) logger.info(f'Instantiate hugvey #{hugvey_id}')
h = HugveyState(hugvey_id, self)
h.config(msg['host'],msg['ip'])
self.hugveys[hugvey_id] = h
thread = threading.Thread(target=h.start, name=f"hugvey#{hugvey_id}")
thread.start()
else:
logger.info(f'Reconfigure hugvey #{hugvey_id}')
# (re)configure exisitng hugveys
h.config(msg['host'],msg['ip'])
thread = threading.Thread(target=h.start)
thread.start()
# self.tasks['hv{}'.format(hugvey_id)] = self.loop.create_task(h.start())
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()

View file

@ -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

View file

@ -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