Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (6.089 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
try:
41
    from io import StringIO
42
except ImportError:
43
    from cStringIO import StringIO
44

    
45

    
46
class MaryTTSBridge(object):
47
    # _feedback = ttsActionFeedback()
48
    # _result   = ttsActionResult()
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
        self.logger.info("starting MaryTTSBridge on topic '"+topic+"'")
59

    
60
        self.tts_client = MaryTTSClient(voice, locale, tts_host, tts_port, loglevel)
61

    
62
        rospy.init_node('MaryTTSBridge', anonymous=True)
63

    
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
    def __del__(self):
69
        """destructor
70
        """
71
        self.logger.debug("destructor of MaryTTSBridge called")
72

    
73
    def config_logger(self, level):
74
        """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

    
84
    def create_soundchunk(self, audio_data):
85
        #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

    
113
    def create_phonemes(self, phoneme_bytes):
114
        last = 0.0
115
        plist = []
116

    
117
        sio = StringIO(phoneme_bytes.decode('ascii'))
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()