annotate 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
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
1466
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
1 """
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
2 play sounds according to POST requests.
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
3 """
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
4 from docopt import docopt
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
5 import cyclone.web
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
6 import sys, tempfile, itertools
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
7 from twisted.internet import reactor
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
8 from cyclone.httpclient import fetch
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
9 from generator import tts
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
10 import xml.etree.ElementTree as ET
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
11 from twisted.web.static import File
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
12 from standardservice.logsetup import log, verboseLogging
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
13 import os
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
14
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
15 soundCount = itertools.count()
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
16
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
17 def makeSpeech(speech, fast=False):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
18 speechWav = tempfile.NamedTemporaryFile(suffix='.wav')
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
19
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
20 root = ET.Element("SABLE")
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
21 r = ET.SubElement(root, "RATE",
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
22 attrib=dict(SPEED="+50%" if fast else "+0%"))
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
23 for sentence in speech.split('.'):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
24 div = ET.SubElement(r, "DIV")
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
25 div.set("TYPE", "sentence")
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
26 div.text = sentence
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
27
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
28 speechSecs = tts(speech, speechWav.name)
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
29 return pygame.mixer.Sound(speechWav.name), speechSecs
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
30
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
31 class LOADING(object): pass
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
32
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
33 class SoundEffects(object):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
34 def __init__(self):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
35 self.buffers = {} # URIRef : pygame.mixer.Sound
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
36 self.playingSources = []
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
37 self.queued = []
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
38 self.volume = 1 # level for the next sound that's played (or existing instances of the same sound)
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
39
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
40 def _getSound(self, uri):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
41 def done(resp):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
42 path = '/tmp/sound_%s' % next(soundCount)
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
43 with open(path, 'w') as out:
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
44 out.write(resp.body)
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
45 log.info('write %s bytes to %s', len(resp.body), path)
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
46 self.buffers[uri] = pygame.mixer.Sound(path)
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
47
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
48 return fetch(uri).addCallback(done).addErrback(log.error)
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
49
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
50 def playEffect(self, uri):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
51 if uri not in self.buffers:
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
52 self.buffers[uri] = LOADING
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
53 self._getSound(uri).addCallback(lambda ret: self.playEffect(uri))
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
54 return
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
55 if self.buffers[uri] is LOADING:
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
56 # The first playback loads then plays, but any attempts
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
57 # during that load are dropped, not queued.
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
58 return
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
59 snd = self.buffers[uri]
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
60 snd.set_volume(self.volume)
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
61 return self.playBuffer(snd)
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
62
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
63 def playSpeech(self, txt, preEffect=None, postEffect=None, preEffectOverlap=0):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
64 buf, secs = makeSpeech(txt)
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
65 t = 0
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
66 if preEffect:
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
67 t += self.playEffect(preEffect)
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
68
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
69 self.playingSources.append(buf)
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
70 reactor.callLater(secs + .1, self.done, buf)
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
71 return secs
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
72
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
73 def done(self, src):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
74 try:
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
75 self.playingSources.remove(src)
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
76 except ValueError:
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
77 pass
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
78
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
79 def stopAll(self):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
80 while self.playingSources:
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
81 self.playingSources.pop().stop()
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
82 for q in self.queued:
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
83 q.cancel()
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
84 # doesn't cover the callLater ones
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
85
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
86
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
87 class Index(cyclone.web.RequestHandler):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
88 def get(self):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
89 self.render('index.html', effectNames=[
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
90 dict(name=k, postUri='effects/%s' % k)
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
91 for k in self.settings.sfx.buffers.keys()])
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
92
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
93 class Speak(cyclone.web.RequestHandler):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
94 def post(self):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
95 self.settings.sfx.playSpeech(self.get_argument('msg'))
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
96 return "ok"
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
97
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
98 class PlaySound(cyclone.web.RequestHandler):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
99 def post(self):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
100 uri = self.get_argument('uri')
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
101 self.settings.sfx.playEffect(uri)
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
102 return "ok"
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
103
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
104 class Volume(cyclone.web.RequestHandler):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
105 def put(self):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
106 self.settings.sfx.setVolume(float(self.get_argument('v')))
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
107 return "ok"
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
108
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
109 class StopAll(cyclone.web.RequestHandler):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
110 def post(self):
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
111 self.settings.sfx.stopAll()
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
112 return "ok"
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
113
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
114
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
115 if __name__ == '__main__':
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
116 arg = docopt('''
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
117 Usage: playSound.py [options]
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
118
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
119 -v Verbose
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
120 ''')
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
121 verboseLogging(arg['-v'])
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
122
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
123 import pygame
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
124 print('mixer init pulse')
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
125 import pygame.mixer
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
126 pygame.mixer.init()
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
127 sfx = SoundEffects()
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
128
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
129 reactor.listenTCP(9049, cyclone.web.Application(handlers=[
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
130 (r'/', Index),
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
131 (r'/speak', Speak),
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
132 (r'/playSound', PlaySound),
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
133 (r'/volume', Volume),
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
134 (r'/stopAll', StopAll),
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
135 (r'/static/(.*)', cyclone.web.StaticFileHandler, {'path': 'static'}),
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
136 ], template_path='.', sfx=sfx))
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
137 reactor.run()
bf05f33c3b3a start rewrite for playSound
drewp <drewp@bigasterisk.com>
parents:
diff changeset
138 server.app.run(endpoint_description=r"tcp6:port=9049:interface=\:\:")