00001
00002
00003 import rospy
00004 import json
00005 import traceback
00006 import requests
00007
00008
00009 from rospeex_core import logging_util
00010 from rospeex_core.validators import accepts
00011 from rospeex_core.validators import check_language
00012 from rospeex_core import exceptions as ext
00013 from rospeex_core.ss.base import IClient
00014 from rospeex_core.ss import nict
00015
00016 logger = logging_util.get_logger(__name__)
00017
00018 class Client(IClient):
00019 """ SpeechSynthesisCient_VoiceText class """
00020 LANGUAGES = ['ja']
00021
00022
00023 VERSION = "v1"
00024 URL = "https://api.apigw.smt.docomo.ne.jp/voiceText/%s/textToSpeech?" % VERSION
00025 SPEAKER_LIST = ['show', 'haruka', 'hikari', 'takeru', 'santa', 'bear', '*']
00026 EMOTION_LIST = ['heppiness', 'anger', 'sadness']
00027 EMOTION_SPEAKER = ['haruka', 'hikari', 'takeru', 'santa', 'bear']
00028 EMOTION_RANGE = [1, 2]
00029 TEXT_RANGE = [0, 200]
00030 PITCH_RANGE = [50, 200]
00031 SPEED_RANGE = [50, 400]
00032 VOLUME_RANGE = [50, 200]
00033 DEFAULT_VOICE_FONT = 'show'
00034
00035 def __init__(self):
00036 """ init function
00037 @param args: parameter list for initialize
00038 @type args: list
00039 @param kwargs: parameter dictionary for initialize
00040 """
00041 self._key = ''
00042 self._pitch = 100
00043 self._speed = 100
00044 self._volume = 100
00045 self._emotion = None
00046 self._emotion_level = 1
00047 self._load_parameter()
00048
00049 def _load_parameter(self):
00050 """ load parameter from rospy """
00051 try:
00052 self._key = rospy.get_param('~docomo_api_key', '')
00053 self._pitch = rospy.get_param('~docomo_hoya_pitch', 100)
00054 self._speed = rospy.get_param('~docomo_hoya_speed', 100)
00055 self._volume = rospy.get_param('~docomo_hoya_volume', 100)
00056 self._emotion = rospy.get_param('~docomo_hoya_emotion', None)
00057 self._emotion_level = rospy.get_param(
00058 '~docomo_hoya_emotion_level',
00059 1
00060 )
00061
00062 except Exception:
00063 pass
00064
00065 @accepts(message=basestring, language=str, voice_font=str, timeout=int)
00066 def request(self, message, language='ja', voice_font='show', timeout=10):
00067 """
00068 Send speech synthesis request to server,
00069 and get speech synthesis result.
00070 @param message: message
00071 @type message: str
00072 @param language: speech synthesis language
00073 @type language: str
00074 @param voice_font: taraget voice font
00075 @type voice_font: str
00076 @param timeout: request timeout time (second)
00077 @type timeout: float
00078 @return: voice data (wav format binary)
00079 @rtype: str
00080 @raise SpeechSynthesisException:
00081 """
00082
00083 check_language(language, self.LANGUAGES)
00084
00085
00086 self._check_api_key()
00087
00088
00089 self._check_voice_font(voice_font)
00090 if voice_font == '*':
00091 voice_font = self.DEFAULT_VOICE_FONT
00092
00093
00094 if self._emotion != None:
00095 self._check_emotion(voice_font)
00096
00097
00098 self._check_text(message)
00099 self._check_pitch()
00100 self._check_speed()
00101 self._check_volume()
00102
00103
00104 try:
00105 client = nict.Client()
00106 client.request(message, language, '*', 10)
00107 except Exception:
00108 pass
00109
00110
00111 params = {
00112 'APIKEY': self._key,
00113 'Content-Type': 'application/x-www-form-urlencoded',
00114 'text': message,
00115 'speaker': voice_font,
00116 'pitch': self._pitch,
00117 'speed': self._speed,
00118 'volume': self._volume
00119 }
00120
00121 try:
00122 self.URL = self.URL
00123 response = requests.post(
00124 self.URL,
00125 params=params,
00126 timeout=timeout
00127 )
00128
00129 except requests.exceptions.Timeout as err:
00130 msg = 'request time out. Exception: {err}'.format(
00131 err=str(err)
00132 )
00133 raise ext.RequestTimeoutException(msg)
00134
00135 except requests.exceptions.ConnectionError as err:
00136 msg = 'network connection error. Exception: {err}'.format(
00137 err=str(err)
00138 )
00139 raise ext.InvalidRequestException(msg)
00140
00141 except requests.exceptions.RequestException as err:
00142 msg = 'invalid request error. Exception: {err}'.format(
00143 err=str(err)
00144 )
00145 raise ext.InvalidRequestException(msg)
00146
00147
00148 if response.status_code == 200:
00149 return response.content
00150
00151 else:
00152 content = json.loads(response.content)
00153 self._check_errors(response.status_code, content)
00154
00155 def _check_api_key(self):
00156 """ check api key
00157 @raises ParameterException
00158 """
00159 if self._key is None or self._key == '':
00160 msg = 'parameter failed. if you want to use docomo_hoya engine'\
00161 'you MUST set api key for docomo_hoya api.'
00162 raise ext.ParameterException(msg)
00163
00164 def _check_text(self, text):
00165 """ check tts text
00166 @param text: text
00167 @type str
00168 @raises ParameterException
00169 """
00170 if not len(text) in range(*self.TEXT_RANGE):
00171 msg = 'parameter failed. text length is not in range.'\
00172 'Except: {range[0]} <= text_length:{value}'\
00173 ' <= {range[1]}'.format(
00174 range=self.TEXT_RANGE,
00175 value=len(text)
00176 )
00177 raise ext.ParameterException(msg)
00178
00179 def _check_voice_font(self, voice_font):
00180 """ check voice font
00181 @param voice_font: voice font
00182 @type voice_font: str
00183 @raises ParameterException
00184 """
00185 if voice_font not in self.SPEAKER_LIST:
00186 msg = 'parameter failed [{voice_font}].'\
00187 'you choose voice fonts following parametrs '\
00188 '{speaker_list}'.format(
00189 voice_font=voice_font,
00190 speaker_list=str(self.SPEAKER_LIST)
00191 )
00192 raise ext.ParameterException(msg)
00193
00194 def _check_emotion(self, voice_font):
00195 """ check emotion and emotion_level
00196 @param voice_font: voice font
00197 @type voice_font: str
00198 @raises ParameterException
00199 """
00200 if self._emotion is None:
00201 return
00202
00203 if voice_font not in self.EMOTION_SPEAKER:
00204 msg = 'parameter failed. if you want to use emotion option,'\
00205 'you choose following speakers %s' % (self.EMOTION_SPEAKER)
00206 raise ext.ParameterException(msg)
00207
00208 if self._emotion not in self.EMOTION_LIST:
00209 msg = 'parameter failed. if you want to use emotion option,'\
00210 'you choose following emotions %s' % (self.EMOTION_LIST)
00211 raise ext.ParameterException(msg)
00212
00213 if self._emotion_level not in range(*self.EMOTION_RANGE):
00214 msg = 'parameter failed. emotion level is not in range.'\
00215 'Except: {range[0]} <= emotion_level:{value}' \
00216 ' <= {range[1]}'.format(
00217 range=self.EMOTION_RANGE,
00218 value=self._emotion_level
00219 )
00220 raise ext.ParameterException(msg)
00221
00222 def _check_volume(self):
00223 """ check volume level
00224 @raises ParameterException
00225 """
00226 if self._volume not in range(*self.VOLUME_RANGE):
00227 msg = 'parameter failed. volume level is not in range.'\
00228 'Except: {range[0]} <= volume:{value} <= {range[1]}'.format(
00229 range=self.VOLUME_RANGE,
00230 value=self._volume,
00231 )
00232 raise ext.ParameterException(msg)
00233
00234 def _check_speed(self):
00235 """ check speed level
00236 @raises ParameterException
00237 """
00238 if self._speed not in range(*self.SPEED_RANGE):
00239 msg = 'parameter failed. speed level is not in range.'\
00240 'Except: {range[0]} <= speed:{value} <= {range[1]}'.format(
00241 range=self.SPEED_RANGE,
00242 value=self._speed,
00243 )
00244 raise ext.ParameterException(msg)
00245
00246 def _check_pitch(self):
00247 """ check speed level
00248 @raises ParameterException
00249 """
00250 if self._pitch not in range(*self.PITCH_RANGE):
00251 msg = 'parameter failed. pitch level is not in range.'\
00252 'Except: {range[0]} <= pitch:{value} <= {range[1]}'.format(
00253 range=self.PITCH_RANGE,
00254 value=self._pitch,
00255 )
00256 raise ext.ParameterException(msg)
00257
00258 def _check_errors(self, status_code, content):
00259 """ check server errors
00260 @param status_code: status code
00261 @type status_code: int
00262 @param content: response message
00263 @type content: str
00264 @raises InvalidRequestException
00265 @raises InvalidResponseException
00266 """
00267 try:
00268 message = content['error']['message']
00269 except KeyError as err:
00270 msg = 'invalid response. Message: {message}'.format(
00271 message=str(err)
00272 )
00273 raise ext.InvalidResponseException(msg)
00274
00275 if status_code == 400:
00276 msg = 'invalid request. Message: {message}'.format(
00277 message=message
00278 )
00279 raise ext.InvalidRequestException(msg)
00280
00281 elif status_code == 401:
00282 msg = 'auth failed. Message: {message}'.format(
00283 message=message
00284 )
00285 raise ext.InvalidRequestException(msg)
00286
00287 elif status_code == 404:
00288 msg = 'target url is not avariable. Message: {message}'.format(
00289 message=message
00290 )
00291 raise ext.InvalidRequestException(msg)
00292
00293 elif status_code == 405:
00294 msg = 'invalid request. Message: {message}'.format(
00295 message=message
00296 )
00297 raise ext.InvalidRequestException(msg)
00298
00299 elif status_code == 500:
00300 msg = 'server internal error. Message: {message}'.format(
00301 message=message
00302 )
00303 raise ext.InvalidResponseException(msg)
00304
00305 elif status_code == 503:
00306 msg = 'service unavailable error. Message: {message}'.format(
00307 message=message
00308 )
00309 raise ext.InvalidResponseException(msg)
00310
00311 else:
00312 msg = 'undefined error. Message: {message} '\
00313 'Traceback:{trace}'.format(
00314 message=message,
00315 trace=traceback.format_exc()
00316 )
00317 raise ext.InvalidResponseException(msg)