Mercurial > code > home > repos > homeauto
view service/playSound/speechMusic.py @ 1466:bf05f33c3b3a
start rewrite for playSound
Ignore-this: a4d4ccceea8ef0ba996ca2d81bf634c6
darcs-hash:76e853b388c95a2e4ba83637170effe872e997a8
author | drewp <drewp@bigasterisk.com> |
---|---|
date | Thu, 19 Dec 2019 16:45:40 -0800 |
parents | |
children |
line wrap: on
line source
""" play sounds according to POST requests. """ from docopt import docopt import cyclone.web import sys, tempfile, itertools from twisted.internet import reactor from cyclone.httpclient import fetch from generator import tts import xml.etree.ElementTree as ET from twisted.web.static import File from standardservice.logsetup import log, verboseLogging import os soundCount = itertools.count() def makeSpeech(speech, fast=False): speechWav = tempfile.NamedTemporaryFile(suffix='.wav') root = ET.Element("SABLE") r = ET.SubElement(root, "RATE", attrib=dict(SPEED="+50%" if fast else "+0%")) for sentence in speech.split('.'): div = ET.SubElement(r, "DIV") div.set("TYPE", "sentence") div.text = sentence speechSecs = tts(speech, speechWav.name) return pygame.mixer.Sound(speechWav.name), speechSecs class LOADING(object): pass class SoundEffects(object): def __init__(self): self.buffers = {} # URIRef : pygame.mixer.Sound self.playingSources = [] self.queued = [] self.volume = 1 # level for the next sound that's played (or existing instances of the same sound) def _getSound(self, uri): def done(resp): path = '/tmp/sound_%s' % next(soundCount) with open(path, 'w') as out: out.write(resp.body) log.info('write %s bytes to %s', len(resp.body), path) self.buffers[uri] = pygame.mixer.Sound(path) return fetch(uri).addCallback(done).addErrback(log.error) def playEffect(self, uri): if uri not in self.buffers: self.buffers[uri] = LOADING self._getSound(uri).addCallback(lambda ret: self.playEffect(uri)) return if self.buffers[uri] is LOADING: # The first playback loads then plays, but any attempts # during that load are dropped, not queued. return snd = self.buffers[uri] snd.set_volume(self.volume) return self.playBuffer(snd) def playSpeech(self, txt, preEffect=None, postEffect=None, preEffectOverlap=0): buf, secs = makeSpeech(txt) t = 0 if preEffect: t += self.playEffect(preEffect) self.playingSources.append(buf) reactor.callLater(secs + .1, self.done, buf) return secs def done(self, src): try: self.playingSources.remove(src) except ValueError: pass def stopAll(self): while self.playingSources: self.playingSources.pop().stop() for q in self.queued: q.cancel() # doesn't cover the callLater ones class Index(cyclone.web.RequestHandler): def get(self): self.render('index.html', effectNames=[ dict(name=k, postUri='effects/%s' % k) for k in self.settings.sfx.buffers.keys()]) class Speak(cyclone.web.RequestHandler): def post(self): self.settings.sfx.playSpeech(self.get_argument('msg')) return "ok" class PlaySound(cyclone.web.RequestHandler): def post(self): uri = self.get_argument('uri') self.settings.sfx.playEffect(uri) return "ok" class Volume(cyclone.web.RequestHandler): def put(self): self.settings.sfx.setVolume(float(self.get_argument('v'))) return "ok" class StopAll(cyclone.web.RequestHandler): def post(self): self.settings.sfx.stopAll() return "ok" if __name__ == '__main__': arg = docopt(''' Usage: playSound.py [options] -v Verbose ''') verboseLogging(arg['-v']) import pygame print('mixer init pulse') import pygame.mixer pygame.mixer.init() sfx = SoundEffects() reactor.listenTCP(9049, cyclone.web.Application(handlers=[ (r'/', Index), (r'/speak', Speak), (r'/playSound', PlaySound), (r'/volume', Volume), (r'/stopAll', StopAll), (r'/static/(.*)', cyclone.web.StaticFileHandler, {'path': 'static'}), ], template_path='.', sfx=sfx)) reactor.run() server.app.run(endpoint_description=r"tcp6:port=9049:interface=\:\:")