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