00001
00002
00003 import rospy
00004 import traceback
00005 import requests
00006
00007 import wave
00008 import struct
00009 import StringIO
00010
00011
00012 from rospeex_core import logging_util
00013 from rospeex_core.validators import accepts
00014 from rospeex_core.validators import check_language
00015 from rospeex_core import exceptions as ext
00016 from rospeex_core.ss.base import IClient
00017 from rospeex_core.ss import nict
00018
00019
00020 logger = logging_util.get_logger(__name__)
00021
00022
00023 class Client(IClient):
00024 """ SpeechSynthesisCient_docomo_aitalk class """
00025 LANGUAGES = ['ja']
00026
00027
00028 VERSION = "v1"
00029 URL = "https://api.apigw.smt.docomo.ne.jp/aiTalk/%s/textToSpeech" % VERSION
00030
00031 SPEAKER_LIST = ['nozomi','seiji','akari','anzu','hiroshi','kaho','koutarou','maki','nanako','osamu','sumire', '*']
00032 TEXT_RANGE = [0, 200]
00033
00034 DEFAULT_VOICE_FONT = 'nozomi'
00035
00036 def __init__(self):
00037 """ init function
00038 @param args: parameter list for initialize
00039 @type args: list
00040 @param kwargs: parameter dictionary for initialize
00041 """
00042 self._key = ''
00043 self._speakerid = 0
00044 self._speechrate = 1.0
00045 self._powerrate = 1.0
00046 self._voicetype = 0
00047 self._audiofileformat = 0
00048 self._load_parameter()
00049
00050 def _load_parameter(self):
00051 """ load parameter from rospy """
00052 try:
00053 self._key = rospy.get_param('~docomo_api_key', '')
00054 except Exception:
00055 pass
00056
00057 @accepts(message=basestring, language=str, voice_font=str, timeout=int)
00058 def request(self, message, language='ja', voice_font='nozomi', timeout=10):
00059 """
00060 Send speech synthesis request to server,
00061 and get speech synthesis result.
00062 @param message: message
00063 @type message: str
00064 @param language: speech synthesis language
00065 @type language: str
00066 @param voice_font: taraget voice font
00067 @type voice_font: str
00068 @param timeout: request timeout time (second)
00069 @type timeout: float
00070 @return: voice data (wav format binary)
00071 @rtype: str
00072 @raise SpeechSynthesisException:
00073 """
00074
00075 check_language(language, self.LANGUAGES)
00076
00077
00078 self._check_api_key()
00079
00080
00081 self._check_voice_font(voice_font)
00082 if voice_font == '*':
00083 voice_font = self.DEFAULT_VOICE_FONT
00084
00085
00086 self._check_text(message)
00087
00088
00089 try:
00090 client = nict.Client()
00091 client.request(message, language, '*', 10)
00092 except Exception:
00093 pass
00094
00095
00096 template = u"<?xml version='1.0' encoding='utf-8' ?>"\
00097 "<speak version='1.1'><voice name='{font}'>{message}</voice></speak>"
00098
00099 self._ssml_text = template.format(
00100 font=voice_font,
00101 message=message.decode('utf-8')
00102 )
00103
00104
00105 headers = {
00106 'Content-Type': 'application/ssml+xml',
00107 'Accept': 'audio/L16'
00108 }
00109
00110 params = {
00111 'APIKEY': self._key,
00112 }
00113
00114 try:
00115 response = requests.post(
00116 self.URL,
00117 headers=headers,
00118 data=self._ssml_text.encode('utf-8'),
00119 params=params,
00120 timeout=timeout,
00121 )
00122
00123 except requests.exceptions.Timeout as err:
00124 msg = 'request time out. Exception: {err}'.format(
00125 err=str(err)
00126 )
00127 raise ext.RequestTimeoutException(msg)
00128
00129 except requests.exceptions.ConnectionError as err:
00130 msg = 'network connection error. Exception: {err}'.format(
00131 err=str(err)
00132 )
00133 raise ext.InvalidRequestException(msg)
00134
00135 except requests.exceptions.RequestException as err:
00136 msg = 'invalid request error. Exception: {err}'.format(
00137 err=str(err)
00138 )
00139 raise ext.InvalidRequestException(msg)
00140
00141
00142 if response.status_code == 200:
00143 wav_buffer = self._create_wav_buffer(response.content)
00144 return wav_buffer
00145
00146 else:
00147 self._check_errors(response.status_code)
00148
00149 def _check_api_key(self):
00150 """ check api key
00151 @raises ParameterException
00152 """
00153 if self._key is None or self._key == '':
00154 msg = 'parameter failed. if you want to use docomo_aitalk engine'\
00155 'you MUST set api key for docomo_aitalk api.'
00156 raise ext.ParameterException(msg)
00157
00158 def _check_text(self, text):
00159 """ check tts text
00160 @param text: text
00161 @type str
00162 @raises ParameterException
00163 """
00164 if not len(text) in range(*self.TEXT_RANGE):
00165 msg = 'parameter failed. text length is not in range.'\
00166 'Except: {range[0]} <= text_length:{value}'\
00167 ' <= {range[1]}'.format(
00168 range=self.TEXT_RANGE,
00169 value=len(text)
00170 )
00171 raise ext.ParameterException(msg)
00172
00173 def _check_voice_font(self, voice_font):
00174 """ check voice font
00175 @param voice_font: voice font
00176 @type voice_font: str
00177 @raises ParameterException
00178 """
00179 if voice_font not in self.SPEAKER_LIST:
00180 msg = 'parameter failed [{voice_font}].'\
00181 'you choose voice fonts following parametrs '\
00182 '{speaker_list}'.format(
00183 voice_font=voice_font,
00184 speaker_list=str(self.SPEAKER_LIST)
00185 )
00186 raise ext.ParameterException(msg)
00187
00188 def _check_errors(self, status_code):
00189 """ check server errors
00190 @param status_code: status code
00191 @type status_code: int
00192 @raises InvalidRequestException
00193 @raises InvalidResponseException
00194 """
00195
00196 if status_code == 400:
00197 msg = 'invalid request. StatusCode: {status_code}'.format(
00198 status_code=status_code
00199 )
00200 raise ext.InvalidRequestException(msg)
00201
00202 elif status_code == 401:
00203 msg = 'auth failed. StatusCode: {status_code}'.format(
00204 status_code=status_code
00205 )
00206 raise ext.InvalidRequestException(msg)
00207
00208 elif status_code == 404:
00209 msg = 'target url is not avariable. StatusCode: {status_code}'.format(
00210 status_code=status_code
00211 )
00212 raise ext.InvalidRequestException(msg)
00213
00214 elif status_code == 405:
00215 msg = 'invalid request. StatusCode: {status_code}'.format(
00216 status_code=status_code
00217 )
00218 raise ext.InvalidRequestException(msg)
00219
00220 elif status_code == 500:
00221 msg = 'server internal error. StatusCode: {status_code}'.format(
00222 status_code=status_code
00223 )
00224 raise ext.InvalidResponseException(msg)
00225
00226 elif status_code == 503:
00227 msg = 'service unavailable error. StatusCode: {status_code}'.format(
00228 status_code=status_code
00229 )
00230 raise ext.InvalidResponseException(msg)
00231
00232 else:
00233 msg = 'undefined error. StatusCode: {status_code} '\
00234 'Traceback:{trace}'.format(
00235 status_code=status_code,
00236 trace=traceback.format_exc()
00237 )
00238 raise ext.InvalidResponseException(msg)
00239
00240 def _create_wav_buffer(self, content):
00241 """ create wave buffer
00242 @param content: binary data
00243 @type content: str
00244 @raises WaveConvertException
00245 """
00246
00247 if content is None or content == '':
00248 msg = 'wave convert failed. input binary data is null.'
00249 raise ext.WaveConvertException(msg)
00250
00251 try:
00252 buffer = StringIO.StringIO()
00253
00254 w = wave.open(buffer, 'w')
00255
00256 buf_len = len(content) / 2
00257 fmt = '<%dH' % buf_len
00258 unpacked_buf = struct.unpack(fmt, content)
00259
00260 fmt = '>%dH' % buf_len
00261 packed_buf = struct.pack(fmt, *unpacked_buf)
00262
00263 param = (1, 2, 16000, len(content), 'NONE', 'not compressed')
00264 w.setparams(param)
00265 w.writeframes(packed_buf)
00266 buffer.flush()
00267
00268 if buffer is None or buffer == '':
00269 msg = 'wave convert failed. converted binary data is null.'
00270 raise ext.WaveConvertException(msg)
00271
00272 wav_buffer = buffer.getvalue()
00273
00274 w.close()
00275 buffer.close()
00276
00277 except Exception as err:
00278 msg = 'wave convert failed.{err}'.format(
00279 err=str(err)
00280 )
00281 raise ext.WaveConvertException(msg)
00282
00283 return wav_buffer