cloudlanguagetools 17.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cloudlanguagetools/__init__.py +0 -0
- cloudlanguagetools/alibaba.py +270 -0
- cloudlanguagetools/amazon.py +286 -0
- cloudlanguagetools/audio_processing.py +29 -0
- cloudlanguagetools/azure.py +804 -0
- cloudlanguagetools/cereproc.py +111 -0
- cloudlanguagetools/chatapi.py +360 -0
- cloudlanguagetools/constants.py +99 -0
- cloudlanguagetools/deepl.py +109 -0
- cloudlanguagetools/dictionarylookup.py +48 -0
- cloudlanguagetools/easypronunciation.py +254 -0
- cloudlanguagetools/elevenlabs.py +290 -0
- cloudlanguagetools/encryption.py +38 -0
- cloudlanguagetools/epitran.py +117 -0
- cloudlanguagetools/errors.py +37 -0
- cloudlanguagetools/forvo.py +684 -0
- cloudlanguagetools/fptai.py +172 -0
- cloudlanguagetools/gemini.py +288 -0
- cloudlanguagetools/google.py +312 -0
- cloudlanguagetools/keys.py +1 -0
- cloudlanguagetools/languages.py +519 -0
- cloudlanguagetools/libretranslate.py +84 -0
- cloudlanguagetools/mandarincantonese.py +87 -0
- cloudlanguagetools/naver.py +286 -0
- cloudlanguagetools/openai.py +292 -0
- cloudlanguagetools/options.py +15 -0
- cloudlanguagetools/pythainlp.py +102 -0
- cloudlanguagetools/service.py +110 -0
- cloudlanguagetools/servicemanager.py +395 -0
- cloudlanguagetools/spacy.py +76 -0
- cloudlanguagetools/test_services.py +167 -0
- cloudlanguagetools/tokenization.py +19 -0
- cloudlanguagetools/translationlanguage.py +19 -0
- cloudlanguagetools/transliterationlanguage.py +42 -0
- cloudlanguagetools/ttsvoice.py +53 -0
- cloudlanguagetools/vocalware.py +288 -0
- cloudlanguagetools/voicen.py +137 -0
- cloudlanguagetools/watson.py +181 -0
- cloudlanguagetools/wenlin.py +167 -0
- cloudlanguagetools-17.0.0.dist-info/METADATA +41 -0
- cloudlanguagetools-17.0.0.dist-info/RECORD +43 -0
- cloudlanguagetools-17.0.0.dist-info/WHEEL +4 -0
- cloudlanguagetools-17.0.0.dist-info/licenses/LICENSE +674 -0
|
File without changes
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
import time
|
|
4
|
+
import datetime
|
|
5
|
+
import uuid
|
|
6
|
+
import urllib
|
|
7
|
+
import hmac
|
|
8
|
+
import base64
|
|
9
|
+
import logging
|
|
10
|
+
import tempfile
|
|
11
|
+
import pprint
|
|
12
|
+
from typing import List
|
|
13
|
+
|
|
14
|
+
import cloudlanguagetools.service
|
|
15
|
+
import cloudlanguagetools.options
|
|
16
|
+
import cloudlanguagetools.constants
|
|
17
|
+
import cloudlanguagetools.languages
|
|
18
|
+
import cloudlanguagetools.ttsvoice
|
|
19
|
+
import cloudlanguagetools.constants
|
|
20
|
+
import cloudlanguagetools.languages
|
|
21
|
+
import cloudlanguagetools.ttsvoice
|
|
22
|
+
import cloudlanguagetools.errors
|
|
23
|
+
from cloudlanguagetools.options import AudioFormat
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
ALIBABA_VOICE_SPEED_DEFAULT = 0
|
|
28
|
+
ALIBABA_VOICE_PITCH_DEFAULT = 0
|
|
29
|
+
|
|
30
|
+
VOICE_OPTIONS = {
|
|
31
|
+
'speed': {'type': 'number_int', 'min': -500, 'max': 500, 'default': ALIBABA_VOICE_SPEED_DEFAULT},
|
|
32
|
+
'pitch': {'type': 'number_int', 'min': -500, 'max': 500, 'default': ALIBABA_VOICE_PITCH_DEFAULT},
|
|
33
|
+
'volume': {'type': 'number_int', 'min': 0, 'max': 100, 'default': 50},
|
|
34
|
+
cloudlanguagetools.options.AUDIO_FORMAT_PARAMETER: {
|
|
35
|
+
'type': cloudlanguagetools.options.ParameterType.list.name,
|
|
36
|
+
'values': ['pcm', 'wav', 'mp3'],
|
|
37
|
+
'default': 'mp3'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AlibabaService(cloudlanguagetools.service.Service):
|
|
43
|
+
def __init__(self):
|
|
44
|
+
self.access_token = None
|
|
45
|
+
|
|
46
|
+
def configure(self, config):
|
|
47
|
+
self.access_key_id = config['access_key_id']
|
|
48
|
+
self.access_key_secret = config['access_key_secret']
|
|
49
|
+
self.app_key = config['app_key']
|
|
50
|
+
|
|
51
|
+
def refresh_token(self):
|
|
52
|
+
logger.info("refreshing token")
|
|
53
|
+
params = {
|
|
54
|
+
"AccessKeyId": self.access_key_id,
|
|
55
|
+
"Action": "CreateToken",
|
|
56
|
+
"Version": "2019-07-17",
|
|
57
|
+
"Format": "JSON",
|
|
58
|
+
"RegionId": "ap-southeast-1",
|
|
59
|
+
"Timestamp": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
60
|
+
"SignatureMethod": "HMAC-SHA1",
|
|
61
|
+
"SignatureVersion": "1.0",
|
|
62
|
+
"SignatureNonce": str(uuid.uuid4())
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# sort by keys alphabetically
|
|
66
|
+
params = dict(sorted(params.items()))
|
|
67
|
+
# timestamp needs to be double-quoted by the end
|
|
68
|
+
params["Timestamp"] = urllib.parse.quote(params["Timestamp"], safe='')
|
|
69
|
+
|
|
70
|
+
# urlencode with noop lambda for no quoting - we will quote later
|
|
71
|
+
params_str = urllib.parse.urlencode(params, quote_via=lambda a, b, c, d: a)
|
|
72
|
+
|
|
73
|
+
# this is always /, as we always hit the path / on the API
|
|
74
|
+
url_encoded = urllib.parse.quote("/", safe='')
|
|
75
|
+
str_to_sign = f"GET&{url_encoded}&{urllib.parse.quote(params_str, safe='')}"
|
|
76
|
+
str_to_sign = str_to_sign.encode("utf-8")
|
|
77
|
+
|
|
78
|
+
key = self.access_key_secret + "&"
|
|
79
|
+
key = key.encode("utf-8")
|
|
80
|
+
|
|
81
|
+
# calculate HMAC-SHA1 digest and convert to base64
|
|
82
|
+
dig = hmac.new(key, str_to_sign, "sha1").digest()
|
|
83
|
+
dig = base64.standard_b64encode(dig).decode("utf-8")
|
|
84
|
+
|
|
85
|
+
# signature also needs to be quoted
|
|
86
|
+
signature = urllib.parse.quote(dig, safe='')
|
|
87
|
+
params_str = f"Signature={signature}&{params_str}"
|
|
88
|
+
|
|
89
|
+
response = requests.get(f"http://nlsmeta.ap-southeast-1.aliyuncs.com/?{params_str}")
|
|
90
|
+
|
|
91
|
+
if response.status_code != 200:
|
|
92
|
+
logger.warning(f"Token request failed: {response.text}")
|
|
93
|
+
raise cloudlanguagetools.errors.RequestError("Token request failed", None, response.text)
|
|
94
|
+
|
|
95
|
+
data = response.json()
|
|
96
|
+
self.access_token = data["Token"]
|
|
97
|
+
logger.info(f"Got access token: {self.access_token}")
|
|
98
|
+
|
|
99
|
+
def get_tts_audio(self, text, voice_key, options):
|
|
100
|
+
logger.debug(f'get_tts_audio, text: {text} voice_key: {voice_key}')
|
|
101
|
+
if not self.access_token or self.access_token["ExpireTime"] <= int(time.time()):
|
|
102
|
+
self.refresh_token()
|
|
103
|
+
|
|
104
|
+
speed = int(options.get('speed', ALIBABA_VOICE_SPEED_DEFAULT))
|
|
105
|
+
pitch = int(options.get('pitch', ALIBABA_VOICE_PITCH_DEFAULT))
|
|
106
|
+
voice = voice_key['name']
|
|
107
|
+
|
|
108
|
+
params = {
|
|
109
|
+
"format": "mp3",
|
|
110
|
+
"appkey": self.app_key,
|
|
111
|
+
"speech_rate": speed,
|
|
112
|
+
"pitch_rate": pitch,
|
|
113
|
+
"text": text,
|
|
114
|
+
"token": self.access_token["Id"],
|
|
115
|
+
"voice": voice
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
response = requests.get(
|
|
119
|
+
"https://nls-gateway-ap-southeast-1.aliyuncs.com/stream/v1/tts",
|
|
120
|
+
params=params,
|
|
121
|
+
timeout=cloudlanguagetools.constants.RequestTimeout
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
logger.debug(f'response status code: {response.status_code} headers: {pprint.pformat(response.headers)} length of response: {len(response.content)}')
|
|
125
|
+
|
|
126
|
+
if response.status_code != 200:
|
|
127
|
+
data = response.json()
|
|
128
|
+
error_message = f'could not retrieve audio using Alibaba: {response.content}'
|
|
129
|
+
logger.error(error_message)
|
|
130
|
+
raise cloudlanguagetools.errors.RequestError(text, voice, error_message)
|
|
131
|
+
|
|
132
|
+
if response.headers['Content-Type'] != 'audio/mpeg':
|
|
133
|
+
logger.error(f'Unexpected response type. Response as text: {response.text}')
|
|
134
|
+
raise cloudlanguagetools.errors.RequestError(
|
|
135
|
+
text, voice,
|
|
136
|
+
f'Got bad content type in response: {response.headers["Content-Type"]}'
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Create a temporary file and write the audio content to it
|
|
140
|
+
output_temp_file = tempfile.NamedTemporaryFile(prefix=f'cloudlanguage_tools_{self.__class__.__name__}_audio', suffix='.mp3')
|
|
141
|
+
output_temp_filename = output_temp_file.name
|
|
142
|
+
with open(output_temp_filename, 'wb') as audio:
|
|
143
|
+
audio.write(response.content)
|
|
144
|
+
logger.debug(f'result temporary file name: {output_temp_file.name}')
|
|
145
|
+
return output_temp_file
|
|
146
|
+
|
|
147
|
+
def get_tts_voice_list(self):
|
|
148
|
+
# returns list of TtsVoice
|
|
149
|
+
return []
|
|
150
|
+
|
|
151
|
+
def build_tts_voice_v3(self, name: str, voice_id: str, gender: cloudlanguagetools.constants.Gender, audio_languages: List[cloudlanguagetools.languages.AudioLanguage]):
|
|
152
|
+
return cloudlanguagetools.ttsvoice.TtsVoice_v3(
|
|
153
|
+
name=name,
|
|
154
|
+
voice_key={'name': voice_id},
|
|
155
|
+
options=VOICE_OPTIONS,
|
|
156
|
+
service=cloudlanguagetools.constants.Service.Alibaba,
|
|
157
|
+
gender=gender,
|
|
158
|
+
audio_languages=audio_languages,
|
|
159
|
+
service_fee=cloudlanguagetools.constants.ServiceFee.paid
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def get_tts_voice_list_v3(self) -> List[cloudlanguagetools.ttsvoice.TtsVoice_v3]:
|
|
163
|
+
result = []
|
|
164
|
+
|
|
165
|
+
# Standard voices for all scenarios
|
|
166
|
+
result.extend([
|
|
167
|
+
# Standard Chinese voices
|
|
168
|
+
self.build_tts_voice_v3('Xiaoyun (Standard)', 'Xiaoyun',
|
|
169
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
170
|
+
self.build_tts_voice_v3('Xiaogang (Standard)', 'Xiaogang',
|
|
171
|
+
cloudlanguagetools.constants.Gender.Male, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
172
|
+
self.build_tts_voice_v3('Ruoxi (Gentle)', 'Ruoxi',
|
|
173
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
174
|
+
self.build_tts_voice_v3('Siqi (Gentle)', 'Siqi',
|
|
175
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
176
|
+
self.build_tts_voice_v3('Sijia (Standard)', 'Sijia',
|
|
177
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
178
|
+
self.build_tts_voice_v3('Sicheng (Standard)', 'Sicheng',
|
|
179
|
+
cloudlanguagetools.constants.Gender.Male, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
180
|
+
self.build_tts_voice_v3('Aiqi (Gentle)', 'Aiqi',
|
|
181
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
182
|
+
self.build_tts_voice_v3('Aijia (Standard)', 'Aijia',
|
|
183
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
184
|
+
self.build_tts_voice_v3('Aicheng (Standard)', 'Aicheng',
|
|
185
|
+
cloudlanguagetools.constants.Gender.Male, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
186
|
+
self.build_tts_voice_v3('Aida (Standard)', 'Aida',
|
|
187
|
+
cloudlanguagetools.constants.Gender.Male, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
188
|
+
self.build_tts_voice_v3("Ning'er", 'Ninger',
|
|
189
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN]),
|
|
190
|
+
self.build_tts_voice_v3('Ruilin', 'Ruilin',
|
|
191
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN]),
|
|
192
|
+
|
|
193
|
+
# Customer service voices
|
|
194
|
+
self.build_tts_voice_v3('Siyue (Gentle)', 'Siyue',
|
|
195
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
196
|
+
self.build_tts_voice_v3('Aiya (Harsh)', 'Aiya',
|
|
197
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
198
|
+
self.build_tts_voice_v3('Aixia (Amiable)', 'Aixia',
|
|
199
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
200
|
+
self.build_tts_voice_v3('Aimei (Sweet)', 'Aimei',
|
|
201
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
202
|
+
self.build_tts_voice_v3('Aiyu (Natural)', 'Aiyu',
|
|
203
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
204
|
+
self.build_tts_voice_v3('Aiyue (Gentle)', 'Aiyue',
|
|
205
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
206
|
+
self.build_tts_voice_v3('Aijing (Harsh)', 'Aijing',
|
|
207
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
208
|
+
self.build_tts_voice_v3('Xiaomei (Sweet)', 'Xiaomei',
|
|
209
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
210
|
+
|
|
211
|
+
# Regional accent voices
|
|
212
|
+
self.build_tts_voice_v3('Aina (Zhejiang)', 'Aina',
|
|
213
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN]),
|
|
214
|
+
self.build_tts_voice_v3('Yina (Zhejiang)', 'Yina',
|
|
215
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN]),
|
|
216
|
+
self.build_tts_voice_v3('Sijing (Harsh)', 'Sijing',
|
|
217
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN]),
|
|
218
|
+
|
|
219
|
+
# Child voices
|
|
220
|
+
self.build_tts_voice_v3('Sitong (Child)', 'Sitong',
|
|
221
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN]),
|
|
222
|
+
self.build_tts_voice_v3('Xiaobei (Little Girl)', 'Xiaobei',
|
|
223
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN]),
|
|
224
|
+
self.build_tts_voice_v3('Aitong (Child)', 'Aitong',
|
|
225
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN]),
|
|
226
|
+
self.build_tts_voice_v3('Aiwei (Little Girl)', 'Aiwei',
|
|
227
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN]),
|
|
228
|
+
self.build_tts_voice_v3('Aibao (Little Girl)', 'Aibao',
|
|
229
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN]),
|
|
230
|
+
|
|
231
|
+
# English voices
|
|
232
|
+
self.build_tts_voice_v3('Harry (British)', 'Harry',
|
|
233
|
+
cloudlanguagetools.constants.Gender.Male, [cloudlanguagetools.languages.AudioLanguage.en_GB]),
|
|
234
|
+
self.build_tts_voice_v3('Abby (American)', 'Abby',
|
|
235
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
236
|
+
self.build_tts_voice_v3('Andy (American)', 'Andy',
|
|
237
|
+
cloudlanguagetools.constants.Gender.Male, [cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
238
|
+
self.build_tts_voice_v3('Eric (British)', 'Eric',
|
|
239
|
+
cloudlanguagetools.constants.Gender.Male, [cloudlanguagetools.languages.AudioLanguage.en_GB]),
|
|
240
|
+
self.build_tts_voice_v3('Emily (British)', 'Emily',
|
|
241
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.en_GB]),
|
|
242
|
+
self.build_tts_voice_v3('Luna (British)', 'Luna',
|
|
243
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.en_GB]),
|
|
244
|
+
self.build_tts_voice_v3('Luca (British)', 'Luca',
|
|
245
|
+
cloudlanguagetools.constants.Gender.Male, [cloudlanguagetools.languages.AudioLanguage.en_GB]),
|
|
246
|
+
self.build_tts_voice_v3('Wendy (British)', 'Wendy',
|
|
247
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.en_GB]),
|
|
248
|
+
self.build_tts_voice_v3('William (British)', 'William',
|
|
249
|
+
cloudlanguagetools.constants.Gender.Male, [cloudlanguagetools.languages.AudioLanguage.en_GB]),
|
|
250
|
+
self.build_tts_voice_v3('Olivia (British)', 'Olivia',
|
|
251
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.en_GB]),
|
|
252
|
+
|
|
253
|
+
# Special dialect voices
|
|
254
|
+
self.build_tts_voice_v3('Shanshan (Cantonese)', 'Shanshan',
|
|
255
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_HK]),
|
|
256
|
+
self.build_tts_voice_v3('Xiaoyue (Sichuan)', 'Xiaoyue',
|
|
257
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN]),
|
|
258
|
+
self.build_tts_voice_v3('Lydia (Bilingual)', 'Lydia',
|
|
259
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
260
|
+
self.build_tts_voice_v3('Aishuo (Natural)', 'Aishuo',
|
|
261
|
+
cloudlanguagetools.constants.Gender.Male, [cloudlanguagetools.languages.AudioLanguage.zh_CN, cloudlanguagetools.languages.AudioLanguage.en_US]),
|
|
262
|
+
self.build_tts_voice_v3('Qingqing (Taiwanese)', 'Qingqing',
|
|
263
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN]),
|
|
264
|
+
self.build_tts_voice_v3('Cuijie (Northeastern)', 'Cuijie',
|
|
265
|
+
cloudlanguagetools.constants.Gender.Female, [cloudlanguagetools.languages.AudioLanguage.zh_CN]),
|
|
266
|
+
self.build_tts_voice_v3('Xiaoze (Hunan)', 'Xiaoze',
|
|
267
|
+
cloudlanguagetools.constants.Gender.Male, [cloudlanguagetools.languages.AudioLanguage.zh_CN])
|
|
268
|
+
])
|
|
269
|
+
|
|
270
|
+
return result
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
import tempfile
|
|
4
|
+
import os
|
|
5
|
+
import boto3
|
|
6
|
+
import botocore.exceptions
|
|
7
|
+
import contextlib
|
|
8
|
+
import logging
|
|
9
|
+
import pprint
|
|
10
|
+
|
|
11
|
+
import cloudlanguagetools.service
|
|
12
|
+
import cloudlanguagetools.constants
|
|
13
|
+
import cloudlanguagetools.options
|
|
14
|
+
import cloudlanguagetools.languages
|
|
15
|
+
import cloudlanguagetools.ttsvoice
|
|
16
|
+
import cloudlanguagetools.translationlanguage
|
|
17
|
+
import cloudlanguagetools.transliterationlanguage
|
|
18
|
+
import cloudlanguagetools.errors
|
|
19
|
+
import cloudlanguagetools.audio_processing
|
|
20
|
+
|
|
21
|
+
from cloudlanguagetools.options import AudioFormat
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
DEFAULT_VOICE_PITCH = 0
|
|
26
|
+
DEFAULT_VOICE_RATE = 100
|
|
27
|
+
|
|
28
|
+
def get_audio_language_enum(language_code):
|
|
29
|
+
language_map = {
|
|
30
|
+
'arb': 'ar_XA',
|
|
31
|
+
'cmn-CN': 'zh_CN',
|
|
32
|
+
'yue-CN': 'zh_HK'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
language_enum_name = language_code.replace('-', '_')
|
|
36
|
+
if language_code in language_map:
|
|
37
|
+
language_enum_name = language_map[language_code]
|
|
38
|
+
|
|
39
|
+
return cloudlanguagetools.languages.AudioLanguage[language_enum_name]
|
|
40
|
+
|
|
41
|
+
class AmazonVoice(cloudlanguagetools.ttsvoice.TtsVoice):
|
|
42
|
+
def __init__(self, voice_data, engine: str):
|
|
43
|
+
# print(voice_data)
|
|
44
|
+
# {'Gender': 'Female', 'Id': 'Lotte', 'LanguageCode': 'nl-NL', 'LanguageName': 'Dutch', 'Name': 'Lotte', 'SupportedEngines': ['standard']}
|
|
45
|
+
self.service = cloudlanguagetools.constants.Service.Amazon
|
|
46
|
+
self.service_fee = cloudlanguagetools.constants.ServiceFee.paid
|
|
47
|
+
self.gender = cloudlanguagetools.constants.Gender[voice_data['Gender']]
|
|
48
|
+
self.voice_id = voice_data['Id']
|
|
49
|
+
self.name = voice_data['Name']
|
|
50
|
+
self.audio_language = get_audio_language_enum(voice_data['LanguageCode'])
|
|
51
|
+
self.engine = engine
|
|
52
|
+
|
|
53
|
+
def get_voice_key(self):
|
|
54
|
+
return {
|
|
55
|
+
'voice_id': self.voice_id,
|
|
56
|
+
'engine': self.engine
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
def get_voice_shortname(self):
|
|
60
|
+
return f'{self.name} ({self.engine.capitalize()})'
|
|
61
|
+
|
|
62
|
+
def get_options(self):
|
|
63
|
+
return {
|
|
64
|
+
'rate' : {
|
|
65
|
+
'type': 'number',
|
|
66
|
+
'min': 20,
|
|
67
|
+
'max': 200,
|
|
68
|
+
'default': DEFAULT_VOICE_RATE
|
|
69
|
+
},
|
|
70
|
+
'pitch': {
|
|
71
|
+
'type': 'number',
|
|
72
|
+
'min': -50,
|
|
73
|
+
'max': 50,
|
|
74
|
+
'default': DEFAULT_VOICE_PITCH
|
|
75
|
+
},
|
|
76
|
+
cloudlanguagetools.options.AUDIO_FORMAT_PARAMETER: {
|
|
77
|
+
'type': cloudlanguagetools.options.ParameterType.list.name,
|
|
78
|
+
'values': [
|
|
79
|
+
cloudlanguagetools.options.AudioFormat.mp3.name,
|
|
80
|
+
cloudlanguagetools.options.AudioFormat.ogg_vorbis.name,
|
|
81
|
+
cloudlanguagetools.options.AudioFormat.wav.name
|
|
82
|
+
],
|
|
83
|
+
'default': cloudlanguagetools.options.AudioFormat.mp3.name
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
class AmazonTranslationLanguage(cloudlanguagetools.translationlanguage.TranslationLanguage):
|
|
88
|
+
def __init__(self, language, language_id):
|
|
89
|
+
self.service = cloudlanguagetools.constants.Service.Amazon
|
|
90
|
+
self.service_fee = cloudlanguagetools.constants.ServiceFee.paid
|
|
91
|
+
self.language = language
|
|
92
|
+
self.language_id = language_id
|
|
93
|
+
|
|
94
|
+
def get_language_id(self):
|
|
95
|
+
return self.language_id
|
|
96
|
+
|
|
97
|
+
class AmazonService(cloudlanguagetools.service.Service):
|
|
98
|
+
def __init__(self):
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
def configure(self, config):
|
|
102
|
+
aws_access_key_id = config['AWS_ACCESS_KEY_ID']
|
|
103
|
+
aws_secret_access_key = config['AWS_SECRET_ACCESS_KEY']
|
|
104
|
+
region_name = config['AWS_DEFAULT_REGION']
|
|
105
|
+
|
|
106
|
+
self.polly_client = boto3.client("polly",
|
|
107
|
+
region_name=region_name,
|
|
108
|
+
aws_access_key_id=aws_access_key_id,
|
|
109
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
110
|
+
config=botocore.config.Config(connect_timeout=cloudlanguagetools.constants.RequestTimeout, read_timeout=cloudlanguagetools.constants.RequestTimeout))
|
|
111
|
+
self.translate_client = boto3.client("translate",
|
|
112
|
+
region_name=region_name,
|
|
113
|
+
aws_access_key_id=aws_access_key_id,
|
|
114
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
115
|
+
config=botocore.config.Config(connect_timeout=cloudlanguagetools.constants.RequestTimeout, read_timeout=cloudlanguagetools.constants.RequestTimeout))
|
|
116
|
+
|
|
117
|
+
def get_translation(self, text, from_language_key, to_language_key):
|
|
118
|
+
result = self.translate_client.translate_text(Text=text,
|
|
119
|
+
SourceLanguageCode=from_language_key, TargetLanguageCode=to_language_key)
|
|
120
|
+
return result.get('TranslatedText')
|
|
121
|
+
|
|
122
|
+
def get_tts_audio(self, text, voice_key, options):
|
|
123
|
+
response_format_parameter, audio_format = self.get_request_audio_format({
|
|
124
|
+
AudioFormat.mp3: 'mp3',
|
|
125
|
+
AudioFormat.ogg_vorbis: 'ogg_vorbis',
|
|
126
|
+
AudioFormat.wav: 'pcm'
|
|
127
|
+
}, options, AudioFormat.mp3)
|
|
128
|
+
|
|
129
|
+
logger.info(f'generating audio with voice {voice_key}')
|
|
130
|
+
|
|
131
|
+
# wav, we need to convert as described here:
|
|
132
|
+
# https://aws.amazon.com/blogs/machine-learning/integrating-amazon-polly-with-legacy-ivr-systems-by-converting-output-to-wav-format/
|
|
133
|
+
|
|
134
|
+
output_temp_file = tempfile.NamedTemporaryFile()
|
|
135
|
+
output_temp_filename = output_temp_file.name
|
|
136
|
+
|
|
137
|
+
pitch = options.get('pitch', DEFAULT_VOICE_PITCH)
|
|
138
|
+
pitch_str = f'{pitch:+.0f}%'
|
|
139
|
+
rate = options.get('rate', DEFAULT_VOICE_RATE)
|
|
140
|
+
rate_str = f'{rate:0.0f}%'
|
|
141
|
+
|
|
142
|
+
prosody_tags = f'pitch="{pitch_str}" rate="{rate_str}"'
|
|
143
|
+
if voice_key['engine'] == 'neural':
|
|
144
|
+
# pitch not supported on neural voices
|
|
145
|
+
prosody_tags = f'rate="{rate_str}"'
|
|
146
|
+
|
|
147
|
+
ssml_str = f"""<speak>
|
|
148
|
+
<prosody {prosody_tags} >
|
|
149
|
+
{text}
|
|
150
|
+
</prosody>
|
|
151
|
+
</speak>"""
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
if audio_format == cloudlanguagetools.options.AudioFormat.wav:
|
|
156
|
+
response = self.polly_client.synthesize_speech(Text=ssml_str,
|
|
157
|
+
TextType="ssml",
|
|
158
|
+
OutputFormat=response_format_parameter,
|
|
159
|
+
VoiceId=voice_key['voice_id'],
|
|
160
|
+
Engine=voice_key['engine'],
|
|
161
|
+
SampleRate="16000")
|
|
162
|
+
else:
|
|
163
|
+
if voice_key['engine'] in ['generative', 'long-form']:
|
|
164
|
+
logger.info(f'voice: {voice_key}, generating text format: {text}')
|
|
165
|
+
response = self.polly_client.synthesize_speech(Text=text, TextType="text", OutputFormat=response_format_parameter, VoiceId=voice_key['voice_id'], Engine=voice_key['engine'])
|
|
166
|
+
else:
|
|
167
|
+
logger.info(f'voice: {voice_key}, generating ssml format: {ssml_str}')
|
|
168
|
+
response = self.polly_client.synthesize_speech(Text=ssml_str, TextType="ssml", OutputFormat=response_format_parameter, VoiceId=voice_key['voice_id'], Engine=voice_key['engine'])
|
|
169
|
+
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as error:
|
|
170
|
+
logger.warning(f'Amazon Polly exception: {type(error).__name__}: {error}')
|
|
171
|
+
raise cloudlanguagetools.errors.RequestError(str(error)) from error
|
|
172
|
+
|
|
173
|
+
if "AudioStream" in response:
|
|
174
|
+
# Note: Closing the stream is important because the service throttles on the
|
|
175
|
+
# number of parallel connections. Here we are using contextlib.closing to
|
|
176
|
+
# ensure the close method of the stream object will be called automatically
|
|
177
|
+
# at the end of the with statement's scope.
|
|
178
|
+
with contextlib.closing(response["AudioStream"]) as stream:
|
|
179
|
+
with open(output_temp_filename, 'wb') as audio:
|
|
180
|
+
audio.write(stream.read())
|
|
181
|
+
|
|
182
|
+
if audio_format == cloudlanguagetools.options.AudioFormat.wav:
|
|
183
|
+
return cloudlanguagetools.audio_processing.wrap_pcm_data_wave(output_temp_file,
|
|
184
|
+
num_channels=1,
|
|
185
|
+
sample_width=2,
|
|
186
|
+
framerate=16000)
|
|
187
|
+
return output_temp_file
|
|
188
|
+
|
|
189
|
+
else:
|
|
190
|
+
# The response didn't contain audio data, exit gracefully
|
|
191
|
+
raise cloudlanguagetools.errors.RequestError('no audio stream')
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def get_tts_voice_list(self):
|
|
195
|
+
result = []
|
|
196
|
+
response = self.polly_client.describe_voices()
|
|
197
|
+
# print(response['Voices'])
|
|
198
|
+
for voice in response['Voices']:
|
|
199
|
+
logger.debug(f'voice: {pprint.pformat(voice)}')
|
|
200
|
+
for engine in voice['SupportedEngines']:
|
|
201
|
+
result.append(AmazonVoice(voice, engine))
|
|
202
|
+
return result
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def get_translation_language_list(self):
|
|
206
|
+
result = [
|
|
207
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.af, 'af'),
|
|
208
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.sq, 'sq'),
|
|
209
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.am, 'am'),
|
|
210
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.ar, 'ar'),
|
|
211
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.hy, 'hy'),
|
|
212
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.az, 'az'),
|
|
213
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.bn, 'bn'),
|
|
214
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.bs, 'bs'),
|
|
215
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.bg, 'bg'),
|
|
216
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.ca, 'ca'),
|
|
217
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.zh_cn, 'zh'),
|
|
218
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.zh_tw, 'zh-TW'),
|
|
219
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.hr, 'hr'),
|
|
220
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.cs, 'cs'),
|
|
221
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.da, 'da'),
|
|
222
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.prs, 'fa-AF'),
|
|
223
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.nl, 'nl'),
|
|
224
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.en, 'en'),
|
|
225
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.et, 'et'),
|
|
226
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.fa, 'fa'),
|
|
227
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.tl, 'tl'),
|
|
228
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.fi, 'fi'),
|
|
229
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.fr, 'fr'),
|
|
230
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.fr_ca, 'fr-CA'),
|
|
231
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.ka, 'ka'),
|
|
232
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.de, 'de'),
|
|
233
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.el, 'el'),
|
|
234
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.gu, 'gu'),
|
|
235
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.ht, 'ht'),
|
|
236
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.ha, 'ha'),
|
|
237
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.he, 'he'),
|
|
238
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.hi, 'hi'),
|
|
239
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.hu, 'hu'),
|
|
240
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.is_, 'is'),
|
|
241
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.id_, 'id'),
|
|
242
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.it, 'it'),
|
|
243
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.ja, 'ja'),
|
|
244
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.kn, 'kn'),
|
|
245
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.kk, 'kk'),
|
|
246
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.ko, 'ko'),
|
|
247
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.lv, 'lv'),
|
|
248
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.lt, 'lt'),
|
|
249
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.mk, 'mk'),
|
|
250
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.ms, 'ms'),
|
|
251
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.ml, 'ml'),
|
|
252
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.mt, 'mt'),
|
|
253
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.mn, 'mn'),
|
|
254
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.no, 'no'),
|
|
255
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.fa, 'fa'),
|
|
256
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.ps, 'ps'),
|
|
257
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.pl, 'pl'),
|
|
258
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.pt_pt, 'pt'),
|
|
259
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.ro, 'ro'),
|
|
260
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.ru, 'ru'),
|
|
261
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.sr_cyrl, 'sr'),
|
|
262
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.si, 'si'),
|
|
263
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.sk, 'sk'),
|
|
264
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.sl, 'sl'),
|
|
265
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.so, 'so'),
|
|
266
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.es, 'es'),
|
|
267
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.es_mx, 'es-MX'),
|
|
268
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.sw, 'sw'),
|
|
269
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.sv, 'sv'),
|
|
270
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.tl, 'tl'),
|
|
271
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.ta, 'ta'),
|
|
272
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.te, 'te'),
|
|
273
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.th, 'th'),
|
|
274
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.tr, 'tr'),
|
|
275
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.uk, 'uk'),
|
|
276
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.ur, 'ur'),
|
|
277
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.uz, 'uz'),
|
|
278
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.vi, 'vi'),
|
|
279
|
+
AmazonTranslationLanguage(cloudlanguagetools.languages.Language.cy, 'cy')
|
|
280
|
+
]
|
|
281
|
+
return result
|
|
282
|
+
|
|
283
|
+
def get_transliteration_language_list(self):
|
|
284
|
+
result = []
|
|
285
|
+
return result
|
|
286
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import wave
|
|
2
|
+
import tempfile
|
|
3
|
+
|
|
4
|
+
# audio utilities
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# take PCM audio and wrap it in a WAV container
|
|
8
|
+
def wrap_pcm_data_wave(audio_temp_file: tempfile.NamedTemporaryFile,
|
|
9
|
+
num_channels,
|
|
10
|
+
sample_width,
|
|
11
|
+
framerate) -> tempfile.NamedTemporaryFile:
|
|
12
|
+
# read the audio data
|
|
13
|
+
f = open(audio_temp_file.name, 'rb')
|
|
14
|
+
data = f.read()
|
|
15
|
+
f.close()
|
|
16
|
+
|
|
17
|
+
wav_frames = []
|
|
18
|
+
wav_frames.append(data)
|
|
19
|
+
|
|
20
|
+
wav_audio_temp_file = tempfile.NamedTemporaryFile(prefix='clt_wav_audio_', suffix='.wav')
|
|
21
|
+
|
|
22
|
+
WAVEFORMAT = wave.open(wav_audio_temp_file.name,'wb')
|
|
23
|
+
WAVEFORMAT.setnchannels(num_channels) # one channel, mono
|
|
24
|
+
WAVEFORMAT.setsampwidth(sample_width) # Polly's output is a stream of 16-bits (2 bytes) samples
|
|
25
|
+
WAVEFORMAT.setframerate(framerate)
|
|
26
|
+
WAVEFORMAT.writeframes(b''.join(wav_frames))
|
|
27
|
+
WAVEFORMAT.close()
|
|
28
|
+
|
|
29
|
+
return wav_audio_temp_file
|