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