Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (5.358 KB)

1
#!/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
import os
38
import pkgutil
39
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
        self.logger.info("starting MaryTTSBridge on topic '"+topic+"'")
56

    
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
                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

    
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
    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
    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
            #try to open error message from file:
167
            success = True
168
            (audio, phonelist) = self.get_error_message()
169

    
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
def main():
183
    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
if __name__ == "__main__":
191
    main()