cloudlanguagetools 11.3.4__tar.gz → 11.4.0__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/PKG-INFO +1 -1
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/amazon.py +27 -7
- cloudlanguagetools-11.4.0/cloudlanguagetools/audio_processing.py +29 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/azure.py +54 -37
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/constants.py +2 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/elevenlabs.py +27 -2
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/google.py +3 -1
- cloudlanguagetools-11.4.0/cloudlanguagetools/keys.py +1 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/naver.py +19 -14
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/openai.py +8 -5
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/service.py +11 -2
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/watson.py +21 -2
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools.egg-info/PKG-INFO +1 -1
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools.egg-info/SOURCES.txt +1 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/setup.py +1 -1
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/tests/test_audio.py +82 -9
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/tests/test_translation.py +21 -6
- cloudlanguagetools-11.3.4/cloudlanguagetools/keys.py +0 -1
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/LICENSE +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/README.rst +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/__init__.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/argostranslate.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/cereproc.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/chatapi.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/deepl.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/dictionarylookup.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/easypronunciation.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/encryption.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/epitran.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/errors.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/forvo.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/fptai.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/languages.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/libretranslate.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/mandarincantonese.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/options.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/pythainlp.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/servicemanager.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/spacy.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/test_services.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/tokenization.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/translationlanguage.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/transliterationlanguage.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/ttsvoice.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/vocalware.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/voicen.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/wenlin.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools.egg-info/dependency_links.txt +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools.egg-info/requires.txt +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools.egg-info/top_level.txt +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/setup.cfg +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/tests/test_breakdown.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/tests/test_chatapi.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/tests/test_dictionary_lookup.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/tests/test_llm.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/tests/test_mock_services.py +0 -0
- {cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/tests/test_servicemanager.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: cloudlanguagetools
|
3
|
-
Version: 11.
|
3
|
+
Version: 11.4.0
|
4
4
|
Summary: Interface with various cloud APIs for language processing such as translation, text to speech
|
5
5
|
Home-page: https://github.com/Language-Tools/cloud-language-tools-core
|
6
6
|
Author: Luc
|
@@ -6,6 +6,7 @@ import boto3
|
|
6
6
|
import botocore.exceptions
|
7
7
|
import contextlib
|
8
8
|
|
9
|
+
|
9
10
|
import cloudlanguagetools.service
|
10
11
|
import cloudlanguagetools.constants
|
11
12
|
import cloudlanguagetools.options
|
@@ -14,6 +15,9 @@ import cloudlanguagetools.ttsvoice
|
|
14
15
|
import cloudlanguagetools.translationlanguage
|
15
16
|
import cloudlanguagetools.transliterationlanguage
|
16
17
|
import cloudlanguagetools.errors
|
18
|
+
import cloudlanguagetools.audio_processing
|
19
|
+
|
20
|
+
from cloudlanguagetools.options import AudioFormat
|
17
21
|
|
18
22
|
DEFAULT_VOICE_PITCH = 0
|
19
23
|
DEFAULT_VOICE_RATE = 100
|
@@ -73,6 +77,7 @@ class AmazonVoice(cloudlanguagetools.ttsvoice.TtsVoice):
|
|
73
77
|
'values': [
|
74
78
|
cloudlanguagetools.options.AudioFormat.mp3.name,
|
75
79
|
cloudlanguagetools.options.AudioFormat.ogg_vorbis.name,
|
80
|
+
cloudlanguagetools.options.AudioFormat.wav.name
|
76
81
|
],
|
77
82
|
'default': cloudlanguagetools.options.AudioFormat.mp3.name
|
78
83
|
}
|
@@ -114,13 +119,14 @@ class AmazonService(cloudlanguagetools.service.Service):
|
|
114
119
|
return result.get('TranslatedText')
|
115
120
|
|
116
121
|
def get_tts_audio(self, text, voice_key, options):
|
117
|
-
|
118
|
-
|
122
|
+
response_format_parameter, audio_format = self.get_request_audio_format({
|
123
|
+
AudioFormat.mp3: 'mp3',
|
124
|
+
AudioFormat.ogg_vorbis: 'ogg_vorbis',
|
125
|
+
AudioFormat.wav: 'pcm'
|
126
|
+
}, options, AudioFormat.mp3)
|
119
127
|
|
120
|
-
|
121
|
-
|
122
|
-
cloudlanguagetools.options.AudioFormat.ogg_vorbis: 'ogg_vorbis'
|
123
|
-
}
|
128
|
+
# wav, we need to convert as described here:
|
129
|
+
# https://aws.amazon.com/blogs/machine-learning/integrating-amazon-polly-with-legacy-ivr-systems-by-converting-output-to-wav-format/
|
124
130
|
|
125
131
|
output_temp_file = tempfile.NamedTemporaryFile()
|
126
132
|
output_temp_filename = output_temp_file.name
|
@@ -143,7 +149,15 @@ class AmazonService(cloudlanguagetools.service.Service):
|
|
143
149
|
</speak>"""
|
144
150
|
|
145
151
|
try:
|
146
|
-
|
152
|
+
if audio_format == cloudlanguagetools.options.AudioFormat.wav:
|
153
|
+
response = self.polly_client.synthesize_speech(Text=ssml_str,
|
154
|
+
TextType="ssml",
|
155
|
+
OutputFormat=response_format_parameter,
|
156
|
+
VoiceId=voice_key['voice_id'],
|
157
|
+
Engine=voice_key['engine'],
|
158
|
+
SampleRate="16000")
|
159
|
+
else:
|
160
|
+
response = self.polly_client.synthesize_speech(Text=ssml_str, TextType="ssml", OutputFormat=response_format_parameter, VoiceId=voice_key['voice_id'], Engine=voice_key['engine'])
|
147
161
|
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as error:
|
148
162
|
raise cloudlanguagetools.errors.RequestError(str(error))
|
149
163
|
|
@@ -155,6 +169,12 @@ class AmazonService(cloudlanguagetools.service.Service):
|
|
155
169
|
with contextlib.closing(response["AudioStream"]) as stream:
|
156
170
|
with open(output_temp_filename, 'wb') as audio:
|
157
171
|
audio.write(stream.read())
|
172
|
+
|
173
|
+
if audio_format == cloudlanguagetools.options.AudioFormat.wav:
|
174
|
+
return cloudlanguagetools.audio_processing.wrap_pcm_data_wave(output_temp_file,
|
175
|
+
num_channels=1,
|
176
|
+
sample_width=2,
|
177
|
+
framerate=16000)
|
158
178
|
return output_temp_file
|
159
179
|
|
160
180
|
else:
|
@@ -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
|
@@ -6,6 +6,8 @@ import operator
|
|
6
6
|
import pydub
|
7
7
|
import logging
|
8
8
|
import pprint
|
9
|
+
import time
|
10
|
+
import cachetools
|
9
11
|
from typing import List
|
10
12
|
|
11
13
|
import cloudlanguagetools.service
|
@@ -17,6 +19,7 @@ import cloudlanguagetools.translationlanguage
|
|
17
19
|
import cloudlanguagetools.transliterationlanguage
|
18
20
|
import cloudlanguagetools.dictionarylookup
|
19
21
|
import cloudlanguagetools.errors
|
22
|
+
from cloudlanguagetools.options import AudioFormat
|
20
23
|
|
21
24
|
|
22
25
|
import azure.cognitiveservices.speech
|
@@ -52,6 +55,7 @@ VOICE_OPTIONS = {
|
|
52
55
|
'values': [
|
53
56
|
cloudlanguagetools.options.AudioFormat.mp3.name,
|
54
57
|
cloudlanguagetools.options.AudioFormat.ogg_opus.name,
|
58
|
+
cloudlanguagetools.options.AudioFormat.wav.name,
|
55
59
|
],
|
56
60
|
'default': cloudlanguagetools.options.AudioFormat.mp3.name
|
57
61
|
}
|
@@ -258,22 +262,18 @@ class AzureService(cloudlanguagetools.service.Service):
|
|
258
262
|
return headers
|
259
263
|
|
260
264
|
def get_tts_audio(self, text, voice_key, options):
|
261
|
-
|
262
|
-
audio_format_str = options.get(cloudlanguagetools.options.AUDIO_FORMAT_PARAMETER, cloudlanguagetools.options.AudioFormat.mp3.name)
|
263
|
-
audio_format = cloudlanguagetools.options.AudioFormat[audio_format_str]
|
264
|
-
|
265
265
|
# https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-text-to-speech?tabs=streaming#audio-outputs
|
266
266
|
# https://learn.microsoft.com/en-us/python/api/azure-cognitiveservices-speech/azure.cognitiveservices.speech.speechsynthesisoutputformat?view=azure-python
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
}
|
267
|
+
response_format_parameter, audio_format = self.get_request_audio_format({
|
268
|
+
AudioFormat.mp3: 'Audio24Khz96KBitRateMonoMp3',
|
269
|
+
AudioFormat.ogg_opus: 'Ogg48Khz16BitMonoOpus',
|
270
|
+
AudioFormat.wav: 'Riff48Khz16BitMonoPcm'
|
271
|
+
}, options, AudioFormat.mp3)
|
272
272
|
|
273
273
|
output_temp_file = tempfile.NamedTemporaryFile(prefix=f'cloudlanguage_tools_{self.__class__.__name__}_audio', suffix=f'.{audio_format.name}')
|
274
274
|
output_temp_filename = output_temp_file.name
|
275
275
|
speech_config = azure.cognitiveservices.speech.SpeechConfig(subscription=self.key, region=self.region)
|
276
|
-
speech_config.set_speech_synthesis_output_format(azure.cognitiveservices.speech.SpeechSynthesisOutputFormat[
|
276
|
+
speech_config.set_speech_synthesis_output_format(azure.cognitiveservices.speech.SpeechSynthesisOutputFormat[response_format_parameter])
|
277
277
|
synthesizer = azure.cognitiveservices.speech.SpeechSynthesizer(speech_config=speech_config, audio_config=None)
|
278
278
|
|
279
279
|
default_pitch = 0
|
@@ -330,17 +330,18 @@ class AzureService(cloudlanguagetools.service.Service):
|
|
330
330
|
headers = {
|
331
331
|
'Authorization': 'Bearer ' + token,
|
332
332
|
}
|
333
|
-
response = requests.get(constructed_url, headers=headers
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
333
|
+
response = requests.get(constructed_url, headers=headers,
|
334
|
+
timeout=cloudlanguagetools.constants.RequestTimeout)
|
335
|
+
response.raise_for_status()
|
336
|
+
voice_list = json.loads(response.content)
|
337
|
+
result = []
|
338
|
+
for voice_data in voice_list:
|
339
|
+
# print(voice_data['Status'])
|
340
|
+
try:
|
341
|
+
result.append(AzureVoice(voice_data))
|
342
|
+
except KeyError:
|
343
|
+
logging.error(f'could not process voice for {voice_data}', exc_info=True)
|
344
|
+
return result
|
344
345
|
|
345
346
|
def get_tts_voice_list_v3(self) -> List[cloudlanguagetools.ttsvoice.TtsVoice_v3]:
|
346
347
|
# returns list of TtsVoice_v3
|
@@ -353,17 +354,18 @@ class AzureService(cloudlanguagetools.service.Service):
|
|
353
354
|
headers = {
|
354
355
|
'Authorization': 'Bearer ' + token,
|
355
356
|
}
|
356
|
-
response = requests.get(constructed_url, headers=headers
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
357
|
+
response = requests.get(constructed_url, headers=headers,
|
358
|
+
timeout=cloudlanguagetools.constants.RequestTimeout)
|
359
|
+
response.raise_for_status()
|
360
|
+
voice_list = json.loads(response.content)
|
361
|
+
result = []
|
362
|
+
for voice_data in voice_list:
|
363
|
+
# print(voice_data['Status'])
|
364
|
+
try:
|
365
|
+
result.append(build_tts_voice_v3(voice_data))
|
366
|
+
except:
|
367
|
+
logger.exception(f'could not process voice for {voice_data}')
|
368
|
+
return result
|
367
369
|
|
368
370
|
def get_translation(self, text, from_language_key, to_language_key):
|
369
371
|
base_url = f'{self.url_translator_base}/translate?api-version=3.0'
|
@@ -415,7 +417,10 @@ class AzureService(cloudlanguagetools.service.Service):
|
|
415
417
|
return result
|
416
418
|
|
417
419
|
|
420
|
+
@cachetools.cached(cache=cachetools.TTLCache(maxsize=1024, ttl=cloudlanguagetools.constants.TTLCacheTimeout))
|
418
421
|
def get_supported_languages(self):
|
422
|
+
max_retries = 3
|
423
|
+
retry_delay = 5 # seconds
|
419
424
|
url = 'https://api.cognitive.microsofttranslator.com/languages?api-version=3.0'
|
420
425
|
|
421
426
|
# If you encounter any issues with the base_url or path, make sure
|
@@ -427,10 +432,20 @@ class AzureService(cloudlanguagetools.service.Service):
|
|
427
432
|
'X-ClientTraceId': str(uuid.uuid4())
|
428
433
|
}
|
429
434
|
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
435
|
+
for attempt in range(max_retries):
|
436
|
+
try:
|
437
|
+
request = requests.get(url, headers=headers,
|
438
|
+
timeout=cloudlanguagetools.constants.RequestTimeoutLong)
|
439
|
+
request.raise_for_status()
|
440
|
+
response = request.json()
|
441
|
+
return response
|
442
|
+
except requests.exceptions.RequestException as e:
|
443
|
+
if attempt < max_retries - 1: # If not the last attempt
|
444
|
+
logger.warning(f"Request failed. Retrying in {retry_delay} seconds... (Attempt {attempt + 1}/{max_retries})")
|
445
|
+
time.sleep(retry_delay)
|
446
|
+
else:
|
447
|
+
logger.error(f"Max retries reached. Unable to get supported languages.")
|
448
|
+
raise # Re-raise the last exception if all retries failed
|
434
449
|
|
435
450
|
# print(json.dumps(response, sort_keys=True, indent=4, ensure_ascii=False, separators=(',', ': ')))
|
436
451
|
|
@@ -475,6 +490,8 @@ class AzureService(cloudlanguagetools.service.Service):
|
|
475
490
|
sound = pydub.AudioSegment.from_mp3(mp3_filepath)
|
476
491
|
elif audio_format in [cloudlanguagetools.options.AudioFormat.ogg_opus, cloudlanguagetools.options.AudioFormat.ogg_vorbis]:
|
477
492
|
sound = pydub.AudioSegment.from_ogg(mp3_filepath)
|
493
|
+
elif audio_format == cloudlanguagetools.options.AudioFormat.wav:
|
494
|
+
sound = pydub.AudioSegment.from_wav(mp3_filepath)
|
478
495
|
wav_filepath = tempfile.NamedTemporaryFile(suffix='.wav').name
|
479
496
|
sound.export(wav_filepath, format="wav")
|
480
497
|
|
@@ -647,4 +664,4 @@ class AzureService(cloudlanguagetools.service.Service):
|
|
647
664
|
|
648
665
|
# return response[0]['translations'][0]['text']
|
649
666
|
|
650
|
-
|
667
|
+
|
@@ -55,6 +55,8 @@ class APIVersion(enum.Enum):
|
|
55
55
|
# ======================================
|
56
56
|
|
57
57
|
RequestTimeout = 10 # 10 seconds max
|
58
|
+
# need to change timeout for long requests, such as retrieving list of services
|
59
|
+
RequestTimeoutLong = 20 # 10 seconds max
|
58
60
|
ReadTimeout = 3 # 3 seconds read timeout
|
59
61
|
|
60
62
|
TTLCacheTimeout = 86400 # 24 hours
|
@@ -5,6 +5,7 @@ import tempfile
|
|
5
5
|
import os
|
6
6
|
import contextlib
|
7
7
|
import logging
|
8
|
+
import urllib.parse
|
8
9
|
from typing import List
|
9
10
|
|
10
11
|
import cloudlanguagetools.service
|
@@ -15,6 +16,7 @@ import cloudlanguagetools.ttsvoice
|
|
15
16
|
import cloudlanguagetools.translationlanguage
|
16
17
|
import cloudlanguagetools.transliterationlanguage
|
17
18
|
import cloudlanguagetools.errors
|
19
|
+
from cloudlanguagetools.options import AudioFormat
|
18
20
|
|
19
21
|
logger = logging.getLogger(__name__)
|
20
22
|
|
@@ -39,7 +41,15 @@ VOICE_OPTIONS = {
|
|
39
41
|
'min': 0.0,
|
40
42
|
'max': 1.0,
|
41
43
|
'default': DEFAULT_SIMILARITY_BOOST
|
42
|
-
},
|
44
|
+
},
|
45
|
+
cloudlanguagetools.options.AUDIO_FORMAT_PARAMETER: {
|
46
|
+
'type': cloudlanguagetools.options.ParameterType.list.name,
|
47
|
+
'values': [
|
48
|
+
cloudlanguagetools.options.AudioFormat.mp3.name,
|
49
|
+
cloudlanguagetools.options.AudioFormat.wav.name
|
50
|
+
],
|
51
|
+
'default': cloudlanguagetools.options.AudioFormat.mp3.name
|
52
|
+
}
|
43
53
|
}
|
44
54
|
|
45
55
|
class ElevenLabsVoice(cloudlanguagetools.ttsvoice.TtsVoice):
|
@@ -84,6 +94,11 @@ class ElevenLabsService(cloudlanguagetools.service.Service):
|
|
84
94
|
voice_id = voice_key['voice_id']
|
85
95
|
url = f'https://api.elevenlabs.io/v1/text-to-speech/{voice_id}'
|
86
96
|
|
97
|
+
response_format_parameter, audio_format = self.get_request_audio_format({
|
98
|
+
AudioFormat.mp3: 'mp3_44100_128',
|
99
|
+
AudioFormat.wav: 'pcm_44100'
|
100
|
+
}, options, AudioFormat.mp3)
|
101
|
+
|
87
102
|
headers = self.get_headers()
|
88
103
|
headers['Accept'] = "audio/mpeg"
|
89
104
|
|
@@ -96,7 +111,17 @@ class ElevenLabsService(cloudlanguagetools.service.Service):
|
|
96
111
|
}
|
97
112
|
}
|
98
113
|
|
99
|
-
|
114
|
+
query_params = {
|
115
|
+
'output_format': response_format_parameter
|
116
|
+
}
|
117
|
+
full_url = f'{url}?{urllib.parse.urlencode(query_params)}'
|
118
|
+
|
119
|
+
if audio_format == cloudlanguagetools.options.AudioFormat.wav:
|
120
|
+
return cloudlanguagetools.audio_processing.wrap_pcm_data_wave(self.get_tts_audio_base_post_request(full_url, json=data, headers=headers),
|
121
|
+
num_channels=1,
|
122
|
+
sample_width=2,
|
123
|
+
framerate=44100) # pcm_44100 - PCM format (S16LE) with 44.1kHz sample rate.
|
124
|
+
return self.get_tts_audio_base_post_request(full_url, json=data, headers=headers)
|
100
125
|
|
101
126
|
|
102
127
|
|
@@ -74,6 +74,7 @@ class GoogleVoice(cloudlanguagetools.ttsvoice.TtsVoice):
|
|
74
74
|
'values': [
|
75
75
|
cloudlanguagetools.options.AudioFormat.mp3.name,
|
76
76
|
cloudlanguagetools.options.AudioFormat.ogg_opus.name,
|
77
|
+
cloudlanguagetools.options.AudioFormat.wav.name
|
77
78
|
],
|
78
79
|
'default': cloudlanguagetools.options.AudioFormat.mp3.name
|
79
80
|
}
|
@@ -146,7 +147,8 @@ class GoogleService(cloudlanguagetools.service.Service):
|
|
146
147
|
|
147
148
|
audio_format_map = {
|
148
149
|
cloudlanguagetools.options.AudioFormat.mp3: google.cloud.texttospeech.AudioEncoding.MP3,
|
149
|
-
cloudlanguagetools.options.AudioFormat.ogg_opus: google.cloud.texttospeech.AudioEncoding.OGG_OPUS
|
150
|
+
cloudlanguagetools.options.AudioFormat.ogg_opus: google.cloud.texttospeech.AudioEncoding.OGG_OPUS,
|
151
|
+
cloudlanguagetools.options.AudioFormat.wav: google.cloud.texttospeech.AudioEncoding.LINEAR16
|
150
152
|
}
|
151
153
|
|
152
154
|
try:
|
@@ -0,0 +1 @@
|
|
1
|
+
KEYS='gAAAAABnCcYzERlg56peIVaJoWPgT7m06v9-gZ-xuDco6PWxZEu-BRxgHR9UaqN3g8HzKcfVSS-j1rBf-x0SHZlFQwcRlIQIY1S9mE3Wm8I6_xCGkFfqeuLkc8c9ScDIFnMt5MtLevVchJWklkl86HH6qoUZyzmzpQm5ihwdShaCwqryQZ95UDzuFis0rPDLX63gvmdWvXSiYRjXb4hO53ajE380bmjjp9E9mbH01Y6aKAz1hBpGWjEmepnajkkopM3uw-mCz0ABq9Ysdhs0yd9UklwvbOgUgdKL1BC_st0xt2piEShHtu0lQL5LOB1ZXBYHzFhk8wLlpftvDhYcFXapoZYBlg6d3tTKoOonPumN78CSIjQdxvTcG6mcA0p1VlAurcN4-YgtbDvtObsN5clpmDXXPupwMwf7Wddmut4sGGvMJXEba4EP5G6R_3Se8F6GRfawOgLVBAJ-SSpdKszJBu9w-BPIOzXkZ0D_ddQwC1CKURM4Kpjv0phHbqulykHgHe1l8mDr8ycC87647jowIz55An2ZttpRo9mdJztPGeuEjoz5RvQHJQvjmhAT7vajWnXv7sB2y50qUnWTF5ip1rde3GfDgmNzrOd7jSe8dxonAtFziKBCiCqc3pFfh7YgeHzWCR7lJcF7wIZvGJ-goLFiUT9uayXXVAVbPFku6et1DnUpDU48nyrO7lUvmwqRprTM706UWbbdZk8dBn7n91KzzjR4AbQhQ3hl6QuvA4WoJYdpzAaUN_4LUiWHNFWtd3uBLjHqTBLOHUlAETkkPkssm48pBqqALSJBWOrNSBIcLhAQI-JcJoer_fgH4KG4pDaeKgmGF7X26xi4rE48GsYwd7Tf2kRMQrHfyLefYDocMTCqH2I7NHs4i66LDvj_Zn9nmkSyuAbO1o-cnWvGmmrEymA39Jn1oiEG-f4gTv7kTkURSZu-FtBie9dKXTWG-EHy8qr7acFitEyuLnBfUpA75OWCEJG7wB4D5DfNti3zagUTq_DSjTBmTGSLRIlS0TVBEiyQ9Vjfpy4QaCd5AFXSXSI-sUzH_wIbY5kDysM8OJ_8rD1MeTDuXRJWLMJmokcOkjR8AXpVPxWCBvDmFnYhwjyA1AkvqpIUCw0H2W59ZQFG-k_AmKJXc3ZGMwEQQuE-fSlZr9jbwAGNTWJPvOVvZWil_yoT8Rb6P_KuA5px6CtfIERCEf-lpag_j6Rn9dGXAFtPrY1kCFCL6pFG9pgi8pHzSHpIkzCfYN1sGytUz6iiWeEV_ua9OCOyoTmuwHJBmMzD2SNMXaNmH3tcniIalbG817u0RUTgsgc79FlMnJV8fgub5kxw4Kedh9S8J6P4P2Z7JyF7x44IQxc9nGPLfT37wb8LibO_esJ-d8hI4XMyApoupt-hzCRNvPbmHRGyzoty9ubtkx_1mGLOMLjCDAEZqBpI1utKYL3j-U1eJDOJGBBugkvTCok9HkbMwWqsEUwMITfwpgz1Oc2ce-DK65Hg75KO55larQlJUU5QkjU65MRduSreCNT-8kLdWTSpyeReUGA1YhbmPhGNd3yzmEVnJV0VDxhrVjljabRpQIbYJ-13OXyRsrNWv386BDW5Wt8RH3MgyluTPh3azuaQkVn3u9C37cy1CgV67BaW0VPevpKFzAclVTQHcFI2zT4byccJSS8sOztnQ2Gzwl1ACFMGwAoso68JENQhEE8jMBLbI6Q-xENbSEaXj7E24oiTFTKsI--b8e1-dyLlTYl1aa-sdj6KKyx0Yr1MTWTZfzqo0Ep2-9JRJAeOBR_5_QMQBoRRwjJXmUeQGMSDynXaExHdn-lkggGxF6Rx_1iXwsvY_7n6uSM_Qg96KHS1eDFhyev1vBZ34MIgRFiYo8Ehr4uGJeXZqwAwFBQERkYR_xFPPgJ1ESk3J5xDa-WtvTgr7iEIulNfvbmx8rCZxvDwh0t6GhUmuLDf2xV0JS_hi7wRGSOLZ1Q4VtbDUr3q11FbKYIvoD_pHYKdqUovVn0oYUmwysWIKaZ-05GUF7Tu9SYluv5JB4cKf8r-Wi2GUQBC-KUnlfYsrGcLlje_puy8XGsU09NUzDDYeRkfeB3M6ZEk3agyfRf15_3813L05MoswWpQ2Kt5UUgY1SVgwxKAJltwN4tHzO2SUrxxalf7eBNV8PO8bPcIXMJwNh_jq6jPqzYbSBk2LRSuUWz_oQl2yUo2uBoL8kPAcR8WMp2uGymgHwPz9-msey2Vk1rfYcwK3yQY3P9KYN55heTdEua-QnS9d_rREqtsdToUH6WtPzMB9zwTrXe7wThP6u9FbqYkgzlCEqzdG-5OnOTZdRcQGhroxpL6fy17_B5RsTvBcjqvBiuR9X4C0mVgae1lwMQWF95ljH3j81oeUhFESdWUy94bluNAL-dHfTRoBwJQ9oaSWAT84sS6GyW98TUIM1M0pOphxLkFKndaD0lsHVxKQObKML0xb3QAACwt3lbcCAqA6_r33zBxreL4_GUGcPkG3eu-dTTztT-aQeL4DqgaOskOPdPuSi3ygUYMBLZ1slWpq6R9sT-pk9LtRMl192vy77fenhW2EEaPp3lgbwAzTsSmwL8Abtd_vEQ9gIAKHdZq0wBy0KUKQjPmho4Ry6tfsjJs2X-qpX0c-fsP_CCqZ8EMuMFVLd1xqtlA59Utp7lj9K-AObYX2ohAALIO4X146csvOyV0BgUoHu5ycNk6T93JcyeocSvOlgLjgn9YgAvgHw0tFSq8EyYGUb9ewrKyjNHuzFffjzvwgfupVJ1PiA8tO7TZuLMSY1NYnSQE3yLluKQNlH2oq9JN2XQ4wtr71yQq2R47GvcqpVqPn_VPCdcKnWqNRo4qlB8dRtfzyACWYRkLzeqWtQ5gEU5vipViECyBnQurpCBNVuMOckfff_dnBhMSkuhsvGbnecX7ThH5As8gDQG23TWb1agL-cnOKS6OPvZty1MiL0lB0jcvTNSHsgE2X1c9d_B_-yII9_EKnEoOh36y7YEWHN66sZE0qG2muawG31EnaYCtBU8FBUILcOI7xKwLZfZm2LxIdSd4oNd6fEWaRtYD-z4V0MkOhpWh2oUuxtpeB9tVMKv8UHBFifRrR87-3xykOEUSBDvTUb33tahvfSeDsEk24FEwa0ZJfq0NXma4a-MdpnvHDSblDPCrpw7AKY1wWWg-oEy4vMsO14qAIcFKgl6A4vWlGPO_rkunrgm9JlTXISRUqHDkgjxdFxdwlsDu-x1oIXvLpTbWveFM9E066Z7Rk0VKvaw8EPOw6FgqYH81yaHhhMzY8BMYiPgzteqtEwUceGoU2H3LXOK5-xw6_24c14nftqLjk0ujhY23HC8I3C8gA42LZsX6gD21rTVafSaS-XD4x2RMOfuhqsKvwODTu7tBx-HRBEekZRhVe_HNYHTi24XyDhQsCfLRZ4OtkI3j_-uGL34Am_qtapr3aQLTs0jLtBkeqmtpRVhw6Z3t2jcoHAzjhzlPUyTn_Pjc9RXWZ_mBNk_Fjl-waZZNii0kRqqvr99yjNZqopeweKfLtVLjHObpyL5DC9KTw7VUIAQgywFxFj7pgWmlPIdjQsBBIsqGFElOZmS-7UZ-ti79A2PEboqJYmDKOQXLMAdOt07O0GPornqeh-IYmaXWQ-BWdaMuaXVdq0Z1aRdi-mryOpcvy02N3L__vUdLXBQZIXG7iPA-Lu2t3S1ixn5ytVLPvCTmWPCl9BmCSZzy5vJK2BvSTF8spGW4sO2EAS_JktsvdnqH2RbTtenf7QcZP1c6aqyQTz44aW0KDnEMyTDBrwGwGesA4iVAZv6oxwSmD-d075paAXb5F9lu_9h8wkjllZyVDsYxpu2tRXNBynROQzSF4nvhdy3PGGSXKz0bPf5Ia9iZuUBg5ndH2SXgtFzW23lgbjNujgFfFi5bVqikyh8xJuIhMeRio8YwZP8IqSZwnezsIdoDEvihcclssrsyK4D9z9jx4UEHgK3dYTc5IpINmv4AnzKCEmMYgeHPnpTNWE7z7wk42rTQ--DuDe3mdwvAdqYfxSYMFu8MM-N1ZOqG2f_UljIbYnFQpYCiiXt8peffWYNx0NvKgRTuBzPZDJlZwGoi5H5Do_sht5kxX3fY4K9UegHcnnUPYvSm6aXOTNkHBC4fy3KGw-qxCrFpW8tx8twc0m2aYMXTR2XmHReLWOjrM6wFvGqf6JyDAieQEAJLCmAFjAgmeDHPpUymRvs4Cd9Xj821aCXoqDbE1MKNmr5OtIx8l8ktJzzzArrnfebu-S138_yD5ZtxddIx4D6jrUrdeTAb_IpSq5Ov9R4u7xZLhl19WhXDUQFcafVS19eyAZkh-JWkQKuVHuXg66rPCw4zFxeAHGnmu-Y7p7nsN303DGMD5b1ug4ameHGc9JpvGnfNEpw7dbbevmFwuAjkBOkutC1v2kVvmO4tnR5t3kEgJyBbgRMT4IldfFhWpMO0fdVjLAJCcTuJqm1jtE9xwbzL-OVmnNpjrG4Nh-kuOFEww8MjF9OgH8I1cGUqZpd9QSi6QfoVDA7Rb6DYcpRjgeSM2LVxD1nZT01ta1mD7AW-aprakQWk7MqVtU9RUbprmkNnL1td5AT7y9JoRukC0QhB-QtM8z2VwrX5k92bk7SoXXbmBpVysm-FKrzuY8yhYXCuyK996Pp7orkuZLbtAma1mRVOboRxx25GDKwPwZQN3vzBGxgcrTmr5Bw7uzaRIkPIO6kCLrRfgPHE0BPJcAI8jlDws1SSGrLZ1kpeOMwpoVtXij9jUFttwQWx0RGfXLYKK30H9cvIxrgaAwkIaq7J5-BeUmzPvZxZENTgyAgqFQWf_CbKgCDr8BTCtjCCsXsxk0QdgRL4B7ceBZ4orFjeCgWh2fEHg9YVNgegTL5vR5YH8hCnz4vBjA2diOU4ZMUvMdHQsIIbSK4Y29Lz3L27qsK8V55Go16-1u_ExHC5o5cV25cce4gzzJUugoHctrLWa73C8K5dlqh16W9gP_jekpfUPj52sfoi-aF9CteSvB8gyBjRtYNCEBjxNgAx_rTNZHL1jhEnZiGUmvs2B8ZSzdX8eWzr-NL5WN5xrysnMgPqCMBwnIRZrqgaccUGsuYJ9qag5WUcvhVS2QIwUj7_be41soNj1_5tA6Q-3dGdVNS8P7iPi79tSgP3FVcGCnW9-P2t8b6nBSn7FsbHLH-ytSNOVRcKyCt0jEa8x0-PRsnza67tWz_logEmPrRPrSaC4r7eCD04IIoZuSM4EcqaFWMIXEm_BNU9vcHSTRfZioxafIQAR7sMjZy0vxUZPIAsaodSu-QfmqJfH0eSXaEpfCngFiidD0YUz2pB8lQMRAvl_eXPynXccgyk7PsjtrMAjh-l48_C3t7hw1BYAPl8BLQzvOBB5__GOAofxItQa50VIx_FK2aT95n_6u-CcYSYP37y_y84AzuTEmPafqN0Y-0m-pjlq56P63RJXmH84VOHv1cfxV-5v4wf7vbrpU2WZOVrgudO0uvsU4dDjuzMkjiTSvqGNSnvp4HUrpZ0YSq8pwegHYTx8TpTFA8oo5UsM0jJY4nTDk9zPe2hRH_RDomNCJKYoTKNjcWaTn0apqq-0LyNtOBx2qAtYN9HKny-WMlI4EGIO4ZoU2j_Krln9NGXSo_CwuW58NBirfQ7RkMmByJFml7KymuhuCaN5SzKb3rTaOgU7Y-RI16c14YgC__8QDzDJeXKNDnlKs83qQWoFijrBAuSFw49G-OUpYEpx3pt7bmYpirL_NOaKfJ7_simblVpMJp6B3bbBnmg5YcnTNHwLOcW99CwTIximeASfGYuMqELYKnLw1nPTFOuZbw8M5IjacSwMiIWD0Oj0ZgiRaNzUWpubLXpRsk5Ap8fzVmIuQrN2BoyKdmvrvN3Ibwx8LolfWjE0I72R2bliQndIDbYkMVKUsZidigbCszLeQTm2wb8DOi4sryp3m7o6R-kEcw0aoFHnWezRqjA-h7oLUzwyM-Vd7o33HwxrxCiq5LbGvNmtQ4Y0uLndqWy4BaiIPKLXPx8hicexxyG_nb0TZ7MX2iDl0nmYvwe_-owxmw-0GQ7dVZfeLCJhjcR6R1ltjSe6KAeoOWBPymHwf2y7O_K31vw1VXfBG_Z8IP9LjMtNz9xNbDMat0V_MTCxDCUNLpKfRjjCr3jQOZQ8cEaBnIUC7ZXuqI6uZVNrAbc0zNSyC_PESFDV2-f1aQ426GpMZqXX2yFI5ieF2G9cPbpWgDez_J12EHTBFiUPoHxFi_0dSVz3qCWOgUCnbgBlQqyy1VXl38gJnKGSED6knhOrNZz0Y3CT1b_B1110EScTeePvzQRIx7R39ob1sxN'
|
@@ -13,6 +13,7 @@ import cloudlanguagetools.ttsvoice
|
|
13
13
|
import cloudlanguagetools.translationlanguage
|
14
14
|
import cloudlanguagetools.transliterationlanguage
|
15
15
|
import cloudlanguagetools.errors
|
16
|
+
from cloudlanguagetools.options import AudioFormat
|
16
17
|
|
17
18
|
NAVER_VOICE_SPEED_DEFAULT = 0
|
18
19
|
NAVER_VOICE_PITCH_DEFAULT = 0
|
@@ -48,6 +49,14 @@ class NaverVoice(cloudlanguagetools.ttsvoice.TtsVoice):
|
|
48
49
|
'min': -5,
|
49
50
|
'max': 5,
|
50
51
|
'default': NAVER_VOICE_PITCH_DEFAULT
|
52
|
+
},
|
53
|
+
cloudlanguagetools.options.AUDIO_FORMAT_PARAMETER: {
|
54
|
+
'type': cloudlanguagetools.options.ParameterType.list.name,
|
55
|
+
'values': [
|
56
|
+
cloudlanguagetools.options.AudioFormat.mp3.name,
|
57
|
+
cloudlanguagetools.options.AudioFormat.wav.name
|
58
|
+
],
|
59
|
+
'default': cloudlanguagetools.options.AudioFormat.mp3.name
|
51
60
|
}
|
52
61
|
}
|
53
62
|
|
@@ -63,7 +72,7 @@ class NaverTranslationLanguage(cloudlanguagetools.translationlanguage.Translatio
|
|
63
72
|
|
64
73
|
class NaverService(cloudlanguagetools.service.Service):
|
65
74
|
def __init__(self):
|
66
|
-
|
75
|
+
self.service = cloudlanguagetools.constants.Service.Naver
|
67
76
|
|
68
77
|
def configure(self, config):
|
69
78
|
self.client_id = config['client_id']
|
@@ -93,8 +102,10 @@ class NaverService(cloudlanguagetools.service.Service):
|
|
93
102
|
|
94
103
|
|
95
104
|
def get_tts_audio(self, text, voice_key, options):
|
96
|
-
|
97
|
-
|
105
|
+
response_format_parameter, audio_format = self.get_request_audio_format({
|
106
|
+
AudioFormat.mp3: 'mp3',
|
107
|
+
AudioFormat.wav: 'wav'
|
108
|
+
}, options, AudioFormat.mp3)
|
98
109
|
|
99
110
|
url = 'https://naveropenapi.apigw.ntruss.com/tts-premium/v1/tts'
|
100
111
|
headers = {
|
@@ -107,19 +118,13 @@ class NaverService(cloudlanguagetools.service.Service):
|
|
107
118
|
'text': text,
|
108
119
|
'speaker': voice_key['name'],
|
109
120
|
'speed': options.get('speed', NAVER_VOICE_SPEED_DEFAULT),
|
110
|
-
'pitch': options.get('pitch', NAVER_VOICE_PITCH_DEFAULT)
|
121
|
+
'pitch': options.get('pitch', NAVER_VOICE_PITCH_DEFAULT),
|
122
|
+
'format': response_format_parameter
|
111
123
|
}
|
124
|
+
if audio_format == cloudlanguagetools.options.AudioFormat.wav:
|
125
|
+
data['sampling-rate'] = 48000
|
112
126
|
|
113
|
-
|
114
|
-
response = requests.post(url, data=data, headers=headers, timeout=cloudlanguagetools.constants.RequestTimeout)
|
115
|
-
if response.status_code == 200:
|
116
|
-
with open(output_temp_filename, 'wb') as audio:
|
117
|
-
audio.write(response.content)
|
118
|
-
return output_temp_file
|
119
|
-
|
120
|
-
response_data = response.json()
|
121
|
-
error_message = f'Status code: {response.status_code}: {response_data}'
|
122
|
-
raise cloudlanguagetools.errors.RequestError(error_message)
|
127
|
+
return self.get_tts_audio_base_post_request(url, data=data, headers=headers)
|
123
128
|
|
124
129
|
def get_tts_voice_list(self):
|
125
130
|
# returns list of TtSVoice
|
@@ -12,6 +12,7 @@ import cloudlanguagetools.languages
|
|
12
12
|
import cloudlanguagetools.options
|
13
13
|
|
14
14
|
from cloudlanguagetools.languages import AudioLanguage
|
15
|
+
from cloudlanguagetools.options import AudioFormat
|
15
16
|
|
16
17
|
logger = logging.getLogger(__name__)
|
17
18
|
|
@@ -29,6 +30,7 @@ VOICE_OPTIONS = {
|
|
29
30
|
'values': [
|
30
31
|
cloudlanguagetools.options.AudioFormat.mp3.name,
|
31
32
|
cloudlanguagetools.options.AudioFormat.ogg_opus.name,
|
33
|
+
cloudlanguagetools.options.AudioFormat.wav.name
|
32
34
|
],
|
33
35
|
'default': cloudlanguagetools.options.AudioFormat.mp3.name
|
34
36
|
}
|
@@ -217,16 +219,17 @@ class OpenAIService(cloudlanguagetools.service.Service):
|
|
217
219
|
output_temp_file = tempfile.NamedTemporaryFile()
|
218
220
|
|
219
221
|
speed = options.get('speed', DEFAULT_TTS_SPEED)
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
222
|
+
response_format_parameter, audio_format = self.get_request_audio_format({
|
223
|
+
AudioFormat.mp3: 'mp3',
|
224
|
+
AudioFormat.ogg_opus: 'opus',
|
225
|
+
AudioFormat.wav: 'wav'
|
226
|
+
}, options, AudioFormat.mp3)
|
224
227
|
|
225
228
|
response = self.client.audio.speech.create(
|
226
229
|
model='tts-1-hd',
|
227
230
|
voice=voice_key['name'],
|
228
231
|
input=text,
|
229
|
-
response_format=
|
232
|
+
response_format=response_format_parameter,
|
230
233
|
speed=speed
|
231
234
|
)
|
232
235
|
response.stream_to_file(output_temp_file.name)
|
@@ -1,10 +1,11 @@
|
|
1
1
|
import requests
|
2
2
|
import tempfile
|
3
3
|
import logging
|
4
|
-
from typing import List
|
4
|
+
from typing import List, Dict
|
5
5
|
|
6
6
|
import cloudlanguagetools.constants
|
7
7
|
import cloudlanguagetools.ttsvoice
|
8
|
+
import cloudlanguagetools.options
|
8
9
|
|
9
10
|
logger = logging.getLogger(__name__)
|
10
11
|
|
@@ -21,9 +22,10 @@ class Service():
|
|
21
22
|
|
22
23
|
def get_tts_audio_base_post_request(self, url, **kwargs):
|
23
24
|
try:
|
25
|
+
kwargs['timeout'] = cloudlanguagetools.constants.RequestTimeout
|
24
26
|
response = self.post_request(url, **kwargs)
|
25
27
|
response.raise_for_status()
|
26
|
-
output_temp_file = tempfile.NamedTemporaryFile()
|
28
|
+
output_temp_file = tempfile.NamedTemporaryFile(prefix='clt_audio_')
|
27
29
|
output_temp_filename = output_temp_file.name
|
28
30
|
with open(output_temp_filename, 'wb') as audio:
|
29
31
|
audio.write(response.content)
|
@@ -35,6 +37,13 @@ class Service():
|
|
35
37
|
logger.exception(error_message)
|
36
38
|
raise cloudlanguagetools.errors.RequestError(error_message)
|
37
39
|
|
40
|
+
def get_request_audio_format(self, format_map: Dict, options: Dict, default_format: cloudlanguagetools.options.AudioFormat):
|
41
|
+
response_format_str = options.get(cloudlanguagetools.options.AUDIO_FORMAT_PARAMETER,
|
42
|
+
default_format.name)
|
43
|
+
response_format = cloudlanguagetools.options.AudioFormat[response_format_str]
|
44
|
+
response_format_parameter = format_map[response_format]
|
45
|
+
return response_format_parameter, response_format
|
46
|
+
|
38
47
|
# used for pre-loading models
|
39
48
|
def load_data(self):
|
40
49
|
pass
|
@@ -11,6 +11,7 @@ import cloudlanguagetools.ttsvoice
|
|
11
11
|
import cloudlanguagetools.translationlanguage
|
12
12
|
import cloudlanguagetools.transliterationlanguage
|
13
13
|
import cloudlanguagetools.errors
|
14
|
+
from cloudlanguagetools.options import AudioFormat
|
14
15
|
|
15
16
|
logger = logging.getLogger(__name__)
|
16
17
|
|
@@ -71,7 +72,18 @@ class WatsonVoice(cloudlanguagetools.ttsvoice.TtsVoice):
|
|
71
72
|
return self.description.split(':')[0] + is_dnn
|
72
73
|
|
73
74
|
def get_options(self):
|
74
|
-
return {
|
75
|
+
return {
|
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_opus.name,
|
81
|
+
cloudlanguagetools.options.AudioFormat.ogg_vorbis.name,
|
82
|
+
cloudlanguagetools.options.AudioFormat.wav.name
|
83
|
+
],
|
84
|
+
'default': cloudlanguagetools.options.AudioFormat.mp3.name
|
85
|
+
}
|
86
|
+
}
|
75
87
|
|
76
88
|
class WatsonService(cloudlanguagetools.service.Service):
|
77
89
|
def __init__(self):
|
@@ -124,6 +136,13 @@ class WatsonService(cloudlanguagetools.service.Service):
|
|
124
136
|
return result
|
125
137
|
|
126
138
|
def get_tts_audio(self, text, voice_key, options):
|
139
|
+
response_format_parameter, audio_format = self.get_request_audio_format({
|
140
|
+
AudioFormat.mp3: 'audio/mp3',
|
141
|
+
AudioFormat.ogg_opus: 'audio/ogg;codecs=opus',
|
142
|
+
AudioFormat.ogg_vorbis: 'audio/ogg;codecs=vorbis',
|
143
|
+
AudioFormat.wav: 'audio/wav;rate=48000'
|
144
|
+
}, options, AudioFormat.mp3)
|
145
|
+
|
127
146
|
output_temp_file = tempfile.NamedTemporaryFile()
|
128
147
|
output_temp_filename = output_temp_file.name
|
129
148
|
|
@@ -133,7 +152,7 @@ class WatsonService(cloudlanguagetools.service.Service):
|
|
133
152
|
constructed_url = base_url + url_path + f'?voice={voice_name}'
|
134
153
|
headers = {
|
135
154
|
'Content-Type': 'application/json',
|
136
|
-
'Accept':
|
155
|
+
'Accept': response_format_parameter
|
137
156
|
}
|
138
157
|
|
139
158
|
data = {
|
{cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools.egg-info/PKG-INFO
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: cloudlanguagetools
|
3
|
-
Version: 11.
|
3
|
+
Version: 11.4.0
|
4
4
|
Summary: Interface with various cloud APIs for language processing such as translation, text to speech
|
5
5
|
Home-page: https://github.com/Language-Tools/cloud-language-tools-core
|
6
6
|
Author: Luc
|
@@ -6,7 +6,7 @@ from setuptools.command.install import install
|
|
6
6
|
# twine upload dist/*
|
7
7
|
|
8
8
|
setup(name='cloudlanguagetools',
|
9
|
-
version='11.
|
9
|
+
version='11.4.0',
|
10
10
|
description='Interface with various cloud APIs for language processing such as translation, text to speech',
|
11
11
|
long_description=open('README.rst', encoding='utf-8').read(),
|
12
12
|
url='https://github.com/Language-Tools/cloud-language-tools-core',
|
@@ -11,6 +11,7 @@ import pytest
|
|
11
11
|
import json
|
12
12
|
import time
|
13
13
|
import pprint
|
14
|
+
import functools
|
14
15
|
|
15
16
|
import audio_utils
|
16
17
|
|
@@ -33,13 +34,26 @@ def get_manager():
|
|
33
34
|
|
34
35
|
return manager
|
35
36
|
|
37
|
+
|
38
|
+
|
39
|
+
def skip_unreliable_clt_test():
|
40
|
+
def decorator(func):
|
41
|
+
@functools.wraps(func)
|
42
|
+
def wrapper(*args, **kwargs):
|
43
|
+
if not CLOUDLANGUAGETOOLS_CORE_TEST_UNRELIABLE:
|
44
|
+
pytest.skip(f'you must set CLOUDLANGUAGETOOLS_CORE_TEST_UNRELIABLE=yes')
|
45
|
+
return func(*args, **kwargs)
|
46
|
+
return wrapper
|
47
|
+
return decorator
|
48
|
+
|
49
|
+
|
36
50
|
class TestAudio(unittest.TestCase):
|
37
51
|
|
38
52
|
ENGLISH_INPUT_TEXT = 'This is the best restaurant in town.'
|
39
|
-
FRENCH_INPUT_TEXT =
|
40
|
-
JAPANESE_INPUT_TEXT = '
|
41
|
-
CHINESE_INPUT_TEXT = '
|
42
|
-
KOREAN_INPUT_TEXT = '
|
53
|
+
FRENCH_INPUT_TEXT = 'Bonjour'
|
54
|
+
JAPANESE_INPUT_TEXT = 'こんにちは'
|
55
|
+
CHINESE_INPUT_TEXT = '你好'
|
56
|
+
KOREAN_INPUT_TEXT = '안녕하세요'
|
43
57
|
|
44
58
|
@classmethod
|
45
59
|
def setUpClass(cls):
|
@@ -73,6 +87,18 @@ class TestAudio(unittest.TestCase):
|
|
73
87
|
subset = [x for x in self.voice_list if x['audio_language_code'] == audio_language.name and x['service'] == service.name]
|
74
88
|
return subset
|
75
89
|
|
90
|
+
def get_voice_by_service_and_name(self, service: Service, voice_name) -> cloudlanguagetools.ttsvoice.TtsVoice_v3:
|
91
|
+
subset = [x for x in self.voice_list_v3 if voice_name in x.name and x.service == service]
|
92
|
+
self.assertEqual(len(subset), 1)
|
93
|
+
return subset[0]
|
94
|
+
|
95
|
+
def get_voice_by_lambda(self, service: Service, filter_func, assert_unique=True):
|
96
|
+
service_voices = [x for x in self.voice_list_v3 if x.service == service]
|
97
|
+
subset = [x for x in service_voices if filter_func(x)]
|
98
|
+
if assert_unique:
|
99
|
+
self.assertEqual(len(subset), 1, pprint.pformat(subset))
|
100
|
+
return subset[0]
|
101
|
+
|
76
102
|
def verify_voice(self, voice, text, recognition_language):
|
77
103
|
return self.verify_voice_internal(voice['voice_key'], voice['service'], text, recognition_language)
|
78
104
|
|
@@ -204,11 +230,11 @@ class TestAudio(unittest.TestCase):
|
|
204
230
|
self.verify_service_audio_language(source_text, Service.Amazon, AudioLanguage.fr_FR, 'fr-FR')
|
205
231
|
|
206
232
|
def test_mandarin_google(self):
|
207
|
-
source_text =
|
233
|
+
source_text = self.CHINESE_INPUT_TEXT
|
208
234
|
self.verify_service_audio_language(source_text, Service.Google, AudioLanguage.zh_CN, 'zh-CN')
|
209
235
|
|
210
236
|
def test_mandarin_azure(self):
|
211
|
-
source_text =
|
237
|
+
source_text = self.CHINESE_INPUT_TEXT
|
212
238
|
self.verify_service_audio_language(source_text, Service.Azure, AudioLanguage.zh_CN, 'zh-CN')
|
213
239
|
|
214
240
|
def test_azure_standard_voice_deprecated(self):
|
@@ -285,16 +311,15 @@ class TestAudio(unittest.TestCase):
|
|
285
311
|
source_text = '老人家'
|
286
312
|
self.verify_service_audio_language(source_text, Service.CereProc, AudioLanguage.zh_CN, 'zh-CN')
|
287
313
|
|
314
|
+
@skip_unreliable_clt_test()
|
288
315
|
def test_mandarin_vocalware(self):
|
289
316
|
# pytest test_audio.py -k test_mandarin_vocalware
|
290
|
-
if not CLOUDLANGUAGETOOLS_CORE_TEST_UNRELIABLE:
|
291
|
-
pytest.skip('you must set CLOUDLANGUAGETOOLS_CORE_TEST_UNRELIABLE=yes')
|
292
317
|
|
293
318
|
source_text = '你好'
|
294
319
|
self.verify_service_audio_language(source_text, Service.VocalWare, AudioLanguage.zh_CN, 'zh-CN')
|
295
320
|
|
296
321
|
def test_cantonese_google(self):
|
297
|
-
source_text =
|
322
|
+
source_text = self.CHINESE_INPUT_TEXT
|
298
323
|
self.verify_service_audio_language(source_text, Service.Google, AudioLanguage.zh_HK, 'zh-HK')
|
299
324
|
|
300
325
|
def test_cantonese_azure(self):
|
@@ -354,6 +379,7 @@ class TestAudio(unittest.TestCase):
|
|
354
379
|
source_text = 'hello <break time="50ms"/>world'
|
355
380
|
self.verify_service_audio_language(source_text, Service.Google, AudioLanguage.en_US, 'en-US')
|
356
381
|
|
382
|
+
@skip_unreliable_clt_test()
|
357
383
|
def test_ssml_english_azure(self):
|
358
384
|
# pytest tests/test_audio.py -k test_ssml_english_azure
|
359
385
|
source_text = 'hello <break time="200ms"/>world'
|
@@ -475,6 +501,50 @@ class TestAudio(unittest.TestCase):
|
|
475
501
|
audio_text = audio_utils.speech_to_text(self.manager, audio_temp_file, 'fr-FR', audio_format=cloudlanguagetools.options.AudioFormat.ogg_opus)
|
476
502
|
self.assertEqual(audio_utils.sanitize_recognized_text(source_text), audio_utils.sanitize_recognized_text(audio_text))
|
477
503
|
|
504
|
+
def verify_wav_voice(self, voice: cloudlanguagetools.ttsvoice.TtsVoice_v3, text: str, recognition_language: str):
|
505
|
+
# assert that the wav format is in the list of supported formats
|
506
|
+
self.assertTrue(cloudlanguagetools.options.AudioFormat.wav.name in
|
507
|
+
voice.options[cloudlanguagetools.options.AUDIO_FORMAT_PARAMETER]['values'])
|
508
|
+
options = {cloudlanguagetools.options.AUDIO_FORMAT_PARAMETER: cloudlanguagetools.options.AudioFormat.wav.name}
|
509
|
+
audio_temp_file = self.manager.get_tts_audio(text, voice.service, voice.voice_key, options)
|
510
|
+
audio_utils.assert_is_wav_format(self, audio_temp_file.name)
|
511
|
+
audio_text = audio_utils.speech_to_text(self.manager, audio_temp_file, recognition_language, audio_format=cloudlanguagetools.options.AudioFormat.wav)
|
512
|
+
self.assertEqual(audio_utils.sanitize_recognized_text(text), audio_utils.sanitize_recognized_text(audio_text))
|
513
|
+
|
514
|
+
def test_azure_format_wav(self):
|
515
|
+
fr_voice = self.get_voice_by_service_and_name(Service.Azure, 'Denise')
|
516
|
+
self.verify_wav_voice(fr_voice, self.FRENCH_INPUT_TEXT, 'fr-FR')
|
517
|
+
|
518
|
+
def test_amazon_format_wav(self):
|
519
|
+
fr_voice = self.get_voice_by_service_and_name(Service.Amazon, 'Mathieu')
|
520
|
+
self.verify_wav_voice(fr_voice, self.FRENCH_INPUT_TEXT, 'fr-FR')
|
521
|
+
|
522
|
+
def test_elevenlabs_format_wav(self):
|
523
|
+
fr_voice = self.get_voice_by_lambda(Service.ElevenLabs,
|
524
|
+
lambda x: 'Charlotte' in x.name and x.voice_key['model_id'] == 'eleven_multilingual_v2')
|
525
|
+
self.verify_wav_voice(fr_voice, self.FRENCH_INPUT_TEXT, 'fr-FR')
|
526
|
+
|
527
|
+
def test_google_format_wav(self):
|
528
|
+
fr_voice = self.get_voice_by_lambda(Service.Google,
|
529
|
+
lambda x: AudioLanguage.fr_FR in x.audio_languages, assert_unique=False)
|
530
|
+
self.verify_wav_voice(fr_voice, self.FRENCH_INPUT_TEXT, 'fr-FR')
|
531
|
+
|
532
|
+
def test_naver_format_wav(self):
|
533
|
+
ko_voice = self.get_voice_by_lambda(Service.Naver,
|
534
|
+
lambda x: AudioLanguage.ko_KR in x.audio_languages, assert_unique=False)
|
535
|
+
self.verify_wav_voice(ko_voice, self.KOREAN_INPUT_TEXT, 'ko-KR')
|
536
|
+
|
537
|
+
def test_openai_format_wav(self):
|
538
|
+
ko_voice = self.get_voice_by_lambda(Service.OpenAI,
|
539
|
+
lambda x: AudioLanguage.en_US in x.audio_languages, assert_unique=False)
|
540
|
+
self.verify_wav_voice(ko_voice, self.ENGLISH_INPUT_TEXT, 'en-US')
|
541
|
+
|
542
|
+
def test_watson_format_wav(self):
|
543
|
+
en_voice = self.get_voice_by_lambda(Service.Watson,
|
544
|
+
lambda x: AudioLanguage.en_US in x.audio_languages, assert_unique=False)
|
545
|
+
self.verify_wav_voice(en_voice, self.ENGLISH_INPUT_TEXT, 'en-US')
|
546
|
+
|
547
|
+
|
478
548
|
def test_google_voice_journey(self):
|
479
549
|
service = 'Google'
|
480
550
|
source_text = self.ENGLISH_INPUT_TEXT
|
@@ -592,12 +662,15 @@ class TestAudio(unittest.TestCase):
|
|
592
662
|
source_text = 'Ich mag das Essen nicht.'
|
593
663
|
self.verify_service_audio_language(source_text, Service.ElevenLabs, AudioLanguage.de_DE, 'de-DE')
|
594
664
|
|
665
|
+
@skip_unreliable_clt_test()
|
595
666
|
def test_elevenlabs_japanese(self):
|
596
667
|
self.verify_service_japanese(Service.ElevenLabs)
|
597
668
|
|
669
|
+
@skip_unreliable_clt_test()
|
598
670
|
def test_elevenlabs_chinese(self):
|
599
671
|
self.verify_service_chinese(Service.ElevenLabs)
|
600
672
|
|
673
|
+
@skip_unreliable_clt_test()
|
601
674
|
def test_elevenlabs_korean(self):
|
602
675
|
self.verify_service_korean(Service.ElevenLabs)
|
603
676
|
|
@@ -5,6 +5,8 @@ import unittest
|
|
5
5
|
import pytest
|
6
6
|
import json
|
7
7
|
import pprint
|
8
|
+
import time
|
9
|
+
import requests
|
8
10
|
|
9
11
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
10
12
|
|
@@ -21,11 +23,24 @@ def get_manager():
|
|
21
23
|
|
22
24
|
class TestTranslation(unittest.TestCase):
|
23
25
|
def setUp(self):
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
max_retries = 3
|
27
|
+
retry_delay = 5 # seconds
|
28
|
+
|
29
|
+
for attempt in range(max_retries):
|
30
|
+
try:
|
31
|
+
self.manager = get_manager()
|
32
|
+
self.language_list = self.manager.get_language_list()
|
33
|
+
self.translation_language_list = self.manager.get_translation_language_list_json()
|
34
|
+
self.transliteration_language_list = self.manager.get_transliteration_language_list_json()
|
35
|
+
self.tokenization_options = self.manager.get_tokenization_options_json()
|
36
|
+
break # If successful, break out of the retry loop
|
37
|
+
except requests.exceptions.RequestException as e:
|
38
|
+
if attempt < max_retries - 1: # If not the last attempt
|
39
|
+
logging.warning(f"Request failed. Retrying in {retry_delay} seconds... (Attempt {attempt + 1}/{max_retries})")
|
40
|
+
time.sleep(retry_delay)
|
41
|
+
else:
|
42
|
+
logging.error(f"Max retries reached. Unable to set up the test environment.")
|
43
|
+
raise # Re-raise the last exception if all retries failed
|
29
44
|
|
30
45
|
def test_language_list(self):
|
31
46
|
self.assertTrue(len(self.language_list) > 0)
|
@@ -140,7 +155,7 @@ class TestTranslation(unittest.TestCase):
|
|
140
155
|
self.assertRaises(cloudlanguagetools.errors.RequestError, self.translate_text, Service.Naver, 'Veuillez parler lentement.', Language.fr, Language.th, 'Please speak slowly.')
|
141
156
|
|
142
157
|
def test_translate_deepl(self):
|
143
|
-
# pytest test_translation.py -rPP -k test_translate_deepl
|
158
|
+
# pytest tests/test_translation.py -rPP -k test_translate_deepl
|
144
159
|
self.translate_text(Service.DeepL, 'Please speak slowly', Language.en, Language.fr, 'Veuillez parler lentement')
|
145
160
|
self.translate_text(Service.DeepL, 'Je ne suis pas intéressé.', Language.fr, Language.en, ["""I'm not interested.""", 'i am not interested'])
|
146
161
|
self.translate_text(Service.DeepL, '送外卖的人', Language.zh_cn, Language.en, ['delivery person', 'takeaway delivery people'])
|
@@ -1 +0,0 @@
|
|
1
|
-
KEYS='gAAAAABkk5vaZm3izjIqmeoWzO8UiDRp8Riz0F2BmB5_tFmcc2Fgu0W-MXvC7y4-7vfN5ggR0hgVRKJVC2UbyJpNBt3yDuJAfBoccZ1327aDb2KfXb69akWXWrTnIqHTpfl1XbpVIbgMk3MT777H_0cz88GM4cgVtH9WuMQC20wjr3AQgHEhWUlDowIdHPbyb_UX-bom3bEF0DfZ__EkYS7tIBmMOH_B6oSayUSibUG1SJc7xfGvJqW4zXUeESJtBujRqVvbr5z0zf46eicNAMiQXEm9IpYIRnrldsUID-6KGw4eUCYx32PPYjldGkvapelHnpAv5WqqHQ6M_p4lu95CbgWwfQIF1qLW68cOOiAZBgjHZrPbY0DLqT-eEfB_xrek0U7ZqlpOisIfc3Q8QfoyGaW-mSchB5GYqXfp38xLlPGI3OSYnJl08MuJ6p7M5cxRTRkjvTj8UimShfvmPm5ltM-XW43uIsuy5rAkrHIbsKCkiJ-tXK1eEIySR092cl0qpZ3U2ztySbzX1eXsgzynYVfGdD7V4tGNpTMPhekOTpFLNKw6swA3EK1tC_7aWWvnqec6ccew7nAPHKE00Li7trYpvZeBdLQvOdVzZj77TTLdBE39_syeB4EDZfV6WXFu8_DhAuNiPF2LJHFIMWCA6RiAo1HtPUC8l3IDU3uSBljyZ1QTGNUBg975SljdrgLWocm-Hmyfvq0WfnTk19JnXFAp_hiIsPtsulyLkuGLXXO3aUHl2xcGy6u9cQE3NxtslnSXy8IRnv9MqCae54NwzNGkWiCiN29pvNvEppuHG-riexmh9MhUvHVOV68kwuB5Et2gm9kFgpKJlV0ZkUWDdJUZDYrgQ2oQ7TmBhez43duyJZ2k2uYHaZ8MFFcFxjgT-WkvfkI8jIUZ0ZIwZMKjcM5OCNLPiL-EbxVO2grithjZ_bCbnpxsahKLvWA4QYVy-3eWhhqutmNpsFIig05No1I4tXNQmcxJcFANGIG2p73DbgBIfDrJToxYIPw_bX2Ii93cKX0FNhzv1be0uUP3RZcuni_ESYnvrXgdNFZ_fIf-lci6lZcWiITFxejDFe1pqb9rd_BLEyJNIFfkvFou_eC8wWT0G-h8yPckuVCYlOEWAPfhL5BjFxQhFvnwyTm4vNHBLnhoOhsURc1NTwJ9EL1_ksYLCktLnlFjjg21Z-93aV4G6T6X2bwIdFpqebo5G-SIz-xKVo7Wln105uzUr2CJEliaufNOWZC6by2N2HJ2jqsajiJX2ZNOJCjkYtjH_-jln9taKF3Q5BFSlOeraam-ppZkBsABMewrUZ8d7lqqdyN7ntDHWiASjFm11KL_25hRT6tMhx4BpKlP40ovfY7XoJhRT-_Ss7Aeze6jBj-Qe58dRDEiIzZcko2F1SEvIZFNVPbN3FuEjhlrOP9N1IvhCYn_8NjXENetbjIVhkQx5nKGaLVeT9toRc4qwhg-4XdN620TnIxPusva9ncXUYXVz1iDVFtN5-RuJRAppuBj7_nfJOG8MngntJOV7B0o6fVDK8McVi3TQ9lyCl1GMPuoeB41xCKyY3Lc-lqzgXhRhiWbuSBg1G45jQ3BE8bWciFjQCXuMeGXmDZzEFtzJMPQWwxzDkuOB2Y74ITEzsbleSvFXhlgUpa692ckDK1z_mK2UPrLbAa629GzPsrdjVGzQ6vWiYiKkuJFHQUkxpBscIH7y9rPW6zc0jg7tVmYwuV94UCS61g2g-s1-UodQog9vcD4JGyPgr2AcetoBzSh-wAsBBzra3A_E-cWdD2uzB5Mv60hgOlInLbi8QUBppCig89IwnFLdXAP7QFamWtijflUpqRECMvokxaImhtKSwPy3BlyJAMr7T3lFC3XY74fA-3k2wKAC_0LLYxedED68-GtklIpfgSwQ9e5oxEzv927oF_uEibZizjJusNtnk4Wz76YMd-VHq-wPJcxFcRqkKdZCgyToLY2z4YP81tWVxB_9F5aR_yF9cvCPbAycRAxny9Qsno44VPjZ5v7zLj5G7xqhEza11EzrUdPk657s_a1M6G8ARehCblBChTUD6Gxz1Y2-dZ0ejpB81kGFqICPEw24dF1xq44GAwNJsx_zYd_0y5GldZSMCYpmLbJNDFx1wGOJwLQq9h0JaqRT5jAYl3uacpmApYLj8jMCdn1HAdJwKSIw-s17RyEZHBg4JHC3tAMzZhhco5k7gHLCCRI_jep9fAusZTjo3mdaJD_tJfAZsoQXx4kq9HJ8k71nyxYz18pwzIfT0hJgFsSbS-UyrhAk8mskTcw5UPKGcgzMOwafh0Giy69Bn4KfU_nHUDeHng_b-KCN7G_fF43xX7l3EB3qIyN9vUtPYrHteJt0LYS0yXtUM_Dc0jxeEdZwcpKYxbkMlTbK1DM6JZ2JitNGAPDvNv8ZjRQxPmKjjgVdvs8OzJZ4kdPzAfEVBvhYx3gKmggcnsQBgxFki3FOhXhPrCAyxXO5-_glom5KzEGqG2VQgJWU3ycCtmFypoNmx38wyd3eH7_M2ydfkIE6e350g8y7sFayK6vqO4GdANxTRbHThF7HfI-3nWtPJ4NpILiu71CtpuaU5MAhqD8qAJjQkUUifANeY6i-aqQZXBPDWw7VaYpQWGAoiPBuFJc-a906yU-FzViRToZIyBbunOgJO0GOy4iJRJSnAQY3Cd3Xrk7VenUnBuYBLpUUNiVCZw6MnWTAhY1BujnDf-Pxrj8jJYf2Y0KipSxkCK4Hkjnc13bTkBovA8YM7HECTIVkkIlsjvV440eyqLpO1r4yD_lecvExcSNsF-EBwSbp0hRps3wTynY0yeuWPM3NTQ4dEmO6fW1ewpsBKicDu0sHWAhK_WOpYiydTT3rAGSgDolNSKKYUpFlHS92g5-Nb_nLW4qwOV5F8yDKbfYtaBKylCNrmDu8aXc5GIdIeK7eGC5JF8uaUCsidPOh7JuOpn-hhKym4Aq6Hb5FlnLf9eNkimCYDm3F1a91CcJwhPRN_FZNLVfpBqw8pOfFK9RkBEttStWxvgWvW-C5K5EzTiBGlZBR2j4LHazDJR8_bjJ6uBCw-8fFQsPWGgliic4_k1ioRVwhJDNfw6VN0mR6iqt9D7duTxU8nuNC3u5idCJuwDC4aJC9vgvpbU2TledADfaqeBujUn8DhkOEhGJsE3WEcwaidsqI3NFSKjtoxiDEOfWmXVmFAjDo_tENvjRnriFPrWR5vMvmluAlncg16ok0qqpLBpgnGYDYhfv4IC3HsYfSwmyCDrmoMUBqv5dUtbEBMbQB1BcWH1hOahCN60AO3Emedq2jsM853UI6_EuZA19TIhLreNMpEL1sSWbB3cVlLfGEvoSzga1MdtKsxCZmBIWa2_Eez9lBtdpuqG4phfpFs5sM6mWKza0ZLr8v5N4-oIRqApAAr2JNscXeehiZPhWxjaXmA_69N0mdmzkjwznH6sQ3bkX7b9UWOQPblEwPfk4iXktxiIOBGxDh0SgWMpEvtEO9MxFnOYoIsmeyTVNOG73iFKQ6gybMoAiCFBeGSq24scq1Tf8ydBkpaM7Z3SzNj3b4u5feRTb1dZCVsjQ3kCP657INEpRWx6GfVAZBH2-GGVuDNrr7-YY7XcLees9h5jSI5JdelsuFwfI8U1D3gH2F_gZenLaR5RlFpH-yma-XPOuCpbflzs-MoEO8A55L2zs6EZTxcjEGt8v-J8rKEIPt8sr0u80YklSYs0m5q8AwDjgzwRG9UPoaZJYclGo4U5N_P-fixUtF2cr7rHWxDZLzuK7jF8KNUW8pWRgYn5Zn6BfKN8ro-49xbqnDQ7CORKEiFxjcSUP4D0ocyFIhHf3V4YiyS860KHqbhkyRm4d765skBe5DqrJn2EeGwh_ehBMw30uqYai-ws1920xFGPYCGuHC2uY5cgY_8QF1HWsDsr-Ei4jtsE24_AA8oJTx2wIbchQIu7oJl9gElYbIRXaoHHyuaFIkxpV4CMpTXtiWTwIjQeCBbQmESnj9s5vBdKvqbqRDZQ0LigkqwCoO8nizJ8kNse601UN5Nci9UkH5vcOUmmc1RCj8kQLiaCWMTQ_lT3bzGXgUxRgUdrgVyKYbSSigWZ98WzT_YjsbjLufIY5uz1ix8e_mz7JK2MxFywSt_JJ2O-ry6NNVOUHig10XJBUEIj2hE4X7ziecWMAH93axfiwk9GxLy_vFxA5Nl0KbZGbvwixN4rrAH1yFChh83vF819kjhe0H4oCsGWvxq3OrxdVSbuF95p6lhxhpcy0hPM4rEQ5EcM3Gd_iFUxuZMBgZzvnjDgvvwGbX97Sx6WjIqEfNHFLbvID1nmUMXROPoMmnr_P14QptBvDCgE1SxDhavEOr8EMTxxzyrBEFeKwZ2Sw8QukuOceDMpwmR5ksAk0QiiWgvdzu_LkgUgayKhp8hQMmtt0wsTlmizkG5tATqGCri1dENCcKM5TqCqd3FEAPlm07VqPJrjwq2OHt5xR90HF8uizO9pdBXyY5_M5asChPsUyeBm5dYLfhl7JKLwVdj8wcZLsVojlunIUKppFdOSx9RW_jzqa20jvq1i7-f43kcMsjzuklAY4a3QoTh9gYOrOxzecko6lij6W0jVC0EuplsbUi75rwNzYVjRV1s_3Ze96S9Ym-dNf3murhzPTOvCL1v3Ugb0vtW5cEK0MwMEM7jN-K1GNprILjVMky15f5hiJEKlmsNuvILi6-F911Zu3s6Rl_a2P6-7zWa7crlrgsrCUoCmGWLpVW6fsscfGyBgSp74NV1qOz_062V9Fg7-LBcYWgR3VILw3VbFQ1ZvLggLuEpxbxwOIWgb_jhWjo5wbQ9pdGziHY1bR7ejh3n_Dc1YwfIKEo0yNG6nnLchcThTggRZhLozHpx-ElamXM7hMgotm1XUbjLlPEgDHfAQjazFPI0h_i_4N2MiJ_OSXvF3IN2U8nKivvfcCn3lVrWLzzHtiAxBoSOfGrwD6S46Um1cSV0_cRCJQAvbUsn2iT8CNVbOli6xzCceWkUdVjpZYhklwV8SMs839PTNN2CU9Poxm2t2PIeQcT_A-KFBqC6MYwGy_wLO_uibXHY1oxcL8FKl2JUD7JbXEWItFydDLQVz3KpumP43D_zJ3pK7kin5sA3CX6ultD94PKZegIpe1gc2SUh2bte31HwGZh96IuFNoEuYVumlSMrJhtpOvpKC_ScSW6uw_f1WHCnBotYXWX_kkmmig6H-Dzv4zOgLmMcSE_R8QhYc3XrUXL_0lpl85xsJHv2sWx-evCWhGeIF-LkYmP8mwCHXkfcigbJV5bPVX8XRXx6X4_ZKSTKmKmsyRaBLmxpWtg6bZRi8D2d1Oz9sNTSISWxJlChqcL5VVqKN2EAaGAupGqmRvn7dPt5JQqGARx14PGQGOcHygif0eCrGv3x78sG2Vi_mNZKbBszJNu-yePTDStZ_LPjKya87DEyIghwe-S4fFrx8X-oSz6PYd26bhCUfYfkPfsSShcfJ1aHXCY6b9In8EXd0-G5um6Ng0eIYlc6Y0Mr--cpc0xXEubTslRvUhwLumZ59Lr-4oN75NFra427iErxLrzhhFCSbcVc7mh-qYa-z_6mQqdbUfiPVwfE2iDEtGiz8-keZUfEysxh_nkKQSJO-dmie8031UpF2TEJTMQ4onnyKNmLLSFltmqT5xa6CRpHXPrhuoCx7J-lH9L5_qbOFiCNwNhMtEKuhMp-CBRQsK3KPLtzKx01haclk4uR36_fXLdLkY3GPG2jrkEoc3sWIlibQA5EtcNAT5Y7vVHVm1hW_P9xVOjGe_uokHLdln9rAOZayJkJid1hqikXjQ2uYdpk9rSNtxeWw-9YB6fGL0o3IFL01MngtMvSfvwXSWK2fbWkPEzSgPv8qpqQuDa8QzIh5IQ_r5IH7jHBY1zwn4IPhb6qxU7WvCmQ0zD0mt3b2XxNsL5qNbmGM0L63Pc-6ICwN5HMIxzo7T5rVzdqZn1sWu00T6pRQzzOXfhJIqS_WvmZe8X1dcTUo9mdrSjjYj5F5GR9k4M6MDih3M6ndfKJWUdE1-l7eD6BvES5faIuDicoU0HCOrU8oK82v7NApultvIu9LkZTBNQc2mv2Tc7nNx0jjoO1N3Kw4LPaFJxfSp3xEEn0eL1R7c-bKmVCCOT1rtbbJx2QCB_LWXqJnQtgZbX7x72jTokiqooKQTBlEc_YiqV4GTDCHQL-mW6XDm96YzhYhY6RxCfcABDx6Ot22uqZQ1g9gbLXV1raTUXC2zowuV49ehW8RWKDq6vVR94l9doLLjhO29cGk5LuQ2oYLA8pO9dDs6HMWtILj5msrcKYyjWR7hYQnnEyG4'
|
File without changes
|
File without changes
|
File without changes
|
{cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/argostranslate.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/dictionarylookup.py
RENAMED
File without changes
|
{cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/easypronunciation.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/libretranslate.py
RENAMED
File without changes
|
{cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/mandarincantonese.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/servicemanager.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools/translationlanguage.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools.egg-info/requires.txt
RENAMED
File without changes
|
{cloudlanguagetools-11.3.4 → cloudlanguagetools-11.4.0}/cloudlanguagetools.egg-info/top_level.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|