hlrc / tts_bridge / mary / mary_tts_bridge / MaryTTSBridge.py @ e21d7f2c
History | View | Annotate | Download (5.358 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 | 0c15613f | Simon Schulz | from MaryTTSClient import * |
40 | from cStringIO import StringIO |
||
41 | |||
42 | class MaryTTSBridge(object): |
||
43 | #_feedback = ttsActionFeedback()
|
||
44 | #_result = ttsActionResult()
|
||
45 | |||
46 | |||
47 | def __init__(self, topic, voice="cmu-slt-hsmm", locale="en_GB", tts_host="127.0.0.1", tts_port=59125, loglevel=logging.WARNING): |
||
48 | """initialise
|
||
49 | :param loglevel: optional log level
|
||
50 | """
|
||
51 | self.loglevel = loglevel
|
||
52 | self.logger = logging.getLogger(__name__)
|
||
53 | # create nice and actually usable formatter and add it to the handler
|
||
54 | self.config_logger(loglevel)
|
||
55 | 163a7434 | Simon Schulz | self.logger.info("starting MaryTTSBridge on topic '"+topic+"'") |
56 | 0c15613f | Simon Schulz | |
57 | self.tts_client = MaryTTSClient(voice, locale, tts_host, tts_port, loglevel)
|
||
58 | |||
59 | rospy.init_node('MaryTTSBridge')
|
||
60 | |||
61 | self._action_name = topic
|
||
62 | self._as = actionlib.SimpleActionServer(self._action_name, ttsAction, execute_cb = self.execute_cb, auto_start = False) |
||
63 | self._as.start()
|
||
64 | |||
65 | |||
66 | |||
67 | def __del__(self): |
||
68 | """destructor
|
||
69 | """
|
||
70 | self.logger.debug("destructor of MaryTTSBridge called") |
||
71 | |||
72 | def config_logger(self, level): |
||
73 | """initialise a nice logger formatting
|
||
74 | :param level: log level
|
||
75 | """
|
||
76 | formatter = logging.Formatter('%(asctime)s %(name)-30s %(levelname)-8s > %(message)s')
|
||
77 | ch = logging.StreamHandler() |
||
78 | #ch.setLevel(level)
|
||
79 | ch.setFormatter(formatter) |
||
80 | self.logger.setLevel(level)
|
||
81 | self.logger.addHandler(ch)
|
||
82 | |||
83 | def create_soundchunk(self, audio_data): |
||
84 | #extract wave from data
|
||
85 | fio = BytesIO(audio_data) |
||
86 | wav = wave.open(fio) |
||
87 | |||
88 | s = soundchunk() |
||
89 | |||
90 | s.channels = wav.getnchannels() |
||
91 | s.data = audio_data |
||
92 | s.endianess = s.ENDIAN_LITTLE #guessed?!
|
||
93 | s.rate = wav.getframerate() |
||
94 | s.samplecount = wav.getnframes() |
||
95 | |||
96 | |||
97 | #sample format:
|
||
98 | sample_width = wav.getsampwidth() |
||
99 | if (sample_width == 1): |
||
100 | s.sample_type = s.SAMPLE_U8 |
||
101 | elif (sample_width == 2): |
||
102 | s.sample_type = s.SAMPLE_U16 |
||
103 | elif (sample_width == 3): |
||
104 | s.sample_type = s.SAMPLE_U24 |
||
105 | else:
|
||
106 | self.logger.error("ERROR: invalid sample width "+str(sample_width) + " detected") |
||
107 | s = soundchunk() |
||
108 | |||
109 | self.logger.info("created soundchunk with "+str(s.samplecount)+" samples") |
||
110 | |||
111 | return s
|
||
112 | |||
113 | def create_phonemes(self, phoneme_str): |
||
114 | last = 0.0
|
||
115 | plist = [] |
||
116 | |||
117 | sio = StringIO(phoneme_str) |
||
118 | for line in sio: |
||
119 | if (line[0] != '#'): |
||
120 | phoneme_list = line.split(" ")
|
||
121 | e21d7f2c | Simon Schulz | if (line == '\n'): |
122 | #ignore empty lines
|
||
123 | continue
|
||
124 | elif (len(phoneme_list) != 3): |
||
125 | print("> could not split line '%s' during phoneme seperation\n" % (line))
|
||
126 | else:
|
||
127 | symbol = phoneme_list[2]
|
||
128 | symbol = symbol.rstrip() |
||
129 | |||
130 | now = float(phoneme_list[0]) |
||
131 | duration = (now - last)*1000
|
||
132 | last = now |
||
133 | plist.append(phoneme(symbol, int(duration)))
|
||
134 | 0c15613f | Simon Schulz | |
135 | self.logger.info("created phonemelist with " + str(len(plist)) + " elements") |
||
136 | |||
137 | return plist
|
||
138 | |||
139 | def create_utterance(self, text, audio_data, phoneme_list): |
||
140 | u = utterance() |
||
141 | u.text = text |
||
142 | u.audio = self.create_soundchunk(audio_data)
|
||
143 | u.phonemes = self.create_phonemes(phoneme_list)
|
||
144 | |||
145 | self.logger.info("created utterance for 'phonemelist with '" + u.text + "'") |
||
146 | return u
|
||
147 | |||
148 | e21d7f2c | Simon Schulz | def get_error_message(self): |
149 | data_wav = pkgutil.get_data('mary_tts_bridge', 'data/connection_failed.wav') |
||
150 | data_phonemes = pkgutil.get_data('mary_tts_bridge', 'data/connection_failed.phonemes') |
||
151 | return (data_wav, data_phonemes)
|
||
152 | |||
153 | 0c15613f | Simon Schulz | def execute_cb(self, goal): |
154 | self.logger.info("incoming utterance '" + goal.text + "'") |
||
155 | |||
156 | success = True
|
||
157 | result = ttsResult() |
||
158 | |||
159 | #incoming msg, ask mary tts for data:
|
||
160 | try:
|
||
161 | audio = self.tts_client.generate_audio(goal.text)
|
||
162 | phonelist = self.tts_client.generate_phonemes(goal.text)
|
||
163 | |||
164 | except:
|
||
165 | self.logger.error("failed to create utterance error = '" + str(sys.exc_info()[1]) + "'") |
||
166 | e21d7f2c | Simon Schulz | #try to open error message from file:
|
167 | success = True
|
||
168 | (audio, phonelist) = self.get_error_message()
|
||
169 | 0c15613f | Simon Schulz | |
170 | if success:
|
||
171 | #build soundchunk
|
||
172 | result.utterance = self.create_utterance(goal.text, audio, phonelist)
|
||
173 | self._as.set_succeeded(result)
|
||
174 | else:
|
||
175 | self._as.set_aborted(result)
|
||
176 | |||
177 | def run(self): |
||
178 | #run the main loop
|
||
179 | rospy.spin() |
||
180 | |||
181 | #test code
|
||
182 | 163a7434 | Simon Schulz | def main(): |
183 | 0c15613f | Simon Schulz | if (len(sys.argv) != 2): |
184 | print("> usage: "+sys.argv[0]+" <topic>\n\n") |
||
185 | sys.exit(1)
|
||
186 | |||
187 | bridge = MaryTTSBridge(topic=sys.argv[1], loglevel=logging.INFO)
|
||
188 | bridge.run() |
||
189 | |||
190 | 163a7434 | Simon Schulz | if __name__ == "__main__": |
191 | main() |