# HG changeset patch # User drewp # Date 1577003851 28800 # Node ID dda4c0bcca65d75c2500947474cb6564cdafa4d7 # Parent 90e1ef015f09226ac74678193b40677a8ec8a32f announce.py start Ignore-this: d5a7fb8eb2fa709be2686991b8c31cee darcs-hash:9c2f3b7b31c7503a44deaed6bb41e93a2b7e205d diff -r 90e1ef015f09 -r dda4c0bcca65 service/announce/announce.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/service/announce/announce.py Sun Dec 22 00:37:31 2019 -0800 @@ -0,0 +1,115 @@ +""" +run TTS and send speech or other sounds to playSound and to chromecasts +""" +import subprocess, time, re + +from docopt import docopt +from lru import LRU +from twisted.internet import utils, reactor +from twisted.internet.defer import inlineCallbacks +from urllib.parse import urlencode +import cyclone.web +import treq + +from standardservice.logsetup import log, verboseLogging + + +recentFiles = LRU(50) + + +class Index(cyclone.web.RequestHandler): + def get(self): + self.render('index.html') + + +class PostAnnouncement(cyclone.web.RequestHandler): + @inlineCallbacks + def post(self): + text = self.get_argument('text') + evType = self.get_argument('evType', default=None) + if evType is not None: + text = f'{evType}!' + text + + url = yield tts(text) + + yield treq.post(r'http://dash:9049/playSound', params={b'uri': url}) + #chromecastPlay('10.2.0.60', url) # ari + #chromecastPlay('10.2.0.61', url) # bed + #chromecastPlay('10.2.0.62', url) # asher + + return url + +@inlineCallbacks +def chromecastPlay(ip, url, volume=None): + cmdPrefix = ['/opt/cast', '--timeout', '3s', '--host', ip] + + if volume is not None: + m = re.search(r'Volume: [\.0-9]+', + subprocess.check_output(cmdPrefix + ['status'])) + oldVolume = float(m.group(1)) + subprocess.check_output(cmdPrefix + ['volume', str(volume)]) + + output = yield utils.getProcessValue( + cmdPrefix[0], args=cmdPrefix[1:] + [ + 'media', 'play', url.decode('utf8')]) + print('cast out', output) + + if volume is not None: + subprocess.check_call(cmdPrefix + ['volume', str(oldVolume)]) + + +class Data(cyclone.web.RequestHandler): + def get(self, which): + print('getting ', which) + self.set_header('Content-Type', 'audio/wav') + self.write(recentFiles[which]) + + +@inlineCallbacks +def tts(input_xml, affect='surprise', ptype='disagree'): + mary_host = "172.17.0.1" + mary_port = "59125" + + maryxml = f''' + +

+ {input_xml} +

+
''' + + query_hash = {"INPUT_TEXT": maryxml, + "INPUT_TYPE": "RAWMARYXML", + "LOCALE": "en_US", + "VOICE": "cmu-slt-hsmm", + "OUTPUT_TYPE": "AUDIO", + "AUDIO": "WAVE", + } + query = urlencode(query_hash).replace('+', '%20') + + resp = yield treq.post( + "http://%s:%s/process?" % (mary_host, mary_port) + query) + wav = yield treq.content(resp) + which = '%.1f' % time.time() + recentFiles[which] = wav + url = f'http://10.2.0.1:9010/data/{which}' + print('save', len(wav), 'bytes at', url) + return url.encode('ascii') + + +if __name__ == '__main__': + arg = docopt(''' + Usage: announce.py [options] + + -v Verbose + ''') + verboseLogging(arg['-v']) + + reactor.listenTCP(9010, cyclone.web.Application(handlers=[ + (r'/', Index), + (r'/announcement', PostAnnouncement), + (r'/data/(.*)', Data), + ], template_path='.')) + reactor.run()