├── .gitignore
├── BingTTSGen.py
├── README.md
├── bingtts
└── __init__.py
├── freeswitch
└── tts_commandline.conf.xml
├── setup.cfg
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/settings.json
2 | *.pyc
--------------------------------------------------------------------------------
/BingTTSGen.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | from __future__ import print_function
4 | import hashlib
5 | import os
6 | import sys
7 | import argparse
8 | import shutil
9 |
10 | # Define a few maps for reference
11 | namemap = {
12 | "ar-EG" : ["Hoda"],
13 | "ar-SA" : ["Naayf"],
14 | "bg-BG" : ["Ivan"],
15 | "ca-ES" : ["HerenaRUS"],
16 | "cs-CZ" : ["Jakub"],
17 | "da-DK" : ["HelleRUS"],
18 | "de-AT" : ["Michael"],
19 | "de-CH" : ["Karsten"],
20 | "de-DE" : ["Hedda", "HeddaRUS", "Stefan, Apollo"],
21 | "el-GR" : ["Stefanos"],
22 | "en-AU" : ["Catherine", "HayleyRUS"],
23 | "en-CA" : ["Linda", "HeatherRUS"],
24 | "en-GB" : ["Susan, Apollo", "HazelRUS", "George, Apollo"],
25 | "en-IE" : ["Sean"],
26 | "en-IN" : ["Heera, Apollo", "PriyaRUS", "Ravi, Apollo"],
27 | "en-US" : ["ZiraRUS", "JessaRUS", "BenjaminRUS"],
28 | "es-ES" : ["Laura, Apollo", "HelenaRUS", "Pablo, Apollo"],
29 | "es-MX" : ["HildaRUS", "Raul, Apollo"],
30 | "fi-FI" : ["HeidiRUS"],
31 | "fr-CA" : ["Caroline", "HarmonieRUS"],
32 | "fr-CH" : ["Guillaume"],
33 | "fr-FR" : ["Julie, Apollo", "HortenseRUS", "Paul, Apollo"],
34 | "he-IL" : ["Asaf"],
35 | "hi-IN" : ["Kalpana, Apollo", "Kalpana", "Hemant"],
36 | "hr-HR" : ["Matej"],
37 | "hu-HU" : ["Szabolcs"],
38 | "id-ID" : ["Andika"],
39 | "it-IT" : ["Cosimo, Apollo","LuciaRUS"],
40 | "ja-JP" : ["Ayumi, Apollo", "Ichiro, Apollo", "HarukaRUS"],
41 | "ko-KR" : ["HeamiRUS"],
42 | "ms-MY" : ["Rizwan"],
43 | "nb-NO" : ["HuldaRUS"],
44 | "nl-NL" : ["HannaRUS"],
45 | "pl-PL" : ["PaulinaRUS"],
46 | "pt-BR" : ["HeloisaRUS", "Daniel, Apollo"],
47 | "pt-PT" : ["HeliaRUS"],
48 | "ro-RO" : ["Andrei"],
49 | "ru-RU" : ["Irina, Apollo", "Pavel, Apollo","EkaterinaRUS"],
50 | "sk-SK" : ["Filip"],
51 | "sl-SI" : ["Lado"],
52 | "sv-SE" : ["HedvigRUS"],
53 | "ta-IN" : ["Valluvar"],
54 | "th-TH" : ["Pattara"],
55 | "tr-TR" : ["SedaRUS"],
56 | "vi-VN" : ["An"],
57 | "zh-CN" : ["HuihuiRUS", "Yaoyao, Apollo", "Kangkang, Apollo"],
58 | "zh-HK" : ["Tracy, Apollo", "TracyRUS", "Danny, Apollo"],
59 | "zh-TW" : ["Yating, Apollo", "HanHanRUS", "Zhiwei, Apollo"]
60 | }
61 |
62 | formatmap = {
63 | "raw-8khz-8bit-mono-mulaw" : None,
64 | "raw-16khz-16bit-mono-pcm" : None,
65 | "riff-8khz-8bit-mono-mulaw" : None,
66 | "riff-16khz-16bit-mono-pcm" : None
67 | }
68 |
69 | # Get config from CLI args
70 | i = 0
71 | argsDict = {}
72 | for item in sys.argv:
73 | if i == 0:
74 | i = i + 1
75 | pass
76 | else:
77 | i = i + 1
78 | paramname, paramval = item.partition(
79 | "="
80 | )[::2]
81 | argsDict[paramname] = paramval
82 |
83 | try:
84 | helpReq = argsDict['--help']
85 | print ("")
86 | print (
87 | "Usage: {} --cache=/path/to/cache --dest=/path/to/destination --lang=en-US --voice=\"ZiraUS\" --fileformat=riff-8khz-8bit-mono-mulaw --apikey=YOUR-API-KEY --text=\"Hello World\"".format(
88 | sys.argv[0]
89 | )
90 | )
91 | print ("")
92 | print ("--cache : Location of file cache. Useful if you repeatedly request")
93 | print (" the same text to speech and wish to conserve bandwidth")
94 | print (" and reduce Azure costs. Must be writeable.")
95 | print ("--dest : Location for the output file. Must be writeable.")
96 | print ("--lang : Language to be spoken. Case sensitive.")
97 | print ("--voice : Voice to be used. Case sensitive.")
98 | print ("--fileformat : Format of the output file.")
99 | print ("--apikey : API key generated by Azure.")
100 | print ("--text : Text to convert to speech. MUST be enclosed in double quotes.")
101 | print ("")
102 | print ("Available language combinations (--lang --voice):")
103 | for item, value in namemap.items():
104 | for voiceval in value:
105 | print (
106 | "Language: {} Voice: {}".format(
107 | item,
108 | voiceval
109 | )
110 | )
111 | print ("")
112 | print ("Available file formats:")
113 | for item, value in formatmap.items():
114 | print (item)
115 | sys.exit(1)
116 | except:
117 | pass
118 |
119 |
120 |
121 | try:
122 | destLoc = argsDict['--dest']
123 | except:
124 | print ("")
125 | print ("Error: destination location not specified.")
126 | print ("")
127 | print (
128 | "Usage: {} --cache=/path/to/cache --dest=/path/to/destination --lang=en-US --voice=\"ZiraUS\" --fileformat=riff-8khz-8bit-mono-mulaw --apikey=YOUR-API-KEY --text=\"Hello World\"".format(
129 | sys.argv[0]
130 | )
131 | )
132 | print ("")
133 | sys.exit(1)
134 |
135 | try:
136 | ttsLang = argsDict['--lang']
137 | except:
138 | print ("")
139 | print ("Error: language not specified.")
140 | print ("")
141 | print (
142 | "Usage: {} --cache=/path/to/cache --dest=/path/to/destination --lang=en-US --voice=\"ZiraUS\" --fileformat=riff-8khz-8bit-mono-mulaw --apikey=YOUR-API-KEY --text=\"Hello World\"".format(
143 | sys.argv[0]
144 | )
145 | )
146 | print ("")
147 | sys.exit(1)
148 |
149 | try:
150 | ttsVoice = argsDict['--voice']
151 | except:
152 | print ("")
153 | print ("Error: voice not specified.")
154 | print ("")
155 | print (
156 | "Usage: {} --cache=/path/to/cache --dest=/path/to/destination --lang=en-US --voice=\"ZiraUS\" --fileformat=riff-8khz-8bit-mono-mulaw --apikey=YOUR-API-KEY --text=\"Hello World\"".format(
157 | sys.argv[0]
158 | )
159 | )
160 | print ("")
161 | sys.exit(1)
162 |
163 | try:
164 | ttsFormat = argsDict['--fileformat']
165 | except:
166 | print ("")
167 | print ("Error: file format not specified.")
168 | print ("")
169 | print (
170 | "Usage: {} --cache=/path/to/cache --dest=/path/to/destination --lang=en-US --voice=\"ZiraUS\" --fileformat=riff-8khz-8bit-mono-mulaw --apikey=YOUR-API-KEY --text=\"Hello World\"".format(
171 | sys.argv[0]
172 | )
173 | )
174 | print ("")
175 | sys.exit(1)
176 |
177 | try:
178 | ttsText = argsDict['--text']
179 | except:
180 | print ("")
181 | print ("Error: text not specified.")
182 | print ("")
183 | print (
184 | "Usage: {} --cache=/path/to/cache --dest=/path/to/destination --lang=en-US --voice=\"ZiraUS\" --fileformat=riff-8khz-8bit-mono-mulaw --apikey=YOUR-API-KEY --text=\"Hello World\"".format(
185 | sys.argv[0]
186 | )
187 | )
188 | print ("")
189 | sys.exit(1)
190 |
191 | try:
192 | ttsApiKey = argsDict['--apikey']
193 | except:
194 | print ("")
195 | print ("Error: API key not specified.")
196 | print ("")
197 | print (
198 | "Usage: {} --cache=/path/to/cache --dest=/path/to/destination --lang=en-US --voice=\"ZiraUS\" --fileformat=riff-8khz-8bit-mono-mulaw --apikey=YOUR-API-KEY --text=\"Hello World\"".format(
199 | sys.argv[0]
200 | )
201 | )
202 | print ("")
203 | sys.exit(1)
204 |
205 | try:
206 | ttsCache = argsDict['--cache']
207 | except:
208 | ttsCache = None
209 |
210 | if ttsVoice not in namemap[ttsLang]:
211 | print ("")
212 | print ("Error: invalid language or Voice specified, or invalid combination: The following language/Voice combinations are available:")
213 | print ("")
214 | for item, value in namemap.items():
215 | for voiceval in value:
216 | print (
217 | "Language: {}".format(
218 | item
219 | )
220 | )
221 | print (
222 | "Language: {} Voice: {}".format(
223 | item, voiceval
224 | )
225 | )
226 | sys.exit(1)
227 |
228 | try:
229 | formatname = formatmap[ttsFormat]
230 | except:
231 | print ("")
232 | print ("Error: invalid format specified: The following formats are available:")
233 | print ("")
234 | for item, value in formatmap.items():
235 | print (item)
236 | sys.exit(1)
237 |
238 | if ttsCache:
239 | cacheaccess = os.access(ttsCache, os.W_OK)
240 | if not cacheaccess:
241 | print ("Cannot write to cache location, ignoring --cache setting...")
242 | ttsCache = None
243 |
244 | m = hashlib.md5()
245 | # Hash lang+Voice+text
246 | m.update(
247 | (
248 | "{}-{}-{}".format(
249 | ttsLang,
250 | ttsVoice,
251 | ttsText
252 | )
253 | ).encode(
254 | 'utf-8'
255 | )
256 | )
257 | # create filename base on MD5 hash
258 | filename = "{}.wav".format(
259 | m.hexdigest()
260 | )
261 | if ttsCache:
262 | # If our file already exists, just return it so we don't have to do an API call...
263 | if os.path.isfile(os.path.join(ttsCache, filename)):
264 | try:
265 | shutil.copy2(
266 | os.path.join(
267 | ttsCache,
268 | filename
269 | ),
270 | destLoc
271 | )
272 | sys.exit(0)
273 | except (Exception) as e:
274 | print (
275 | "Could not copy cached file: {}".format(
276 | e
277 | )
278 | )
279 | print ("Exiting...")
280 | sys.exit(1)
281 |
282 | # Wait to import this until after we check cache to keep things as speedy as possible...
283 | from bingtts import Translator
284 | translator = Translator(ttsApiKey)
285 | try:
286 | data = translator.speak(
287 | ttsText.encode(
288 | 'utf-8'
289 | ).decode(
290 | 'latin-1'
291 | ),
292 | ttsLang,
293 | ttsVoice,
294 | ttsFormat
295 | )
296 | except (Exception) as e:
297 | print (
298 | "Error retrieving speech file: {}".format(
299 | e
300 | )
301 | )
302 | sys.exit(1)
303 |
304 | if ttsCache:
305 | try:
306 | with open(destLoc, 'wb') as f:
307 | f.write(data)
308 | except (Exception) as e:
309 | print (
310 | "Couldn't write to file {}: {}".format(
311 | destLoc,
312 | e
313 | )
314 | )
315 | sys.exit(1)
316 | try:
317 | with open(os.path.join(ttsCache, filename), 'w') as f:
318 | f.write(data)
319 | except (Exception) as e:
320 | cacheFileLoc = os.path.join(
321 | ttsCache,
322 | filename
323 | )
324 | print (
325 | "Couldn't write to file {}: {}".format(
326 | cacheFileLoc,
327 | e
328 | )
329 | )
330 | sys.exit(1)
331 | else:
332 | try:
333 | with open(destLoc, 'wb') as f:
334 | f.write(data)
335 | except (Exception) as e:
336 | print (
337 | "Couldn't write to file {}: {}".format(
338 | destLoc,
339 | e
340 | )
341 | )
342 | sys.exit(1)
343 |
344 | sys.exit(0)
345 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Python-Bing-TTS
2 | Microsoft Bing Text to Speech library for Python
3 |
4 | # Installation
5 | To install using pip, run the following command:
6 |
7 | pip install git+https://github.com/westparkcom/Python-Bing-TTS.git
8 |
9 | To install using pipenv, run the following command:
10 |
11 | pipenv install git+https://github.com/westparkcom/Python-Bing-TTS.git#egg=bingtts
12 | # Usage
13 | The following is the usage of the library
14 |
15 | translator.speak(text, language, voice, fileformat)
16 |
17 | Variable | Description | Note
18 | --- | --- | ---
19 | text | The text that you wish to convert to speech |
20 | language | The language/country you wish to hear the speech in | Case sensitive. See [Bing TTS API Reference](https://docs.microsoft.com/en-us/azure/cognitive-services/speech/api-reference-rest/bingvoiceoutput#SupLocales) for list
21 | voice | The name of the voice to use | Case sensitive
22 | fileformat | File format to encode the speech to | See [Bing TTS API Reference](https://docs.microsoft.com/en-us/azure/cognitive-services/speech/api-reference-rest/bingvoiceoutput#Http) for list of formats
23 |
24 | # Example
25 | from bingtts import Translator
26 | translator = Translator('YOUR-API-KEY-HERE')
27 | output = translator.speak("This is a text to speech translation", "en-US", "JessaRUS", "riff-16khz-16bit-mono-pcm")
28 | with open("file.wav", "wb") as f:
29 | f.write(output)
30 |
--------------------------------------------------------------------------------
/bingtts/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | __init__
4 |
5 | A library to get text to speech from micrsoft translation engine.
6 |
7 | See: https://www.microsoft.com/cognitive-services/en-us/speech-api/documentation/api-reference-rest/bingvoiceoutput
8 |
9 | """
10 |
11 | try:
12 | import simplejson as json
13 | except ImportError:
14 | import json
15 | import logging
16 |
17 | try:
18 | import httplib
19 | except ImportError:
20 | import http.client as httplib
21 |
22 |
23 | class BadRequestException(Exception):
24 | def __init__(self, message):
25 | self.message = "{} {}".format(
26 | message.status,
27 | message.reason
28 | )
29 | super(
30 | BadRequestException,
31 | self
32 | ).__init__(
33 | self.message
34 | )
35 |
36 | class AuthException(Exception):
37 | def __init__(self, message):
38 | self.message = "{} {}".format(
39 | message.status,
40 | message.reason
41 | )
42 | super(
43 | AuthException,
44 | self
45 | ).__init__(
46 | self.message
47 | )
48 |
49 | class LanguageException(Exception):
50 | def __init__(self, message):
51 | self.message = "{}".format(
52 | message
53 | )
54 | super(
55 | LanguageException,
56 | self
57 | ).__init__(
58 | self.message
59 | )
60 |
61 |
62 | class Translator(object):
63 | """
64 | Implements API for the Microsoft Translator service
65 | """
66 | auth_host = 'api.cognitive.microsoft.com'
67 | auth_path = '/sts/v1.0/issueToken'
68 | base_host = 'speech.platform.bing.com'
69 | base_path = ''
70 | def __init__(self, client_secret, debug=False):
71 | """
72 | :param clien_secret: The API key provided by Azure
73 | :param debug: If true, the logging level will be set to debug
74 | """
75 | self.client_secret = client_secret
76 | self.debug = debug
77 | self.logger = logging.getLogger(
78 | "bingtts"
79 | )
80 | self.access_token = None
81 | if self.debug:
82 | self.logger.setLevel(
83 | level=logging.DEBUG
84 | )
85 |
86 | def get_access_token(self):
87 | """
88 | Retrieve access token from Azure.
89 |
90 | :return: Text of the access token to be used with requests
91 | """
92 | headers={
93 | 'Ocp-Apim-Subscription-Key' : self.client_secret
94 | }
95 | conn = httplib.HTTPSConnection(
96 | self.auth_host
97 | )
98 | conn.request(
99 | method="POST",
100 | url=self.auth_path,
101 | headers=headers,
102 | body=""
103 | )
104 | response = conn.getresponse()
105 | if int(response.status) != 200:
106 | raise AuthException(
107 | response
108 | )
109 | return response.read()
110 |
111 | def call(self, headerfields, path, body):
112 | """
113 | Calls Bing API and retrieved audio
114 |
115 | :param headerfields: Dictionary of all headers to be sent
116 | :param path: URL path to be appended to requests
117 | :param body: Content body to be posted
118 | """
119 |
120 | # If we don't have an access token, get one
121 | if not self.access_token:
122 | self.access_token = self.get_access_token()
123 |
124 | # Set authorization header to token we just retrieved
125 | headerfields["Authorization"] = "Bearer {}".format(
126 | self.access_token.decode(
127 | 'utf-8'
128 | )
129 | )
130 | # Post to Bing API
131 | urlpath = "/".join(
132 | [
133 | self.base_path,
134 | path
135 | ]
136 | )
137 | conn = httplib.HTTPSConnection(
138 | self.base_host
139 | )
140 | conn.request(
141 | method="POST",
142 | url=urlpath,
143 | headers=headerfields,
144 | body=body.encode('utf-8')
145 | )
146 | resp = conn.getresponse()
147 | # If token was expired, get a new one and try again
148 | if int(resp.status) == 401:
149 | self.access_token = None
150 | return self.call(
151 | headerfields,
152 | path,
153 | body
154 | )
155 |
156 | # Bad data or problem, raise exception
157 | if int(resp.status) != 200:
158 | raise BadRequestException(
159 | resp
160 | )
161 |
162 | return resp.read()
163 |
164 | def speak(self, text, lang, voice, fileformat):
165 | """
166 | Gather parameters and call.
167 |
168 | :param text: Text to be sent to Bing TTS API to be
169 | converted to speech
170 | :param lang: Language to be spoken
171 | :param voice: Voice of the speaker
172 | :param fileformat: File format (see link below)
173 |
174 | Name maps and file format specifications can be found here:
175 | https://docs.microsoft.com/en-us/azure/cognitive-services/speech/api-reference-rest/bingvoiceoutput
176 | """
177 |
178 | namemap = {
179 | "ar-EG" : ["Hoda"],
180 | "ar-SA" : ["Naayf"],
181 | "bg-BG" : ["Ivan"],
182 | "ca-ES" : ["HerenaRUS"],
183 | "cs-CZ" : ["Jakub"],
184 | "da-DK" : ["HelleRUS"],
185 | "de-AT" : ["Michael"],
186 | "de-CH" : ["Karsten"],
187 | "de-DE" : ["Hedda", "HeddaRUS", "Stefan, Apollo"],
188 | "el-GR" : ["Stefanos"],
189 | "en-AU" : ["Catherine", "HayleyRUS"],
190 | "en-CA" : ["Linda", "HeatherRUS"],
191 | "en-GB" : ["Susan, Apollo", "HazelRUS", "George, Apollo"],
192 | "en-IE" : ["Sean"],
193 | "en-IN" : ["Heera, Apollo", "PriyaRUS", "Ravi, Apollo"],
194 | "en-US" : ["ZiraRUS", "JessaRUS", "BenjaminRUS"],
195 | "es-ES" : ["Laura, Apollo", "HelenaRUS", "Pablo, Apollo"],
196 | "es-MX" : ["HildaRUS", "Raul, Apollo"],
197 | "fi-FI" : ["HeidiRUS"],
198 | "fr-CA" : ["Caroline", "HarmonieRUS"],
199 | "fr-CH" : ["Guillaume"],
200 | "fr-FR" : ["Julie, Apollo", "HortenseRUS", "Paul, Apollo"],
201 | "he-IL" : ["Asaf"],
202 | "hi-IN" : ["Kalpana, Apollo", "Kalpana", "Hemant"],
203 | "hr-HR" : ["Matej"],
204 | "hu-HU" : ["Szabolcs"],
205 | "id-ID" : ["Andika"],
206 | "it-IT" : ["Cosimo, Apollo","LuciaRUS"],
207 | "ja-JP" : ["Ayumi, Apollo", "Ichiro, Apollo", "HarukaRUS"],
208 | "ko-KR" : ["HeamiRUS"],
209 | "ms-MY" : ["Rizwan"],
210 | "nb-NO" : ["HuldaRUS"],
211 | "nl-NL" : ["HannaRUS"],
212 | "pl-PL" : ["PaulinaRUS"],
213 | "pt-BR" : ["HeloisaRUS", "Daniel, Apollo"],
214 | "pt-PT" : ["HeliaRUS"],
215 | "ro-RO" : ["Andrei"],
216 | "ru-RU" : ["Irina, Apollo", "Pavel, Apollo","EkaterinaRUS"],
217 | "sk-SK" : ["Filip"],
218 | "sl-SI" : ["Lado"],
219 | "sv-SE" : ["HedvigRUS"],
220 | "ta-IN" : ["Valluvar"],
221 | "th-TH" : ["Pattara"],
222 | "tr-TR" : ["SedaRUS"],
223 | "vi-VN" : ["An"],
224 | "zh-CN" : ["HuihuiRUS", "Yaoyao, Apollo", "Kangkang, Apollo"],
225 | "zh-HK" : ["Tracy, Apollo", "TracyRUS", "Danny, Apollo"],
226 | "zh-TW" : ["Yating, Apollo", "HanHanRUS", "Zhiwei, Apollo"]
227 | }
228 | if not text:
229 | raise LanguageException(
230 | "Text to convert is not defined!"
231 | )
232 | if not voice and not lang:
233 | # Default to English voice if nothing is defined
234 | voice = 'ZiraRUS'
235 | lang = 'en-US'
236 | if voice and not lang:
237 | raise LanguageException(
238 | "Voice defined witout defining language!"
239 | )
240 | if lang not in namemap:
241 | raise LanguageException(
242 | "Requested language {} not available!".format(
243 | lang
244 | )
245 | )
246 | if lang and not voice:
247 | # Default to first voice in array
248 | voice = namemap[lang][0]
249 | if voice not in namemap[lang]:
250 | raise LanguageException(
251 | "Requested language {} does not have voice {}!".format(
252 | lang,
253 | voice
254 | )
255 | )
256 | if not fileformat:
257 | fileformat = 'riff-8khz-8bit-mono-mulaw'
258 | # Set the service name sent to Bing TTS
259 | servicename = "Microsoft Server Speech Text to Speech Voice ({}, {})".format(
260 | lang,
261 | voice
262 | )
263 |
264 | headers = {
265 | "Content-type" : "application/ssml+xml",
266 | "X-Microsoft-OutputFormat" : fileformat,
267 | "X-Search-AppId" : "07D3234E49CE426DAA29772419F436CA",
268 | "X-Search-ClientID" : "1ECFAE91408841A480F00935DC390960",
269 | "User-Agent" : "TTSForPython"
270 | }
271 |
272 | body = "{}".format(
273 | lang,
274 | lang,
275 | voice,
276 | servicename,
277 | text
278 | )
279 |
280 | return self.call(headers, "synthesize", body)
281 |
--------------------------------------------------------------------------------
/freeswitch/tts_commandline.conf.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.md
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from distutils.core import setup
2 |
3 | setup(
4 | name='bingtts',
5 | packages=['bingtts'],
6 | install_requires=[
7 | ],
8 | version='0.1.6',
9 | description='Python library to access Microsoft Bing Text to Speech API',
10 | author='jpattWPC',
11 | author_email='jpatten@westparkcom.net',
12 | url='https://github.com/westparkcom/Python-Bing-TTS',
13 | download_url='https://github.com/westparkcom/Python-Bing-TTS/tarball/master',
14 | keywords=['microsoft', 'bing', 'text to speech', 'cognitive'],
15 | classifiers=[],
16 | )
17 |
--------------------------------------------------------------------------------