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