hlrc / tts_bridge / mary / mary_tts_bridge / MaryTTSBridge.py @ 836f314c
History | View | Annotate | Download (5.45 KB)
1 | 0c15613f | Simon Schulz | #!/usr/bin/python
|
---|---|---|---|
2 | """
|
||
3 | This file is part of hlrc
|
||
4 |
|
||
5 | Copyright(c) sschulz <AT> techfak.uni-bielefeld.de
|
||
6 | http://opensource.cit-ec.de/projects/hlrc
|
||
7 |
|
||
8 | This file may be licensed under the terms of the
|
||
9 | GNU General Public License Version 3 (the ``GPL''),
|
||
10 | or (at your option) any later version.
|
||
11 |
|
||
12 | Software distributed under the License is distributed
|
||
13 | on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
|
||
14 | express or implied. See the GPL for the specific language
|
||
15 | governing rights and limitations.
|
||
16 |
|
||
17 | You should have received a copy of the GPL along with this
|
||
18 | program. If not, go to http://www.gnu.org/licenses/gpl.html
|
||
19 | or write to the Free Software Foundation, Inc.,
|
||
20 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
21 |
|
||
22 | The development of this software was supported by the
|
||
23 | Excellence Cluster EXC 277 Cognitive Interaction Technology.
|
||
24 | The Excellence Cluster EXC 277 is a grant of the Deutsche
|
||
25 | Forschungsgemeinschaft (DFG) in the context of the German
|
||
26 | Excellence Initiative.
|
||
27 | """
|
||
28 | |||
29 | import logging |
||
30 | import rospy |
||
31 | from hlrc_server.msg import * |
||
32 | import time |
||
33 | import sys |
||
34 | import actionlib |
||
35 | from io import BytesIO |
||
36 | import wave |
||
37 | e21d7f2c | Simon Schulz | import os |
38 | import pkgutil |
||
39 | 836f314c | Robert Haschke | from .MaryTTSClient import * |
40 | try:
|
||
41 | from io import StringIO |
||
42 | except ImportError: |
||
43 | from cStringIO import StringIO |
||
44 | 0c15613f | Simon Schulz | |
45 | class MaryTTSBridge(object): |
||
46 | #_feedback = ttsActionFeedback()
|
||
47 | #_result = ttsActionResult()
|
||
48 | |||
49 | |||
50 | def __init__(self, topic, voice="cmu-slt-hsmm", locale="en_GB", tts_host="127.0.0.1", tts_port=59125, loglevel=logging.WARNING): |
||
51 | """initialise
|
||
52 | :param loglevel: optional log level
|
||
53 | """
|
||
54 | self.loglevel = loglevel
|
||
55 | self.logger = logging.getLogger(__name__)
|
||
56 | # create nice and actually usable formatter and add it to the handler
|
||
57 | self.config_logger(loglevel)
|
||
58 | 163a7434 | Simon Schulz | self.logger.info("starting MaryTTSBridge on topic '"+topic+"'") |
59 | 0c15613f | Simon Schulz | |
60 | self.tts_client = MaryTTSClient(voice, locale, tts_host, tts_port, loglevel)
|
||
61 | |||
62 | f1d5f58f | sschulz | rospy.init_node('MaryTTSBridge', anonymous=True) |
63 | 0c15613f | Simon Schulz | |
64 | self._action_name = topic
|
||
65 | self._as = actionlib.SimpleActionServer(self._action_name, ttsAction, execute_cb = self.execute_cb, auto_start = False) |
||
66 | self._as.start()
|
||
67 | |||
68 | |||
69 | |||
70 | def __del__(self): |
||
71 | """destructor
|
||
72 | """
|
||
73 | self.logger.debug("destructor of MaryTTSBridge called") |
||
74 | |||
75 | def config_logger(self, level): |
||
76 | """initialise a nice logger formatting
|
||
77 | :param level: log level
|
||
78 | """
|
||
79 | formatter = logging.Formatter('%(asctime)s %(name)-30s %(levelname)-8s > %(message)s')
|
||
80 | ch = logging.StreamHandler() |
||
81 | #ch.setLevel(level)
|
||
82 | ch.setFormatter(formatter) |
||
83 | self.logger.setLevel(level)
|
||
84 | self.logger.addHandler(ch)
|
||
85 | |||
86 | def create_soundchunk(self, audio_data): |
||
87 | #extract wave from data
|
||
88 | fio = BytesIO(audio_data) |
||
89 | wav = wave.open(fio) |
||
90 | |||
91 | s = soundchunk() |
||
92 | |||
93 | s.channels = wav.getnchannels() |
||
94 | s.data = audio_data |
||
95 | s.endianess = s.ENDIAN_LITTLE #guessed?!
|
||
96 | s.rate = wav.getframerate() |
||
97 | s.samplecount = wav.getnframes() |
||
98 | |||
99 | |||
100 | #sample format:
|
||
101 | sample_width = wav.getsampwidth() |
||
102 | if (sample_width == 1): |
||
103 | s.sample_type = s.SAMPLE_U8 |
||
104 | elif (sample_width == 2): |
||
105 | s.sample_type = s.SAMPLE_U16 |
||
106 | elif (sample_width == 3): |
||
107 | s.sample_type = s.SAMPLE_U24 |
||
108 | else:
|
||
109 | self.logger.error("ERROR: invalid sample width "+str(sample_width) + " detected") |
||
110 | s = soundchunk() |
||
111 | |||
112 | self.logger.info("created soundchunk with "+str(s.samplecount)+" samples") |
||
113 | |||
114 | return s
|
||
115 | |||
116 | 836f314c | Robert Haschke | def create_phonemes(self, phoneme_bytes): |
117 | 0c15613f | Simon Schulz | last = 0.0
|
118 | plist = [] |
||
119 | |||
120 | 836f314c | Robert Haschke | sio = StringIO(phoneme_bytes.decode('ascii'))
|
121 | 0c15613f | Simon Schulz | for line in sio: |
122 | if (line[0] != '#'): |
||
123 | phoneme_list = line.split(" ")
|
||
124 | e21d7f2c | Simon Schulz | if (line == '\n'): |
125 | #ignore empty lines
|
||
126 | continue
|
||
127 | elif (len(phoneme_list) != 3): |
||
128 | print("> could not split line '%s' during phoneme seperation\n" % (line))
|
||
129 | else:
|
||
130 | symbol = phoneme_list[2]
|
||
131 | symbol = symbol.rstrip() |
||
132 | |||
133 | now = float(phoneme_list[0]) |
||
134 | duration = (now - last)*1000
|
||
135 | last = now |
||
136 | plist.append(phoneme(symbol, int(duration)))
|
||
137 | 0c15613f | Simon Schulz | |
138 | self.logger.info("created phonemelist with " + str(len(plist)) + " elements") |
||
139 | |||
140 | return plist
|
||
141 | |||
142 | def create_utterance(self, text, audio_data, phoneme_list): |
||
143 | u = utterance() |
||
144 | u.text = text |
||
145 | u.audio = self.create_soundchunk(audio_data)
|
||
146 | u.phonemes = self.create_phonemes(phoneme_list)
|
||
147 | |||
148 | self.logger.info("created utterance for 'phonemelist with '" + u.text + "'") |
||
149 | return u
|
||
150 | |||
151 | e21d7f2c | Simon Schulz | def get_error_message(self): |
152 | data_wav = pkgutil.get_data('mary_tts_bridge', 'data/connection_failed.wav') |
||
153 | data_phonemes = pkgutil.get_data('mary_tts_bridge', 'data/connection_failed.phonemes') |
||
154 | return (data_wav, data_phonemes)
|
||
155 | |||
156 | 0c15613f | Simon Schulz | def execute_cb(self, goal): |
157 | self.logger.info("incoming utterance '" + goal.text + "'") |
||
158 | |||
159 | success = True
|
||
160 | result = ttsResult() |
||
161 | |||
162 | #incoming msg, ask mary tts for data:
|
||
163 | try:
|
||
164 | audio = self.tts_client.generate_audio(goal.text)
|
||
165 | phonelist = self.tts_client.generate_phonemes(goal.text)
|
||
166 | |||
167 | except:
|
||
168 | self.logger.error("failed to create utterance error = '" + str(sys.exc_info()[1]) + "'") |
||
169 | e21d7f2c | Simon Schulz | #try to open error message from file:
|
170 | success = True
|
||
171 | (audio, phonelist) = self.get_error_message()
|
||
172 | 0c15613f | Simon Schulz | |
173 | if success:
|
||
174 | #build soundchunk
|
||
175 | result.utterance = self.create_utterance(goal.text, audio, phonelist)
|
||
176 | self._as.set_succeeded(result)
|
||
177 | else:
|
||
178 | self._as.set_aborted(result)
|
||
179 | |||
180 | def run(self): |
||
181 | #run the main loop
|
||
182 | rospy.spin() |
||
183 | |||
184 | #test code
|
||
185 | 163a7434 | Simon Schulz | def main(): |
186 | 0c15613f | Simon Schulz | if (len(sys.argv) != 2): |
187 | print("> usage: "+sys.argv[0]+" <topic>\n\n") |
||
188 | sys.exit(1)
|
||
189 | |||
190 | bridge = MaryTTSBridge(topic=sys.argv[1], loglevel=logging.INFO)
|
||
191 | bridge.run() |
||
192 | |||
193 | 163a7434 | Simon Schulz | if __name__ == "__main__": |
194 | main() |