changeset 666:91d17a68c21b

drop speech synth, try to get pulse client working Ignore-this: f032ddd2169cbb668c11ed7d48f7de23
author drewp@bigasterisk.com
date Thu, 19 Dec 2019 17:33:24 -0800
parents 9f9cb1d85c08
children 06a03b0832c8
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	Mon Aug 12 16:47:24 2019 -0700
+++ 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	Mon Aug 12 16:47:24 2019 -0700
+++ 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	Mon Aug 12 16:47:24 2019 -0700
+++ 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	Mon Aug 12 16:47:24 2019 -0700
+++ /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=\:\:")