Revision f27f1860

View differences:

tts_bridge/mary/mary_tts_bridge/MaryTTSBridge.py
42 42
except ImportError:
43 43
    from cStringIO import StringIO
44 44

  
45
class MaryTTSBridge(object):
46
    #_feedback = ttsActionFeedback()
47
    #_result   = ttsActionResult()
48 45

  
46
class MaryTTSBridge(object):
47
    # _feedback = ttsActionFeedback()
48
    # _result   = ttsActionResult()
49 49

  
50 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)
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+"'")
61 59

  
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()
60
        self.tts_client = MaryTTSClient(voice, locale, tts_host, tts_port, loglevel)
67 61

  
62
        rospy.init_node('MaryTTSBridge', anonymous=True)
68 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()
69 67

  
70 68
    def __del__(self):
71
	"""destructor
72
	"""
73
	self.logger.debug("destructor of MaryTTSBridge called")
69
        """destructor
70
        """
71
        self.logger.debug("destructor of MaryTTSBridge called")
74 72

  
75 73
    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)
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)
85 83

  
86 84
    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
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
115 112

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

  
120
	sio = StringIO(phoneme_bytes.decode('ascii'))
121
	for line in sio:
122
	    if (line[0] != '#'):
123
		phoneme_list = line.split(" ")
117
        sio = StringIO(phoneme_bytes.decode('ascii'))
118
        for line in sio:
119
            if (line[0] != '#'):
120
                phoneme_list = line.split(" ")
124 121
                if (line == '\n'):
125
                    #ignore empty lines
122
                    # ignore empty lines
126 123
                    continue
127 124
                elif (len(phoneme_list) != 3):
128 125
                    print("> could not split line '%s' during phoneme seperation\n" % (line))
129 126
                else:
130
		    symbol = phoneme_list[2]
131
    		    symbol = symbol.rstrip()
127
                    symbol = phoneme_list[2]
128
                    symbol = symbol.rstrip()
132 129

  
133
   		    now = float(phoneme_list[0])
134
		    duration = (now - last)*1000
135
		    last = now
136
		    plist.append(phoneme(symbol, int(duration)))
130
                    now = float(phoneme_list[0])
131
                    duration = (now - last)*1000
132
                    last = now
133
                    plist.append(phoneme(symbol, int(duration)))
137 134

  
138
	self.logger.info("created phonemelist with " + str(len(plist)) + " elements")
135
        self.logger.info("created phonemelist with " + str(len(plist)) + " elements")
139 136

  
140
	return plist
137
        return plist
141 138

  
142 139
    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)
140
        u = utterance()
141
        u.text     = text
142
        u.audio    = self.create_soundchunk(audio_data)
143
        u.phonemes = self.create_phonemes(phoneme_list)
147 144

  
148
	self.logger.info("created utterance for 'phonemelist with '" + u.text + "'")
149
	return u
145
        self.logger.info("created utterance for 'phonemelist with '" + u.text + "'")
146
        return u
150 147

  
151 148
    def get_error_message(self):
152 149
        data_wav = pkgutil.get_data('mary_tts_bridge', 'data/connection_failed.wav')
......
154 151
        return (data_wav, data_phonemes)
155 152

  
156 153
    def execute_cb(self, goal):
157
	self.logger.info("incoming utterance '" + goal.text + "'")
154
        self.logger.info("incoming utterance '" + goal.text + "'")
158 155

  
159
	success = True
160
	result   = ttsResult()
156
        success = True
157
        result  = ttsResult()
161 158

  
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)
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)
166 163

  
167
	except:
168
	    self.logger.error("failed to create utterance error = '" + str(sys.exc_info()[1]) + "'")
169
            #try to open error message from file:
164
        except:
165
            self.logger.error("failed to create utterance error = '" + str(sys.exc_info()[1]) + "'")
166
            # try to open error message from file:
170 167
            success = True
171 168
            (audio, phonelist) = self.get_error_message()
172 169

  
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)
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)
179 176

  
180 177
    def run(self):
181
	#run the main loop
182
	rospy.spin()
178
        # run the main loop
179
        rospy.spin()
183 180

  
184
#test code
181
# test code
185 182
def main():
186 183
    if (len(sys.argv) != 2):
187
	print("> usage: "+sys.argv[0]+" <topic>\n\n")
188
	sys.exit(1)
184
        print("> usage: "+sys.argv[0]+" <topic>\n\n")
185
        sys.exit(1)
189 186

  
190 187
    bridge = MaryTTSBridge(topic=sys.argv[1], loglevel=logging.INFO)
191 188
    bridge.run()
tts_bridge/mary/mary_tts_bridge/MaryTTSClient.py
26 26
"""
27 27

  
28 28
import logging
29
#try:
30
#	import rsb
31
#except ImportError:
32
#	RSB_SUPPORT = False
33
#else:
34
#	from MiddlewareRSB import *
35
#	RSB_SUPPORT = True
36

  
37
#from MiddlewareROS import *
38 29
import sys
39 30
try:
40
	from http.client import HTTPConnection
41
	from urllib.parse import urlencode
31
    from http.client import HTTPConnection
32
    from urllib.parse import urlencode
42 33
except ImportError:  # Python 2
43
	from httplib import HTTPConnection
44
	from urllib import urlencode
34
    from httplib import HTTPConnection
35
    from urllib import urlencode
45 36
import wave
46 37
import ctypes
47 38
import wave
48 39
import sys
49 40

  
41

  
50 42
class MaryTTSClient:
51 43
    def __init__(self, voice="cmu-slt-hsmm", locale="en_US", tts_host="127.0.0.1", tts_port=59125, loglevel=logging.WARNING):
52
	"""initialise
53
	:param  loglevel: optional log level
54
	"""
55
	self.loglevel = loglevel
56
	self.logger = logging.getLogger(__name__)
57
	# create nice and actually usable formatter and add it to the handler
58
	self.config_logger(loglevel)
44
        """initialise
45
        :param  loglevel: optional log level
46
        """
47
        self.loglevel = loglevel
48
        self.logger = logging.getLogger(__name__)
49
        # create nice and actually usable formatter and add it to the handler
50
        self.config_logger(loglevel)
59 51

  
60
	self.logger.info("starting MaryTTSClient (voice="+voice+", locale="+locale+", host="+tts_host+", port="+str(tts_port))
52
        self.logger.info("starting MaryTTSClient (voice="+voice+", locale="+locale+", host="+tts_host+", port="+str(tts_port))
61 53

  
62
	self.tts_host = tts_host
63
	self.tts_port = tts_port
64
	self.locale   = locale
65
	self.voice    = voice
54
        self.tts_host = tts_host
55
        self.tts_port = tts_port
56
        self.locale   = locale
57
        self.voice    = voice
66 58

  
67 59
    def __del__(self):
68
	"""destructor
69
	"""
70
	self.logger.debug("destructor of MaryTTSClient called")
60
        """destructor
61
        """
62
        self.logger.debug("destructor of MaryTTSClient called")
71 63

  
72 64
    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)
65
        """initialise a nice logger formatting
66
        :param  level: log level
67
        """
68
        formatter = logging.Formatter('%(asctime)s %(name)-30s %(levelname)-8s > %(message)s')
69
        ch = logging.StreamHandler()
70
        # ch.setLevel(level)
71
        ch.setFormatter(formatter)
72
        self.logger.setLevel(level)
73
        self.logger.addHandler(ch)
82 74

  
83 75
    def generate_audio(self, message):
84
	"""generate audio from text
85
	:param message: text to synthesize
86
	"""
87
	return self.generate(message, "AUDIO")
76
        """generate audio from text
77
        :param message: text to synthesize
78
        """
79
        return self.generate(message, "AUDIO")
88 80

  
89 81
    def generate_phonemes(self, message):
90
	"""generate phoneme list from text
91
	:param message: text to synthesize
92
	"""
93
	return self.generate(message, "REALISED_DURATIONS")
82
        """generate phoneme list from text
83
        :param message: text to synthesize
84
        """
85
        return self.generate(message, "REALISED_DURATIONS")
94 86

  
95 87
    def generate(self, message, output_type):
96
	"""generate requested data object from text
97
	:param message: text to synthesize
98
	"""
99

  
100
	raw_params = {
101
	    "INPUT_TEXT": message,
102
	    "INPUT_TYPE": "RAWMARYXML",
103
	    "OUTPUT_TYPE": output_type,
104
	    "LOCALE": self.locale,
105
	    "AUDIO": "WAVE_FILE",
106
	    "VOICE": self.voice,
107
	}
108

  
109
	params = urlencode(raw_params)
110
	headers = {}
111

  
112
	#conn.set_debuglevel(5)
113
	#open connection to mary server
88
        """generate requested data object from text
89
        :param message: text to synthesize
90
        """
91

  
92
        raw_params = {
93
            "INPUT_TEXT": message,
94
            "INPUT_TYPE": "RAWMARYXML",
95
            "OUTPUT_TYPE": output_type,
96
            "LOCALE": self.locale,
97
            "AUDIO": "WAVE_FILE",
98
            "VOICE": self.voice,
99
        }
100

  
101
        params = urlencode(raw_params)
102
        headers = {}
103

  
104
        # conn.set_debuglevel(5)
105
        # open connection to mary server
114 106
        conn = HTTPConnection(self.tts_host, self.tts_port)
115 107

  
116
	conn.request("POST", "/process", params, headers)
117
	response = conn.getresponse()
108
        conn.request("POST", "/process", params, headers)
109
        response = conn.getresponse()
118 110

  
119
	if response.status != 200:
120
	    print(response.getheaders())
111
        if response.status != 200:
112
            print(response.getheaders())
121 113
            conn.close()
122
	    raise RuntimeError("{0}: {1}".format(response.status,response.reason))
123
	return response.read()
114
            raise RuntimeError("{0}: {1}".format(response.status,response.reason))
115
        return response.read()
116

  
124 117

  
125
#test code
118
# test code
126 119
if __name__ == "__main__":
127 120
    client = MaryTTSClient()
128 121
    audio = client.generate_phonemes("test 1 2 3 4 5 6 7 8 9 10")

Also available in: Unified diff