Mercurial > code > home > repos > homeauto
changeset 1467:667794accec2
drop speech synth, try to get pulse client working
Ignore-this: f032ddd2169cbb668c11ed7d48f7de23
darcs-hash:66edd441656c5f73888aff4627729b33d416d586
author | drewp <drewp@bigasterisk.com> |
---|---|
date | Thu, 19 Dec 2019 17:33:24 -0800 |
parents | bf05f33c3b3a |
children | 32f12665a620 |
files | service/playSound/Dockerfile service/playSound/playSound.py service/playSound/pulse-client.conf service/playSound/serv.n3 service/playSound/speechMusic.py |
diffstat | 5 files changed, 127 insertions(+), 152 deletions(-) [+] |
line wrap: on
line diff
--- a/service/playSound/Dockerfile Thu Dec 19 16:45:40 2019 -0800 +++ b/service/playSound/Dockerfile Thu Dec 19 17:33:24 2019 -0800 @@ -3,25 +3,35 @@ WORKDIR /opt - apt install libsdl-mixer-dev - apt install libsdl-mixer1.2-dev -wget https://www.pygame.org/ftp/pygame-1.9.6.tar.gz - 34 tar xvzf pygame-1.9.6.tar.gz - 35 cd pygame-1.9.6 - python3 setup.py install -python3 -c 'import pygame; print(pygame.__version__, pygame.__file__); pygame.mixer.init()' +#RUN apt-get install -y libsdl-mixer-dev libsdl-mixer1.2-dev +# wget https://www.pygame.org/ftp/pygame-1.9.6.tar.gz +# 34 tar xvzf pygame-1.9.6.tar.gz +# 35 cd pygame-1.9.6 +# python3 setup.py install +# python3 -c 'import pygame; print(pygame.__version__, pygame.__file__); pygame.mixer.init()' RUN touch need-new-update RUN apt-get update -RUN apt-get install --yes libopenal1 libogg0 pulseaudio-utils festival sox python-pygame +RUN apt-get install --yes libopenal1 libogg0 pulseaudio-utils python-pygame COPY requirements.txt ./ RUN pip3 install --index-url https://projects.bigasterisk.com/ --extra-index-url https://pypi.org/simple -r requirements.txt ENV SDL_AUDIODRIVER pulse -ENV PULSE_SERVER /tmp/pulseaudio COPY pulse-client.conf /etc/pulse/client.conf -COPY *.py req* *.jade ./ +COPY *.py ./ + +# ENV UNAME pulseuser +# RUN export UNAME=$UNAME UID=501 GID=501 && \ +# mkdir -p "/home/${UNAME}" && \ +# echo "${UNAME}:x:${UID}:${GID}:${UNAME} User,,,:/home/${UNAME}:/bin/bash" >> /etc/passwd && \ +# echo "${UNAME}:x:${UID}:" >> /etc/group && \ +# mkdir -p /etc/sudoers.d && \ +# echo "${UNAME} ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/${UNAME} && \ +# chmod 0440 /etc/sudoers.d/${UNAME} && \ +# chown ${UID}:${GID} -R /home/${UNAME} && \ +# gpasswd -a ${UNAME} audio +# USER $UNAME EXPOSE 9049
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/service/playSound/playSound.py Thu Dec 19 17:33:24 2019 -0800 @@ -0,0 +1,102 @@ +""" +play sounds according to POST requests. +""" +from docopt import docopt +import cyclone.web +import os, sys, tempfile, itertools +from twisted.internet import reactor +from cyclone.httpclient import fetch +from twisted.web.static import File +from standardservice.logsetup import log, verboseLogging + +soundCount = itertools.count() + +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 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') + +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'/playSound', PlaySound), + (r'/volume', Volume), + (r'/stopAll', StopAll), + ], template_path='.', sfx=sfx)) + reactor.run() +server.app.run(endpoint_description=r"tcp6:port=9049:interface=\:\:")
--- a/service/playSound/pulse-client.conf Thu Dec 19 16:45:40 2019 -0800 +++ b/service/playSound/pulse-client.conf Thu Dec 19 17:33:24 2019 -0800 @@ -1,4 +1,4 @@ -default-server = /tmp/pulseaudio +default-server = unix:/run/user/501/pulse/native # Prevent a server running in the container autospawn = no @@ -6,3 +6,4 @@ # Prevent the use of shared memory enable-shm = false +enable-memfd = false \ No newline at end of file
--- a/service/playSound/serv.n3 Thu Dec 19 16:45:40 2019 -0800 +++ b/service/playSound/serv.n3 Thu Dec 19 17:33:24 2019 -0800 @@ -8,16 +8,16 @@ :serverHost "bang"; :internalPort 9049; :prodDockerFlags ( - "-p" "9049:9049" + "-p" "9049:9049" "--privileged" "--net=host"); :localDockerFlags ( "--cap-add" "SYS_PTRACE" #"--mount type=bind,source=/etc/pulse,target=/etc/pulse" "--mount" "type=tmpfs,destination=/tmp,tmpfs-size=52428800" - "--mount" "type=bind,source=/tmp/pulseaudio,target=/tmp/pulseaudio" + "--mount" "type=bind,source=/run/user/501/pulse,target=/run/user/501/pulse" ); :localRunCmdline ( - "strace" "-ftts" "999" +# "strace" "-ftts" "999" "python3" "playSound.py" "-v"); :dockerFile "Dockerfile" .
--- a/service/playSound/speechMusic.py Thu Dec 19 16:45:40 2019 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,138 +0,0 @@ -""" -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=\:\:")