Statistics
| Branch: | Tag: | Revision:

hlrc / tts_bridge / mary / mary_tts_bridge / MaryTTSBridge.py @ f27f1860

History | View | Annotate | Download (6.089 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 38936fe1 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
46 f27f1860 Robert Haschke
class MaryTTSBridge(object):
47
    # _feedback = ttsActionFeedback()
48
    # _result   = ttsActionResult()
49 0c15613f Simon Schulz
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 f27f1860 Robert Haschke
        """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
        self.logger.info("starting MaryTTSBridge on topic '"+topic+"'")
59 0c15613f Simon Schulz
60 f27f1860 Robert Haschke
        self.tts_client = MaryTTSClient(voice, locale, tts_host, tts_port, loglevel)
61 0c15613f Simon Schulz
62 f27f1860 Robert Haschke
        rospy.init_node('MaryTTSBridge', anonymous=True)
63 0c15613f Simon Schulz
64 f27f1860 Robert Haschke
        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 0c15613f Simon Schulz
68
    def __del__(self):
69 f27f1860 Robert Haschke
        """destructor
70
        """
71
        self.logger.debug("destructor of MaryTTSBridge called")
72 0c15613f Simon Schulz
73
    def config_logger(self, level):
74 f27f1860 Robert Haschke
        """initialise a nice logger formatting
75
        :param  level: log level
76
        """
77
        formatter = logging.Formatter('%(asctime)s %(name)-30s %(levelname)-8s > %(message)s')
78
        ch = logging.StreamHandler()
79
        #ch.setLevel(level)
80
        ch.setFormatter(formatter)
81
        self.logger.setLevel(level)
82
        self.logger.addHandler(ch)
83 0c15613f Simon Schulz
84
    def create_soundchunk(self, audio_data):
85 f27f1860 Robert Haschke
        #extract wave from data
86
        fio = BytesIO(audio_data)
87
        wav = wave.open(fio)
88
89
        s = soundchunk()
90
91
        s.channels = wav.getnchannels()
92
        s.data = audio_data
93
        s.endianess = s.ENDIAN_LITTLE  # guessed?!
94
        s.rate = wav.getframerate()
95
        s.samplecount = wav.getnframes()
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 0c15613f Simon Schulz
113 38936fe1 Robert Haschke
    def create_phonemes(self, phoneme_bytes):
114 f27f1860 Robert Haschke
        last = 0.0
115
        plist = []
116 0c15613f Simon Schulz
117 f27f1860 Robert Haschke
        sio = StringIO(phoneme_bytes.decode('ascii'))
118
        for line in sio:
119
            if (line[0] != '#'):
120
                phoneme_list = line.split(" ")
121 e21d7f2c Simon Schulz
                if (line == '\n'):
122 f27f1860 Robert Haschke
                    # ignore empty lines
123 e21d7f2c Simon Schulz
                    continue
124
                elif (len(phoneme_list) != 3):
125
                    print("> could not split line '%s' during phoneme seperation\n" % (line))
126
                else:
127 f27f1860 Robert Haschke
                    symbol = phoneme_list[2]
128
                    symbol = symbol.rstrip()
129 e21d7f2c Simon Schulz
130 f27f1860 Robert Haschke
                    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 f27f1860 Robert Haschke
        self.logger.info("created phonemelist with " + str(len(plist)) + " elements")
136 0c15613f Simon Schulz
137 f27f1860 Robert Haschke
        return plist
138 0c15613f Simon Schulz
139
    def create_utterance(self, text, audio_data, phoneme_list):
140 f27f1860 Robert Haschke
        u = utterance()
141
        u.text     = text
142
        u.audio    = self.create_soundchunk(audio_data)
143
        u.phonemes = self.create_phonemes(phoneme_list)
144 0c15613f Simon Schulz
145 f27f1860 Robert Haschke
        self.logger.info("created utterance for 'phonemelist with '" + u.text + "'")
146
        return u
147 0c15613f Simon Schulz
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 f27f1860 Robert Haschke
        self.logger.info("incoming utterance '" + goal.text + "'")
155 0c15613f Simon Schulz
156 f27f1860 Robert Haschke
        success = True
157
        result  = ttsResult()
158 0c15613f Simon Schulz
159 f27f1860 Robert Haschke
        # 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 0c15613f Simon Schulz
164 f27f1860 Robert Haschke
        except:
165
            self.logger.error("failed to create utterance error = '" + str(sys.exc_info()[1]) + "'")
166
            # try to open error message from file:
167 e21d7f2c Simon Schulz
            success = True
168
            (audio, phonelist) = self.get_error_message()
169 0c15613f Simon Schulz
170 f27f1860 Robert Haschke
        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 0c15613f Simon Schulz
177
    def run(self):
178 f27f1860 Robert Haschke
        # run the main loop
179
        rospy.spin()
180 0c15613f Simon Schulz
181 f27f1860 Robert Haschke
# test code
182 163a7434 Simon Schulz
def main():
183 0c15613f Simon Schulz
    if (len(sys.argv) != 2):
184 f27f1860 Robert Haschke
        print("> usage: "+sys.argv[0]+" <topic>\n\n")
185
        sys.exit(1)
186 0c15613f Simon Schulz
187
    bridge = MaryTTSBridge(topic=sys.argv[1], loglevel=logging.INFO)
188
    bridge.run()
189
190 163a7434 Simon Schulz
if __name__ == "__main__":
191
    main()