webscout 8.3.5__py3-none-any.whl → 8.3.6__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.

Potentially problematic release.


This version of webscout might be problematic. Click here for more details.

Files changed (63) hide show
  1. webscout/Bard.py +12 -6
  2. webscout/DWEBS.py +66 -57
  3. webscout/Provider/{UNFINISHED → AISEARCH}/PERPLEXED_search.py +34 -74
  4. webscout/Provider/AISEARCH/__init__.py +1 -1
  5. webscout/Provider/Deepinfra.py +6 -0
  6. webscout/Provider/Flowith.py +6 -1
  7. webscout/Provider/GithubChat.py +1 -0
  8. webscout/Provider/GptOss.py +207 -0
  9. webscout/Provider/Kimi.py +445 -0
  10. webscout/Provider/Netwrck.py +3 -6
  11. webscout/Provider/OPENAI/README.md +2 -1
  12. webscout/Provider/OPENAI/TogetherAI.py +50 -55
  13. webscout/Provider/OPENAI/__init__.py +4 -2
  14. webscout/Provider/OPENAI/copilot.py +20 -4
  15. webscout/Provider/OPENAI/deepinfra.py +6 -0
  16. webscout/Provider/OPENAI/e2b.py +60 -8
  17. webscout/Provider/OPENAI/flowith.py +4 -3
  18. webscout/Provider/OPENAI/generate_api_key.py +48 -0
  19. webscout/Provider/OPENAI/gptoss.py +288 -0
  20. webscout/Provider/OPENAI/kimi.py +469 -0
  21. webscout/Provider/OPENAI/netwrck.py +8 -12
  22. webscout/Provider/OPENAI/refact.py +274 -0
  23. webscout/Provider/OPENAI/textpollinations.py +3 -6
  24. webscout/Provider/OPENAI/toolbaz.py +1 -0
  25. webscout/Provider/TTI/bing.py +14 -2
  26. webscout/Provider/TTI/together.py +10 -9
  27. webscout/Provider/TTS/README.md +0 -1
  28. webscout/Provider/TTS/__init__.py +0 -1
  29. webscout/Provider/TTS/base.py +479 -159
  30. webscout/Provider/TTS/deepgram.py +409 -156
  31. webscout/Provider/TTS/elevenlabs.py +425 -111
  32. webscout/Provider/TTS/freetts.py +317 -140
  33. webscout/Provider/TTS/gesserit.py +192 -128
  34. webscout/Provider/TTS/murfai.py +248 -113
  35. webscout/Provider/TTS/openai_fm.py +347 -129
  36. webscout/Provider/TTS/speechma.py +620 -586
  37. webscout/Provider/TextPollinationsAI.py +3 -6
  38. webscout/Provider/TogetherAI.py +50 -55
  39. webscout/Provider/UNFINISHED/VercelAIGateway.py +339 -0
  40. webscout/Provider/__init__.py +2 -90
  41. webscout/Provider/cerebras.py +83 -33
  42. webscout/Provider/copilot.py +42 -23
  43. webscout/Provider/toolbaz.py +1 -0
  44. webscout/conversation.py +22 -20
  45. webscout/sanitize.py +14 -10
  46. webscout/scout/README.md +20 -23
  47. webscout/scout/core/crawler.py +125 -38
  48. webscout/scout/core/scout.py +26 -5
  49. webscout/version.py +1 -1
  50. webscout/webscout_search.py +13 -6
  51. webscout/webscout_search_async.py +10 -8
  52. webscout/yep_search.py +13 -5
  53. {webscout-8.3.5.dist-info → webscout-8.3.6.dist-info}/METADATA +2 -1
  54. {webscout-8.3.5.dist-info → webscout-8.3.6.dist-info}/RECORD +59 -56
  55. webscout/Provider/Glider.py +0 -225
  56. webscout/Provider/OPENAI/c4ai.py +0 -394
  57. webscout/Provider/OPENAI/glider.py +0 -330
  58. webscout/Provider/TTS/sthir.py +0 -94
  59. /webscout/Provider/{samurai.py → UNFINISHED/samurai.py} +0 -0
  60. {webscout-8.3.5.dist-info → webscout-8.3.6.dist-info}/WHEEL +0 -0
  61. {webscout-8.3.5.dist-info → webscout-8.3.6.dist-info}/entry_points.txt +0 -0
  62. {webscout-8.3.5.dist-info → webscout-8.3.6.dist-info}/licenses/LICENSE.md +0 -0
  63. {webscout-8.3.5.dist-info → webscout-8.3.6.dist-info}/top_level.txt +0 -0
@@ -1,586 +1,620 @@
1
- ##################################################################################
2
- ## Modified version of code written by t.me/infip1217 ##
3
- ##################################################################################
4
- import requests
5
- import pathlib
6
- import tempfile
7
- from webscout import exceptions
8
- from webscout.litagent import LitAgent
9
- from webscout.Litlogger import Logger, LogLevel
10
- from webscout.Provider.TTS.base import BaseTTSProvider
11
-
12
- class SpeechMaTTS(BaseTTSProvider):
13
- """
14
- Text-to-speech provider using the SpeechMa API.
15
- """
16
- # Request headers
17
- headers = {
18
- "authority": "speechma.com",
19
- "origin": "https://speechma.com",
20
- "referer": "https://speechma.com/",
21
- "content-type": "application/json",
22
- **LitAgent().generate_fingerprint()
23
- }
24
-
25
- # Available voices with their IDs
26
- all_voices = {
27
- # Multilingual voices
28
- "Andrew Multilingual": "voice-107", # Male, Multilingual, United States
29
- "Ava Multilingual": "voice-110", # Female, Multilingual, United States
30
- "Brian Multilingual": "voice-112", # Male, Multilingual, United States
31
- "Emma Multilingual": "voice-115", # Female, Multilingual, United States
32
- "Remy Multilingual": "voice-142", # Male, Multilingual, France
33
- "Vivienne Multilingual": "voice-143", # Female, Multilingual, France
34
- "Florian Multilingual": "voice-154", # Male, Multilingual, Germany
35
- "Seraphina Multilingual": "voice-157", # Female, Multilingual, Germany
36
- "Giuseppe Multilingual": "voice-177", # Male, Multilingual, Italy
37
- "Hyunsu Multilingual": "voice-189", # Male, Multilingual, South Korea
38
- "Thalita Multilingual": "voice-222", # Female, Multilingual, Brazil
39
- # English (US)
40
- "Ana": "voice-106", # Female, English, United States
41
- "Andrew": "voice-108", # Male, English, United States
42
- "Aria": "voice-109", # Female, English, United States
43
- "Ava": "voice-111", # Female, English, United States
44
- "Brian": "voice-113", # Male, English, United States
45
- "Christopher": "voice-114", # Male, English, United States
46
- "Emma": "voice-116", # Female, English, United States
47
- "Eric": "voice-117", # Male, English, United States
48
- "Guy": "voice-118", # Male, English, United States
49
- "Jenny": "voice-119", # Female, English, United States
50
- "Michelle": "voice-120", # Female, English, United States
51
- "Roger": "voice-121", # Male, English, United States
52
- "Steffan": "voice-122", # Male, English, United States
53
- # English (UK)
54
- "Libby": "voice-82", # Female, English, United Kingdom
55
- "Maisie": "voice-83", # Female, English, United Kingdom
56
- "Ryan": "voice-84", # Male, English, United Kingdom
57
- "Sonia": "voice-85", # Female, English, United Kingdom
58
- "Thomas": "voice-86", # Male, English, United Kingdom
59
- # English (Australia)
60
- "Natasha": "voice-78", # Female, English, Australia
61
- "William": "voice-79", # Male, English, Australia
62
- # English (Canada)
63
- "Clara": "voice-80", # Female, English, Canada
64
- "Liam": "voice-81", # Male, English, Canada
65
- # English (India)
66
- "Neerja Expressive": "voice-91", # Female, English, India
67
- "Neerja": "voice-92", # Female, English, India
68
- "Prabhat": "voice-93", # Male, English, India
69
- # English (Hong Kong)
70
- "Sam": "voice-87", # Male, English, Hong Kong
71
- "Yan": "voice-88", # Female, English, Hong Kong
72
- # English (Ireland)
73
- "Connor": "voice-89", # Male, English, Ireland
74
- "Emily": "voice-90", # Female, English, Ireland
75
- # English (Kenya)
76
- "Asilia": "voice-94", # Female, English, Kenya
77
- "Chilemba": "voice-95", # Male, English, Kenya
78
- # English (Nigeria)
79
- "Abeo": "voice-96", # Male, English, Nigeria
80
- "Ezinne": "voice-97", # Female, English, Nigeria
81
- # English (New Zealand)
82
- "Mitchell": "voice-98", # Male, English, New Zealand
83
- "Molly": "voice-99", # Female, English, New Zealand
84
- # English (Philippines)
85
- "James": "voice-100", # Male, English, Philippines
86
- "Rosa": "voice-101", # Female, English, Philippines
87
- # English (Singapore)
88
- "Luna": "voice-102", # Female, English, Singapore
89
- "Wayne": "voice-103", # Male, English, Singapore
90
- # English (Tanzania)
91
- "Elimu": "voice-104", # Male, English, Tanzania
92
- "Imani": "voice-105", # Female, English, Tanzania
93
- # English (South Africa)
94
- "Leah": "voice-123", # Female, English, South Africa
95
- "Luke": "voice-124", # Male, English, South Africa
96
- # Spanish (Argentina)
97
- "Elena": "voice-239", # Female, Spanish, Argentina
98
- "Tomas": "voice-240", # Male, Spanish, Argentina
99
- # Spanish (Bolivia)
100
- "Marcelo": "voice-241", # Male, Spanish, Bolivia
101
- "Sofia": "voice-242", # Female, Spanish, Bolivia
102
- # Spanish (Chile)
103
- "Catalina": "voice-243", # Female, Spanish, Chile
104
- "Lorenzo": "voice-244", # Male, Spanish, Chile
105
- # Spanish (Colombia)
106
- "Gonzalo": "voice-245", # Male, Spanish, Colombia
107
- "Salome": "voice-246", # Female, Spanish, Colombia
108
- # Spanish (Costa Rica)
109
- "Juan": "voice-247", # Male, Spanish, Costa Rica
110
- "Maria": "voice-248", # Female, Spanish, Costa Rica
111
- # Spanish (Cuba)
112
- "Belkys": "voice-249", # Female, Spanish, Cuba
113
- "Manuel": "voice-250", # Male, Spanish, Cuba
114
- # Spanish (Dominican Republic)
115
- "Emilio": "voice-251", # Male, Spanish, Dominican Republic
116
- "Ramona": "voice-252", # Female, Spanish, Dominican Republic
117
- # Spanish (Ecuador)
118
- "Andrea": "voice-253", # Female, Spanish, Ecuador
119
- "Luis": "voice-254", # Male, Spanish, Ecuador
120
- # Spanish (Spain)
121
- "Alvaro": "voice-255", # Male, Spanish, Spain
122
- "Elvira": "voice-256", # Female, Spanish, Spain
123
- "Ximena": "voice-257", # Female, Spanish, Spain
124
- # Spanish (Equatorial Guinea)
125
- "Javier": "voice-258", # Male, Spanish, Equatorial Guinea
126
- "Teresa": "voice-259", # Female, Spanish, Equatorial Guinea
127
- # Spanish (Guatemala)
128
- "Andres": "voice-260", # Male, Spanish, Guatemala
129
- "Marta": "voice-261", # Female, Spanish, Guatemala
130
- # Spanish (Honduras)
131
- "Carlos": "voice-262", # Male, Spanish, Honduras
132
- "Karla": "voice-263", # Female, Spanish, Honduras
133
- # Spanish (Mexico)
134
- "Dalia": "voice-264", # Female, Spanish, Mexico
135
- "Jorge": "voice-265", # Male, Spanish, Mexico
136
- # Spanish (Nicaragua)
137
- "Federico": "voice-266", # Male, Spanish, Nicaragua
138
- "Yolanda": "voice-267", # Female, Spanish, Nicaragua
139
- # Spanish (Panama)
140
- "Margarita": "voice-268", # Female, Spanish, Panama
141
- "Roberto": "voice-269", # Male, Spanish, Panama
142
- # Spanish (Peru)
143
- "Alex": "voice-270", # Male, Spanish, Peru
144
- "Camila": "voice-271", # Female, Spanish, Peru
145
- # Spanish (Puerto Rico)
146
- "Karina": "voice-272", # Female, Spanish, Puerto Rico
147
- "Victor": "voice-273", # Male, Spanish, Puerto Rico
148
- # Spanish (Paraguay)
149
- "Mario": "voice-274", # Male, Spanish, Paraguay
150
- "Tania": "voice-275", # Female, Spanish, Paraguay
151
- # Spanish (El Salvador)
152
- "Lorena": "voice-276", # Female, Spanish, El Salvador
153
- "Rodrigo": "voice-277", # Male, Spanish, El Salvador
154
- # Spanish (United States)
155
- "Alonso": "voice-278", # Male, Spanish, United States
156
- "Paloma": "voice-279", # Female, Spanish, United States
157
- # Spanish (Uruguay)
158
- "Mateo": "voice-280", # Male, Spanish, Uruguay
159
- "Valentina": "voice-281", # Female, Spanish, Uruguay
160
- # Spanish (Venezuela)
161
- "Paola": "voice-282", # Female, Spanish, Venezuela
162
- "Sebastian": "voice-283", # Male, Spanish, Venezuela
163
- # Chinese (China)
164
- "Xiaoxiao": "voice-53", # Female, Chinese, China
165
- "Xiaoyi": "voice-54", # Female, Chinese, China
166
- "Yunjian": "voice-55", # Male, Chinese, China
167
- "Yunxi": "voice-56", # Male, Chinese, China
168
- "Yunxia": "voice-57", # Male, Chinese, China
169
- "Yunyang": "voice-58", # Male, Chinese, China
170
- "Xiaobei": "voice-59", # Female, Chinese, China
171
- "Xiaoni": "voice-60", # Female, Chinese, China
172
- # Chinese (Hong Kong)
173
- "HiuGaai": "voice-61", # Female, Chinese, Hong Kong
174
- "HiuMaan": "voice-62", # Female, Chinese, Hong Kong
175
- "WanLung": "voice-63", # Male, Chinese, Hong Kong
176
- # Chinese (Taiwan)
177
- "HsiaoChen": "voice-64", # Female, Chinese, Taiwan
178
- "HsiaoYu": "voice-65", # Female, Chinese, Taiwan
179
- "YunJhe": "voice-66", # Male, Chinese, Taiwan
180
- # French (Belgium)
181
- "Charline": "voice-131", # Female, French, Belgium
182
- "Gerard": "voice-132", # Male, French, Belgium
183
- # French (Canada)
184
- "Antoine": "voice-133", # Male, French, Canada
185
- "Jean": "voice-134", # Male, French, Canada
186
- "Sylvie": "voice-135", # Female, French, Canada
187
- "Thierry": "voice-136", # Male, French, Canada
188
- # French (Switzerland)
189
- "Ariane": "voice-137", # Female, French, Switzerland
190
- "Fabrice": "voice-138", # Male, French, Switzerland
191
- # French (France)
192
- "Denise": "voice-139", # Female, French, France
193
- "Eloise": "voice-140", # Female, French, France
194
- "Henri": "voice-141", # Male, French, France
195
- # German (Austria)
196
- "Ingrid": "voice-148", # Female, German, Austria
197
- "Jonas": "voice-149", # Male, German, Austria
198
- # German (Switzerland)
199
- "Jan": "voice-150", # Male, German, Switzerland
200
- "Leni": "voice-151", # Female, German, Switzerland
201
- # German (Germany)
202
- "Amala": "voice-152", # Female, German, Germany
203
- "Conrad": "voice-153", # Male, German, Germany
204
- "Katja": "voice-155", # Female, German, Germany
205
- "Killian": "voice-156", # Male, German, Germany
206
- # Arabic (United Arab Emirates)
207
- "Fatima": "voice-7", # Female, Arabic, United Arab Emirates
208
- "Hamdan": "voice-8", # Male, Arabic, United Arab Emirates
209
- # Arabic (Bahrain)
210
- "Ali": "voice-9", # Male, Arabic, Bahrain
211
- "Laila": "voice-10", # Female, Arabic, Bahrain
212
- # Arabic (Algeria)
213
- "Amina": "voice-11", # Female, Arabic, Algeria
214
- "Ismael": "voice-12", # Male, Arabic, Algeria
215
- # Arabic (Egypt)
216
- "Salma": "voice-13", # Female, Arabic, Egypt
217
- "Shakir": "voice-14", # Male, Arabic, Egypt
218
- # Arabic (Iraq)
219
- "Bassel": "voice-15", # Male, Arabic, Iraq
220
- "Rana": "voice-16", # Female, Arabic, Iraq
221
- # Arabic (Jordan)
222
- "Sana": "voice-17", # Female, Arabic, Jordan
223
- "Taim": "voice-18", # Male, Arabic, Jordan
224
- # Arabic (Kuwait)
225
- "Fahed": "voice-19", # Male, Arabic, Kuwait
226
- "Noura": "voice-20", # Female, Arabic, Kuwait
227
- # Arabic (Lebanon)
228
- "Layla": "voice-21", # Female, Arabic, Lebanon
229
- "Rami": "voice-22", # Male, Arabic, Lebanon
230
- # Arabic (Libya)
231
- "Iman": "voice-23", # Female, Arabic, Libya
232
- "Omar": "voice-24", # Male, Arabic, Libya
233
- # Arabic (Morocco)
234
- "Jamal": "voice-25", # Male, Arabic, Morocco
235
- "Mouna": "voice-26", # Female, Arabic, Morocco
236
- # Arabic (Oman)
237
- "Abdullah": "voice-27", # Male, Arabic, Oman
238
- "Aysha": "voice-28", # Female, Arabic, Oman
239
- # Arabic (Qatar)
240
- "Amal": "voice-29", # Female, Arabic, Qatar
241
- "Moaz": "voice-30", # Male, Arabic, Qatar
242
- # Arabic (Saudi Arabia)
243
- "Hamed": "voice-31", # Male, Arabic, Saudi Arabia
244
- "Zariyah": "voice-32", # Female, Arabic, Saudi Arabia
245
- # Arabic (Syria)
246
- "Amany": "voice-33", # Female, Arabic, Syria
247
- "Laith": "voice-34", # Male, Arabic, Syria
248
- # Arabic (Tunisia)
249
- "Hedi": "voice-35", # Male, Arabic, Tunisia
250
- "Reem": "voice-36", # Female, Arabic, Tunisia
251
- # Arabic (Yemen)
252
- "Maryam": "voice-37", # Female, Arabic, Yemen
253
- "Saleh": "voice-38", # Male, Arabic, Yemen
254
- # Afrikaans (South Africa)
255
- "Adri": "voice-1", # Female, Afrikaans, South Africa
256
- "Willem": "voice-2", # Male, Afrikaans, South Africa
257
- # Albanian (Albania)
258
- "Anila": "voice-3", # Female, Albanian, Albania
259
- "Ilir": "voice-4", # Male, Albanian, Albania
260
- # Amharic (Ethiopia)
261
- "Ameha": "voice-5", # Male, Amharic, Ethiopia
262
- "Mekdes": "voice-6", # Female, Amharic, Ethiopia
263
- # Azerbaijani (Azerbaijan)
264
- "Babek": "voice-39", # Male, Azerbaijani, Azerbaijan
265
- "Banu": "voice-40", # Female, Azerbaijani, Azerbaijan
266
- # Bengali (Bangladesh)
267
- "Nabanita": "voice-41", # Female, Bengali, Bangladesh
268
- "Pradeep": "voice-42", # Male, Bengali, Bangladesh
269
- # Bengali (India)
270
- "Bashkar": "voice-43", # Male, Bengali, India
271
- "Tanishaa": "voice-44", # Female, Bengali, India
272
- # Bosnian (Bosnia and Herzegovina)
273
- "Goran": "voice-45", # Male, Bosnian, Bosnia and Herzegovina
274
- "Vesna": "voice-46", # Female, Bosnian, Bosnia and Herzegovina
275
- # Bulgarian (Bulgaria)
276
- "Borislav": "voice-47", # Male, Bulgarian, Bulgaria
277
- "Kalina": "voice-48", # Female, Bulgarian, Bulgaria
278
- # Burmese (Myanmar)
279
- "Nilar": "voice-49", # Female, Burmese, Myanmar
280
- "Thiha": "voice-50", # Male, Burmese, Myanmar
281
- # Catalan (Spain)
282
- "Enric": "voice-51", # Male, Catalan, Spain
283
- "Joana": "voice-52", # Female, Catalan, Spain
284
- # Croatian (Croatia)
285
- "Gabrijela": "voice-67", # Female, Croatian, Croatia
286
- "Srecko": "voice-68", # Male, Croatian, Croatia
287
- # Czech (Czech Republic)
288
- "Antonin": "voice-69", # Male, Czech, Czech Republic
289
- "Vlasta": "voice-70", # Female, Czech, Czech Republic
290
- # Danish (Denmark)
291
- "Christel": "voice-71", # Female, Danish, Denmark
292
- "Jeppe": "voice-72", # Male, Danish, Denmark
293
- # Dutch (Belgium)
294
- "Arnaud": "voice-73", # Male, Dutch, Belgium
295
- "Dena": "voice-74", # Female, Dutch, Belgium
296
- # Dutch (Netherlands)
297
- "Colette": "voice-75", # Female, Dutch, Netherlands
298
- "Fenna": "voice-76", # Female, Dutch, Netherlands
299
- "Maarten": "voice-77", # Male, Dutch, Netherlands
300
- # Estonian (Estonia)
301
- "Anu": "voice-125", # Female, Estonian, Estonia
302
- "Kert": "voice-126", # Male, Estonian, Estonia
303
- # Filipino (Philippines)
304
- "Angelo": "voice-127", # Male, Filipino, Philippines
305
- "Blessica": "voice-128", # Female, Filipino, Philippines
306
- # Finnish (Finland)
307
- "Harri": "voice-129", # Male, Finnish, Finland
308
- "Noora": "voice-130", # Female, Finnish, Finland
309
- # Galician (Spain)
310
- "Roi": "voice-144", # Male, Galician, Spain
311
- "Sabela": "voice-145", # Female, Galician, Spain
312
- # Georgian (Georgia)
313
- "Eka": "voice-146", # Female, Georgian, Georgia
314
- "Giorgi": "voice-147", # Male, Georgian, Georgia
315
- # Greek (Greece)
316
- "Athina": "voice-158", # Female, Greek, Greece
317
- "Nestoras": "voice-159", # Male, Greek, Greece (Note: voice-160 is a duplicate name)
318
- # Gujarati (India)
319
- "Dhwani": "voice-161", # Female, Gujarati, India
320
- "Niranjan": "voice-162", # Male, Gujarati, India
321
- # Hebrew (Israel)
322
- "Avri": "voice-163", # Male, Hebrew, Israel
323
- "Hila": "voice-164", # Female, Hebrew, Israel
324
- # Hindi (India)
325
- "Madhur": "voice-165", # Male, Hindi, India
326
- "Swara": "voice-166", # Female, Hindi, India
327
- # Hungarian (Hungary)
328
- "Noemi": "voice-167", # Female, Hungarian, Hungary
329
- "Tamas": "voice-168", # Male, Hungarian, Hungary
330
- # Icelandic (Iceland)
331
- "Gudrun": "voice-169", # Female, Icelandic, Iceland
332
- "Gunnar": "voice-170", # Male, Icelandic, Iceland
333
- # Indonesian (Indonesia)
334
- "Ardi": "voice-171", # Male, Indonesian, Indonesia
335
- "Gadis": "voice-172", # Female, Indonesian, Indonesia
336
- # Irish (Ireland)
337
- "Colm": "voice-173", # Male, Irish, Ireland
338
- "Orla": "voice-174", # Female, Irish, Ireland
339
- # Italian (Italy)
340
- "Diego": "voice-175", # Male, Italian, Italy
341
- "Elsa": "voice-176", # Female, Italian, Italy
342
- "Isabella": "voice-178", # Female, Italian, Italy
343
- # Japanese (Japan)
344
- "Keita": "voice-179", # Male, Japanese, Japan
345
- "Nanami": "voice-180", # Female, Japanese, Japan
346
- # Javanese (Indonesia)
347
- "Dimas": "voice-181", # Male, Javanese, Indonesia
348
- "Siti": "voice-182", # Female, Javanese, Indonesia
349
- # Kannada (India)
350
- "Gagan": "voice-183", # Male, Kannada, India
351
- "Sapna": "voice-184", # Female, Kannada, India
352
- # Kazakh (Kazakhstan)
353
- "Aigul": "voice-185", # Female, Kazakh, Kazakhstan
354
- "Daulet": "voice-186", # Male, Kazakh, Kazakhstan
355
- # Khmer (Cambodia)
356
- "Piseth": "voice-187", # Male, Khmer, Cambodia
357
- "Sreymom": "voice-188", # Female, Khmer, Cambodia
358
- # Korean (South Korea)
359
- "InJoon": "voice-190", # Male, Korean, South Korea
360
- "SunHi": "voice-191", # Female, Korean, South Korea
361
- # Lao (Laos)
362
- "Chanthavong": "voice-192", # Male, Lao, Laos
363
- "Keomany": "voice-193", # Female, Lao, Laos
364
- # Latvian (Latvia)
365
- "Everita": "voice-194", # Female, Latvian, Latvia
366
- "Nils": "voice-195", # Male, Latvian, Latvia
367
- # Lithuanian (Lithuania)
368
- "Leonas": "voice-196", # Male, Lithuanian, Lithuania
369
- "Ona": "voice-197", # Female, Lithuanian, Lithuania
370
- # Macedonian (North Macedonia)
371
- "Aleksandar": "voice-198", # Male, Macedonian, North Macedonia
372
- "Marija": "voice-199", # Female, Macedonian, North Macedonia
373
- # Malay (Malaysia)
374
- "Osman": "voice-200", # Male, Malay, Malaysia
375
- "Yasmin": "voice-201", # Female, Malay, Malaysia
376
- # Malayalam (India)
377
- "Midhun": "voice-202", # Male, Malayalam, India
378
- "Sobhana": "voice-203", # Female, Malayalam, India
379
- # Maltese (Malta)
380
- "Grace": "voice-204", # Female, Maltese, Malta
381
- "Joseph": "voice-205", # Male, Maltese, Malta
382
- # Marathi (India)
383
- "Aarohi": "voice-206", # Female, Marathi, India
384
- "Manohar": "voice-207", # Male, Marathi, India
385
- # Mongolian (Mongolia)
386
- "Bataa": "voice-208", # Male, Mongolian, Mongolia
387
- "Yesui": "voice-209", # Female, Mongolian, Mongolia
388
- # Nepali (Nepal)
389
- "Hemkala": "voice-210", # Female, Nepali, Nepal
390
- "Sagar": "voice-211", # Male, Nepali, Nepal
391
- # Norwegian (Norway)
392
- "Finn": "voice-212", # Male, Norwegian, Norway
393
- "Pernille": "voice-213", # Female, Norwegian, Norway
394
- # Pashto (Afghanistan)
395
- "GulNawaz": "voice-214", # Male, Pashto, Afghanistan
396
- "Latifa": "voice-215", # Female, Pashto, Afghanistan
397
- # Persian (Iran)
398
- "Dilara": "voice-216", # Female, Persian, Iran
399
- "Farid": "voice-217", # Male, Persian, Iran
400
- # Polish (Poland)
401
- "Marek": "voice-218", # Male, Polish, Poland
402
- "Zofia": "voice-219", # Female, Polish, Poland
403
- # Portuguese (Brazil)
404
- "Antonio": "voice-220", # Male, Portuguese, Brazil
405
- "Francisca": "voice-221", # Female, Portuguese, Brazil
406
- # Portuguese (Portugal)
407
- "Duarte": "voice-223", # Male, Portuguese, Portugal
408
- "Raquel": "voice-224", # Female, Portuguese, Portugal
409
- # Romanian (Romania)
410
- "Alina": "voice-225", # Female, Romanian, Romania
411
- "Emil": "voice-226", # Male, Romanian, Romania
412
- # Russian (Russia)
413
- "Dmitry": "voice-227", # Male, Russian, Russia
414
- "Svetlana": "voice-228", # Female, Russian, Russia
415
- # Serbian (Serbia)
416
- "Nicholas": "voice-229", # Male, Serbian, Serbia
417
- "Sophie": "voice-230", # Female, Serbian, Serbia
418
- # Sinhala (Sri Lanka)
419
- "Sameera": "voice-231", # Male, Sinhala, Sri Lanka
420
- "Thilini": "voice-232", # Female, Sinhala, Sri Lanka
421
- # Slovak (Slovakia)
422
- "Lukas": "voice-233", # Male, Slovak, Slovakia
423
- "Viktoria": "voice-234", # Female, Slovak, Slovakia
424
- # Slovenian (Slovenia)
425
- "Petra": "voice-235", # Female, Slovenian, Slovenia
426
- "Rok": "voice-236", # Male, Slovenian, Slovenia
427
- # Somali (Somalia)
428
- "Muuse": "voice-237", # Male, Somali, Somalia
429
- "Ubax": "voice-238", # Female, Somali, Somalia
430
- # Sundanese (Indonesia)
431
- "Jajang": "voice-284", # Male, Sundanese, Indonesia
432
- "Tuti": "voice-285", # Female, Sundanese, Indonesia
433
- # Swahili (Kenya)
434
- "Rafiki": "voice-286", # Male, Swahili, Kenya
435
- "Zuri": "voice-287", # Female, Swahili, Kenya
436
- # Swahili (Tanzania)
437
- "Daudi": "voice-288", # Male, Swahili, Tanzania
438
- "Rehema": "voice-289", # Female, Swahili, Tanzania
439
- # Swedish (Sweden)
440
- "Mattias": "voice-290", # Male, Swedish, Sweden
441
- "Sofie": "voice-291", # Female, Swedish, Sweden
442
- # Tamil (India)
443
- "Pallavi": "voice-292", # Female, Tamil, India
444
- "Valluvar": "voice-293", # Male, Tamil, India
445
- # Tamil (Sri Lanka)
446
- "Kumar": "voice-294", # Male, Tamil, Sri Lanka
447
- "Saranya": "voice-295", # Female, Tamil, Sri Lanka
448
- # Tamil (Malaysia)
449
- "Kani": "voice-296", # Female, Tamil, Malaysia
450
- "Surya": "voice-297", # Male, Tamil, Malaysia
451
- # Tamil (Singapore)
452
- "Anbu": "voice-298", # Male, Tamil, Singapore
453
- "Venba": "voice-299", # Female, Tamil, Singapore
454
- # Telugu (India)
455
- "Mohan": "voice-300", # Male, Telugu, India
456
- "Shruti": "voice-301", # Female, Telugu, India
457
- # Thai (Thailand)
458
- "Niwat": "voice-302", # Male, Thai, Thailand
459
- "Premwadee": "voice-303", # Female, Thai, Thailand
460
- # Turkish (Turkey)
461
- "Ahmet": "voice-304", # Male, Turkish, Turkey
462
- "Emel": "voice-305", # Female, Turkish, Turkey
463
- # Ukrainian (Ukraine)
464
- "Ostap": "voice-306", # Male, Ukrainian, Ukraine
465
- "Polina": "voice-307", # Female, Ukrainian, Ukraine
466
- # Urdu (India)
467
- "Gul": "voice-308", # Female, Urdu, India
468
- "Salman": "voice-309", # Male, Urdu, India
469
- # Urdu (Pakistan)
470
- "Asad": "voice-310", # Male, Urdu, Pakistan
471
- "Uzma": "voice-311", # Female, Urdu, Pakistan
472
- # Uzbek (Uzbekistan)
473
- "Madina": "voice-312", # Female, Uzbek, Uzbekistan
474
- "Sardor": "voice-313", # Male, Uzbek, Uzbekistan
475
- # Vietnamese (Vietnam)
476
- "HoaiMy": "voice-314", # Female, Vietnamese, Vietnam
477
- "NamMinh": "voice-315", # Male, Vietnamese, Vietnam
478
- # Welsh (United Kingdom)
479
- "Aled": "voice-316", # Male, Welsh, United Kingdom
480
- "Nia": "voice-317", # Female, Welsh, United Kingdom
481
- # Zulu (South Africa)
482
- "Thando": "voice-318", # Female, Zulu, South Africa
483
- "Themba": "voice-319", # Male, Zulu, South Africa
484
- }
485
-
486
- def __init__(self, timeout: int = 20, proxies: dict = None):
487
- """Initializes the SpeechMa TTS client."""
488
- super().__init__()
489
- self.api_url = "https://speechma.com/com.api/tts-api.php"
490
- self.session = requests.Session()
491
- self.session.headers.update(self.headers)
492
- if proxies:
493
- self.session.proxies.update(proxies)
494
- self.timeout = timeout
495
- self.logger = Logger(name="SpeechMaTTS", level=LogLevel.INFO)
496
-
497
- def tts(self, text: str, voice: str = "Emma", pitch: int = 0, rate: int = 0, verbose: bool = False) -> str:
498
- """
499
- Converts text to speech using the SpeechMa API and saves it to a file.
500
-
501
- Args:
502
- text (str): The text to convert to speech
503
- voice (str): The voice to use for TTS (default: "Emma")
504
- pitch (int): Voice pitch adjustment (-10 to 10, default: 0)
505
- rate (int): Voice rate/speed adjustment (-10 to 10, default: 0)
506
- verbose (bool): Whether to print debug information (default: False)
507
-
508
- Returns:
509
- str: Path to the generated audio file
510
-
511
- Raises:
512
- exceptions.FailedToGenerateResponseError: If there is an error generating or saving the audio.
513
- """
514
- assert (
515
- voice in self.all_voices
516
- ), f"Voice '{voice}' not one of [{', '.join(self.all_voices.keys())}]"
517
-
518
- if not text or text.strip() == '':
519
- raise exceptions.FailedToGenerateResponseError("Text is empty")
520
-
521
- filename = pathlib.Path(tempfile.mktemp(suffix=".mp3", dir=self.temp_dir))
522
- voice_id = self.all_voices[voice]
523
-
524
- # Prepare payload for the API
525
- payload = {
526
- "text": text,
527
- "voice": voice_id,
528
- "pitch": pitch,
529
- "rate": rate,
530
- "volume": 100
531
- }
532
-
533
- try:
534
- # Set logger level based on verbose flag
535
- if verbose:
536
- self.logger.level = LogLevel.DEBUG
537
- self.logger.debug(f"Generating audio for voice: {voice} ({voice_id})")
538
- self.logger.debug(f"Text length: {len(text)} characters")
539
- else:
540
- self.logger.level = LogLevel.INFO
541
-
542
- # Make the request to the SpeechMa API
543
- response = self.session.post(
544
- self.api_url,
545
- headers=self.headers,
546
- json=payload,
547
- timeout=self.timeout
548
- )
549
-
550
- if response.status_code != 200:
551
- if verbose:
552
- self.logger.error(f"API error: Status {response.status_code}")
553
- raise exceptions.FailedToGenerateResponseError(f"API returned status {response.status_code}: {response.text[:500]}")
554
-
555
- # Check if response is audio data (content-type should be audio/mpeg)
556
- content_type = response.headers.get('content-type', '').lower()
557
- if verbose:
558
- self.logger.debug(f"Response content type: {content_type}")
559
- self.logger.debug(f"Response size: {len(response.content)} bytes")
560
-
561
- if 'audio' in content_type or response.content.startswith(b'\xff\xfb') or response.content.startswith(b'ID3') or b'LAME' in response.content[:100]:
562
- # This is audio data, save it directly
563
- with open(filename, 'wb') as f:
564
- f.write(response.content)
565
- if verbose:
566
- self.logger.debug(f"Audio saved to: {filename}")
567
- return filename.as_posix()
568
- else:
569
- # Unexpected response format
570
- if verbose:
571
- self.logger.error(f"Unexpected response format: {content_type}")
572
- raise exceptions.FailedToGenerateResponseError(f"Unexpected response format. Content-Type: {content_type}, Content: {response.text[:200]}")
573
-
574
- except requests.exceptions.RequestException as e:
575
- if verbose:
576
- self.logger.error(f"Request failed: {e}")
577
- raise exceptions.FailedToGenerateResponseError(
578
- f"Failed to perform the operation: {e}"
579
- )
580
-
581
- # Example usage
582
- if __name__ == "__main__":
583
- speechma = SpeechMaTTS()
584
- text = "This is a test of the SpeechMa text-to-speech API. It supports multiple sentences."
585
- audio_file = speechma.tts(text, voice="Emma")
586
- print(f"Audio saved to: {audio_file}")
1
+ ##################################################################################
2
+ ## Modified version of code written by t.me/infip1217 ##
3
+ ##################################################################################
4
+ import time
5
+ import requests
6
+ import pathlib
7
+ import tempfile
8
+ from io import BytesIO
9
+ from webscout import exceptions
10
+ from webscout.litagent import LitAgent
11
+ from webscout.Litlogger import Logger, LogLevel
12
+ from concurrent.futures import ThreadPoolExecutor, as_completed
13
+
14
+ try:
15
+ from . import utils
16
+ from .base import BaseTTSProvider
17
+ except ImportError:
18
+ # Handle direct execution
19
+ import sys
20
+ import os
21
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
22
+ from webscout.Provider.TTS import utils
23
+ from webscout.Provider.TTS.base import BaseTTSProvider
24
+
25
+ class SpeechMaTTS(BaseTTSProvider):
26
+ """
27
+ Text-to-speech provider using the SpeechMa API with OpenAI-compatible interface.
28
+
29
+ This provider follows the OpenAI TTS API structure with support for:
30
+ - Multiple TTS models (gpt-4o-mini-tts, tts-1, tts-1-hd)
31
+ - Multilingual voices with pitch and rate control
32
+ - Voice instructions for controlling speech aspects
33
+ - Multiple output formats
34
+ - Streaming support
35
+ """
36
+
37
+ # Request headers
38
+ headers = {
39
+ "authority": "speechma.com",
40
+ "origin": "https://speechma.com",
41
+ "referer": "https://speechma.com/",
42
+ "content-type": "application/json",
43
+ **LitAgent().generate_fingerprint()
44
+ }
45
+
46
+ # SpeechMa doesn't support different models - set to None
47
+ SUPPORTED_MODELS = None
48
+
49
+ # All supported voices from SpeechMa API
50
+ SUPPORTED_VOICES = [
51
+ "aditi", "amy", "astrid", "bianca", "carla", "carmen", "celine", "chant",
52
+ "conchita", "cristiano", "dora", "enrique", "ewa", "filiz", "geraint",
53
+ "giorgio", "gwyneth", "hans", "ines", "ivy", "jacek", "jan", "joanna",
54
+ "joey", "justin", "karl", "kendra", "kimberly", "lea", "liv", "lotte",
55
+ "lucia", "lupe", "mads", "maja", "marlene", "mathieu", "matthew", "maxim",
56
+ "mia", "miguel", "mizuki", "naja", "nicole", "penelope", "raveena",
57
+ "ricardo", "ruben", "russell", "salli", "seoyeon", "takumi", "tatyana",
58
+ "vicki", "vitoria", "zeina", "zhiyu", "aditi-neural", "amy-neural",
59
+ "aria-neural", "ayanda-neural", "brian-neural", "emma-neural",
60
+ "jenny-neural", "joey-neural", "justin-neural", "kendra-neural",
61
+ "kimberly-neural", "matthew-neural", "olivia-neural", "ruth-neural",
62
+ "salli-neural", "stephen-neural", "suvi-neural", "camila-neural",
63
+ "lupe-neural", "pedro-neural", "natasha-neural", "william-neural",
64
+ "clara-neural", "liam-neural", "libby-neural", "maisie-neural",
65
+ "ryan-neural", "sonia-neural", "thomas-neural", "aria-multilingual",
66
+ "andrew-multilingual", "brian-multilingual", "emma-multilingual",
67
+ "jenny-multilingual", "ryan-multilingual", "adam-multilingual",
68
+ "liam-multilingual", "aria-turbo", "andrew-turbo", "brian-turbo",
69
+ "emma-turbo", "jenny-turbo", "ryan-turbo", "adam-turbo", "liam-turbo",
70
+ "aria-hd", "andrew-hd", "brian-hd", "emma-hd", "jenny-hd", "andrew-hd-2",
71
+ "aria-hd-2", "adam-hd", "ava-hd", "davis-hd", "brian-hd-2",
72
+ "christopher-hd", "coral-hd", "emma-hd-2", "eric-hd", "fable-hd",
73
+ "jenny-hd-2", "michelle-hd", "roger-hd", "sage-hd", "vale-hd", "verse-hd",
74
+ # Legacy voice names for backward compatibility
75
+ "emma", "ava", "brian", "andrew", "aria", "christopher", "eric", "jenny",
76
+ "michelle", "roger", "libby", "ryan", "sonia", "thomas", "natasha",
77
+ "william", "clara", "liam"
78
+ ]
79
+
80
+ # Voice mapping for SpeechMa API compatibility (lowercase keys for all voices)
81
+ voice_mapping = {
82
+ # Standard voices
83
+ "aditi": "voice-1",
84
+ "amy": "voice-2",
85
+ "astrid": "voice-3",
86
+ "bianca": "voice-4",
87
+ "carla": "voice-5",
88
+ "carmen": "voice-6",
89
+ "celine": "voice-7",
90
+ "chant": "voice-8",
91
+ "conchita": "voice-9",
92
+ "cristiano": "voice-10",
93
+ "dora": "voice-11",
94
+ "enrique": "voice-12",
95
+ "ewa": "voice-13",
96
+ "filiz": "voice-14",
97
+ "geraint": "voice-15",
98
+ "giorgio": "voice-16",
99
+ "gwyneth": "voice-17",
100
+ "hans": "voice-18",
101
+ "ines": "voice-19",
102
+ "ivy": "voice-20",
103
+ "jacek": "voice-21",
104
+ "jan": "voice-22",
105
+ "joanna": "voice-23",
106
+ "joey": "voice-24",
107
+ "justin": "voice-25",
108
+ "karl": "voice-26",
109
+ "kendra": "voice-27",
110
+ "kimberly": "voice-28",
111
+ "lea": "voice-29",
112
+ "liv": "voice-30",
113
+ "lotte": "voice-31",
114
+ "lucia": "voice-32",
115
+ "lupe": "voice-33",
116
+ "mads": "voice-34",
117
+ "maja": "voice-35",
118
+ "marlene": "voice-36",
119
+ "mathieu": "voice-37",
120
+ "matthew": "voice-38",
121
+ "maxim": "voice-39",
122
+ "mia": "voice-40",
123
+ "miguel": "voice-41",
124
+ "mizuki": "voice-42",
125
+ "naja": "voice-43",
126
+ "nicole": "voice-44",
127
+ "penelope": "voice-45",
128
+ "raveena": "voice-46",
129
+ "ricardo": "voice-47",
130
+ "ruben": "voice-48",
131
+ "russell": "voice-49",
132
+ "salli": "voice-50",
133
+ "seoyeon": "voice-51",
134
+ "takumi": "voice-52",
135
+ "tatyana": "voice-53",
136
+ "vicki": "voice-54",
137
+ "vitoria": "voice-55",
138
+ "zeina": "voice-56",
139
+ "zhiyu": "voice-57",
140
+ # Neural voices
141
+ "aditi-neural": "voice-58",
142
+ "amy-neural": "voice-59",
143
+ "aria-neural": "voice-60",
144
+ "ayanda-neural": "voice-61",
145
+ "brian-neural": "voice-62",
146
+ "emma-neural": "voice-63",
147
+ "jenny-neural": "voice-64",
148
+ "joey-neural": "voice-65",
149
+ "justin-neural": "voice-66",
150
+ "kendra-neural": "voice-67",
151
+ "kimberly-neural": "voice-68",
152
+ "matthew-neural": "voice-69",
153
+ "olivia-neural": "voice-70",
154
+ "ruth-neural": "voice-71",
155
+ "salli-neural": "voice-72",
156
+ "stephen-neural": "voice-73",
157
+ "suvi-neural": "voice-74",
158
+ "camila-neural": "voice-75",
159
+ "lupe-neural": "voice-76",
160
+ "pedro-neural": "voice-77",
161
+ "natasha-neural": "voice-78",
162
+ "william-neural": "voice-79",
163
+ "clara-neural": "voice-80",
164
+ "liam-neural": "voice-81",
165
+ "libby-neural": "voice-82",
166
+ "maisie-neural": "voice-83",
167
+ "ryan-neural": "voice-84",
168
+ "sonia-neural": "voice-85",
169
+ "thomas-neural": "voice-86",
170
+ # Multilingual voices
171
+ "aria-multilingual": "voice-87",
172
+ "andrew-multilingual": "voice-88",
173
+ "brian-multilingual": "voice-89",
174
+ "emma-multilingual": "voice-90",
175
+ "jenny-multilingual": "voice-91",
176
+ "ryan-multilingual": "voice-92",
177
+ "adam-multilingual": "voice-93",
178
+ "liam-multilingual": "voice-94",
179
+ # Turbo voices
180
+ "aria-turbo": "voice-95",
181
+ "andrew-turbo": "voice-96",
182
+ "brian-turbo": "voice-97",
183
+ "emma-turbo": "voice-98",
184
+ "jenny-turbo": "voice-99",
185
+ "ryan-turbo": "voice-100",
186
+ "adam-turbo": "voice-101",
187
+ "liam-turbo": "voice-102",
188
+ # HD voices
189
+ "aria-hd": "voice-103",
190
+ "andrew-hd": "voice-104",
191
+ "brian-hd": "voice-105",
192
+ "emma-hd": "voice-106",
193
+ "jenny-hd": "voice-107",
194
+ "andrew-hd-2": "voice-108",
195
+ "aria-hd-2": "voice-109",
196
+ "adam-hd": "voice-110",
197
+ "ava-hd": "voice-111",
198
+ "davis-hd": "voice-112",
199
+ "brian-hd-2": "voice-113",
200
+ "christopher-hd": "voice-114",
201
+ "coral-hd": "voice-115",
202
+ "emma-hd-2": "voice-116",
203
+ "eric-hd": "voice-117",
204
+ "fable-hd": "voice-118",
205
+ "jenny-hd-2": "voice-119",
206
+ "michelle-hd": "voice-120",
207
+ "roger-hd": "voice-121",
208
+ "sage-hd": "voice-122",
209
+ "vale-hd": "voice-123",
210
+ "verse-hd": "voice-124",
211
+ # Legacy compatibility mappings (lowercase)
212
+ "emma": "voice-116",
213
+ "ava": "voice-111",
214
+ "brian": "voice-113",
215
+ "andrew": "voice-108",
216
+ "aria": "voice-109",
217
+ "christopher": "voice-114",
218
+ "eric": "voice-117",
219
+ "jenny": "voice-119",
220
+ "michelle": "voice-120",
221
+ "roger": "voice-121",
222
+ "libby": "voice-82",
223
+ "ryan": "voice-84",
224
+ "sonia": "voice-85",
225
+ "thomas": "voice-86",
226
+ "natasha": "voice-78",
227
+ "william": "voice-79",
228
+ "clara": "voice-80",
229
+ "liam": "voice-81"
230
+ }
231
+
232
+ # Legacy voice mapping for backward compatibility
233
+ all_voices = voice_mapping
234
+
235
+ def __init__(self, timeout: int = 20, proxies: dict = None):
236
+ """
237
+ Initialize the SpeechMa TTS client.
238
+
239
+ Args:
240
+ timeout (int): Request timeout in seconds
241
+ proxies (dict): Proxy configuration
242
+ """
243
+ super().__init__()
244
+ self.api_url = "https://speechma.com/com.api/tts-api.php"
245
+ self.session = requests.Session()
246
+ self.session.headers.update(self.headers)
247
+ if proxies:
248
+ self.session.proxies.update(proxies)
249
+ self.timeout = timeout
250
+ self.logger = Logger(name="SpeechMaTTS", level=LogLevel.INFO)
251
+ # Override defaults for SpeechMa
252
+ self.default_voice = "emma"
253
+ self.default_model = "gpt-4o-mini-tts"
254
+
255
+ def create_speech(
256
+ self,
257
+ input: str,
258
+ voice: str = "emma",
259
+ model: str = None,
260
+ response_format: str = "mp3",
261
+ speed: float = 1.0,
262
+ instructions: str = None,
263
+ **kwargs
264
+ ) -> bytes:
265
+ """
266
+ Create speech from text using OpenAI-compatible interface.
267
+
268
+ Args:
269
+ input (str): The text to convert to speech
270
+ voice (str): Voice to use for generation
271
+ model (str): TTS model to use
272
+ response_format (str): Audio format (mp3, opus, aac, flac, wav, pcm)
273
+ speed (float): Speed of speech (0.25 to 4.0)
274
+ instructions (str): Voice instructions (not used by SpeechMa)
275
+ **kwargs: Additional parameters (pitch, rate for SpeechMa compatibility)
276
+
277
+ Returns:
278
+ bytes: Audio data
279
+
280
+ Raises:
281
+ ValueError: If input parameters are invalid
282
+ exceptions.FailedToGenerateResponseError: If generation fails
283
+ """
284
+ # Validate parameters
285
+ if not input or not isinstance(input, str):
286
+ raise ValueError("Input text must be a non-empty string")
287
+ if len(input) > 10000:
288
+ raise ValueError("Input text exceeds maximum allowed length of 10,000 characters")
289
+
290
+ model = self.validate_model(model or self.default_model)
291
+ voice = self.validate_voice(voice)
292
+ response_format = self.validate_format(response_format)
293
+
294
+ # Convert speed to SpeechMa rate parameter
295
+ rate = int((speed - 1.0) * 10) # Convert 0.25-4.0 to -7.5 to 30, clamp to -10 to 10
296
+ rate = max(-10, min(10, rate))
297
+
298
+ # Extract SpeechMa-specific parameters
299
+ pitch = kwargs.get('pitch', 0)
300
+
301
+ # Map voice to SpeechMa format
302
+ speechma_voice = self.voice_mapping.get(voice, self.all_voices.get(voice.title(), "voice-116"))
303
+
304
+ # Prepare payload
305
+ payload = {
306
+ "text": input,
307
+ "voice": speechma_voice,
308
+ "pitch": pitch,
309
+ "rate": rate,
310
+ "volume": 100
311
+ }
312
+
313
+ try:
314
+ response = self.session.post(
315
+ self.api_url,
316
+ headers=self.headers,
317
+ json=payload,
318
+ timeout=self.timeout
319
+ )
320
+ response.raise_for_status()
321
+
322
+ # Validate audio response
323
+ content_type = response.headers.get('content-type', '').lower()
324
+ if ('audio' in content_type or
325
+ response.content.startswith(b'\xff\xfb') or
326
+ response.content.startswith(b'ID3') or
327
+ b'LAME' in response.content[:100]):
328
+ return response.content
329
+ else:
330
+ raise exceptions.FailedToGenerateResponseError(
331
+ f"Unexpected response format. Content-Type: {content_type}"
332
+ )
333
+
334
+ except requests.exceptions.RequestException as e:
335
+ raise exceptions.FailedToGenerateResponseError(f"API request failed: {e}")
336
+
337
+ def with_streaming_response(self):
338
+ """
339
+ Return a context manager for streaming responses.
340
+
341
+ Returns:
342
+ SpeechMaStreamingResponse: Context manager for streaming
343
+ """
344
+ return SpeechMaStreamingResponse(self)
345
+
346
+ def tts(
347
+ self,
348
+ text: str,
349
+ model: str = None,
350
+ voice: str = "emma",
351
+ response_format: str = "mp3",
352
+ instructions: str = None,
353
+ pitch: int = 0,
354
+ rate: int = 0,
355
+ verbose: bool = True
356
+ ) -> str:
357
+ """
358
+ Convert text to speech using SpeechMa API with OpenAI-compatible parameters.
359
+
360
+ Args:
361
+ text (str): The text to convert to speech (max 10,000 characters)
362
+ model (str): The TTS model to use (gpt-4o-mini-tts, tts-1, tts-1-hd)
363
+ voice (str): The voice to use for TTS (emma, ava, brian, etc.)
364
+ response_format (str): Audio format (mp3, opus, aac, flac, wav, pcm)
365
+ instructions (str): Voice instructions (not used by SpeechMa but kept for compatibility)
366
+ pitch (int): Voice pitch adjustment (-10 to 10, default: 0)
367
+ rate (int): Voice rate/speed adjustment (-10 to 10, default: 0)
368
+ verbose (bool): Whether to print debug information
369
+
370
+ Returns:
371
+ str: Path to the generated audio file
372
+
373
+ Raises:
374
+ ValueError: If input parameters are invalid
375
+ exceptions.FailedToGenerateResponseError: If there is an error generating or saving the audio
376
+ """
377
+ # Validate input parameters
378
+ if not text or not isinstance(text, str):
379
+ raise ValueError("Input text must be a non-empty string")
380
+ if len(text) > 10000:
381
+ raise ValueError("Input text exceeds maximum allowed length of 10,000 characters")
382
+
383
+ # Validate model, voice, and format using base class methods
384
+ model = self.validate_model(model or self.default_model)
385
+ voice = self.validate_voice(voice)
386
+ response_format = self.validate_format(response_format)
387
+
388
+ # Map voice to SpeechMa API format
389
+ speechma_voice = self.voice_mapping.get(voice, voice)
390
+ if speechma_voice not in self.all_voices.values():
391
+ # Fallback to legacy voice mapping
392
+ speechma_voice = self.all_voices.get(voice.title(), self.all_voices.get("Emma", "voice-116"))
393
+
394
+ # Create temporary file with appropriate extension
395
+ file_extension = f".{response_format}" if response_format != "pcm" else ".wav"
396
+ filename = pathlib.Path(tempfile.mktemp(suffix=file_extension, dir=self.temp_dir))
397
+
398
+ # Split text into sentences using the utils module for better processing
399
+ sentences = utils.split_sentences(text)
400
+ if verbose:
401
+ print(f"[debug] Processing {len(sentences)} sentences")
402
+ print(f"[debug] Model: {model}")
403
+ print(f"[debug] Voice: {voice} -> {speechma_voice}")
404
+ print(f"[debug] Format: {response_format}")
405
+
406
+ def generate_audio_for_chunk(part_text: str, part_number: int):
407
+ """
408
+ Generate audio for a single chunk of text.
409
+
410
+ Args:
411
+ part_text (str): The text chunk to convert
412
+ part_number (int): The chunk number for ordering
413
+
414
+ Returns:
415
+ tuple: (part_number, audio_data)
416
+
417
+ Raises:
418
+ requests.RequestException: If there's an API error
419
+ """
420
+ max_retries = 3
421
+ retry_count = 0
422
+
423
+ while retry_count < max_retries:
424
+ try:
425
+ payload = {
426
+ "text": part_text,
427
+ "voice": speechma_voice,
428
+ "pitch": pitch,
429
+ "rate": rate,
430
+ "volume": 100,
431
+ # Add model parameter for future SpeechMa API compatibility
432
+ "tts_model": model
433
+ }
434
+ response = self.session.post(
435
+ url=self.api_url,
436
+ headers=self.headers,
437
+ json=payload,
438
+ timeout=self.timeout
439
+ )
440
+ response.raise_for_status()
441
+
442
+ # Check if response is audio data
443
+ content_type = response.headers.get('content-type', '').lower()
444
+ if ('audio' in content_type or
445
+ response.content.startswith(b'\xff\xfb') or
446
+ response.content.startswith(b'ID3') or
447
+ b'LAME' in response.content[:100]):
448
+ if verbose:
449
+ print(f"[debug] Chunk {part_number} processed successfully")
450
+ return part_number, response.content
451
+ else:
452
+ raise exceptions.FailedToGenerateResponseError(
453
+ f"Unexpected response format. Content-Type: {content_type}"
454
+ )
455
+
456
+ except requests.exceptions.RequestException as e:
457
+ retry_count += 1
458
+ if retry_count >= max_retries:
459
+ raise exceptions.FailedToGenerateResponseError(
460
+ f"Failed to generate audio for chunk {part_number} after {max_retries} retries: {e}"
461
+ )
462
+ if verbose:
463
+ print(f"[debug] Retrying chunk {part_number} (attempt {retry_count + 1})")
464
+ time.sleep(1) # Brief delay before retry
465
+
466
+ # Process chunks concurrently for better performance
467
+ audio_chunks = []
468
+ if len(sentences) > 1:
469
+ with ThreadPoolExecutor(max_workers=3) as executor:
470
+ future_to_chunk = {
471
+ executor.submit(generate_audio_for_chunk, sentence, i): i
472
+ for i, sentence in enumerate(sentences)
473
+ }
474
+
475
+ for future in as_completed(future_to_chunk):
476
+ try:
477
+ chunk_number, audio_data = future.result()
478
+ audio_chunks.append((chunk_number, audio_data))
479
+ except Exception as e:
480
+ if verbose:
481
+ print(f"[debug] Error processing chunk: {e}")
482
+ raise
483
+ else:
484
+ # Single sentence, process directly
485
+ chunk_number, audio_data = generate_audio_for_chunk(sentences[0], 0)
486
+ audio_chunks.append((chunk_number, audio_data))
487
+
488
+ # Sort chunks by their original order and combine
489
+ audio_chunks.sort(key=lambda x: x[0])
490
+ combined_audio = b''.join([chunk[1] for chunk in audio_chunks])
491
+
492
+ # Save combined audio to file
493
+ try:
494
+ with open(filename, 'wb') as f:
495
+ f.write(combined_audio)
496
+ if verbose:
497
+ print(f"[debug] Audio saved to: {filename}")
498
+ return filename.as_posix()
499
+ except IOError as e:
500
+ raise exceptions.FailedToGenerateResponseError(f"Failed to save audio file: {e}")
501
+
502
+
503
+ class SpeechMaStreamingResponse:
504
+ """Context manager for streaming SpeechMa TTS responses."""
505
+
506
+ def __init__(self, client: SpeechMaTTS):
507
+ self.client = client
508
+
509
+ def __enter__(self):
510
+ return self
511
+
512
+ def __exit__(self, exc_type, exc_val, exc_tb):
513
+ pass
514
+
515
+ def create_speech(
516
+ self,
517
+ input: str,
518
+ voice: str = "emma",
519
+ model: str = "gpt-4o-mini-tts",
520
+ response_format: str = "mp3",
521
+ speed: float = 1.0,
522
+ instructions: str = None,
523
+ **kwargs
524
+ ):
525
+ """
526
+ Create speech with streaming response simulation.
527
+
528
+ Note: SpeechMa doesn't support true streaming, so this returns
529
+ the complete audio data wrapped in a BytesIO object.
530
+
531
+ Args:
532
+ input (str): Text to convert to speech
533
+ voice (str): Voice to use
534
+ model (str): TTS model
535
+ response_format (str): Audio format
536
+ speed (float): Speech speed
537
+ instructions (str): Voice instructions
538
+ **kwargs: Additional parameters
539
+
540
+ Returns:
541
+ BytesIO: Audio data stream
542
+ """
543
+ audio_data = self.client.create_speech(
544
+ input=input,
545
+ voice=voice,
546
+ model=model,
547
+ response_format=response_format,
548
+ speed=speed,
549
+ instructions=instructions,
550
+ **kwargs
551
+ )
552
+ return BytesIO(audio_data)
553
+
554
+
555
+ # Example usage and testing
556
+ if __name__ == "__main__":
557
+ # Initialize the SpeechMa TTS client
558
+ speechma = SpeechMaTTS()
559
+
560
+ # Example 1: Basic usage with legacy method
561
+ print("=== Example 1: Basic TTS ===")
562
+ text = "Hello, this is a test of the SpeechMa text-to-speech API."
563
+ try:
564
+ audio_file = speechma.tts(text, voice="emma", verbose=True)
565
+ print(f"Audio saved to: {audio_file}")
566
+ except Exception as e:
567
+ print(f"Error: {e}")
568
+
569
+ # Example 2: OpenAI-compatible interface
570
+ print("\n=== Example 2: OpenAI-compatible interface ===")
571
+ try:
572
+ audio_data = speechma.create_speech(
573
+ input="This demonstrates the OpenAI-compatible interface.",
574
+ voice="brian",
575
+ model="tts-1-hd",
576
+ response_format="mp3",
577
+ speed=1.2
578
+ )
579
+ print(f"Generated {len(audio_data)} bytes of audio data")
580
+
581
+ # Save to file
582
+ with open("openai_compatible_test.mp3", "wb") as f:
583
+ f.write(audio_data)
584
+ print("Audio saved to: openai_compatible_test.mp3")
585
+ except Exception as e:
586
+ print(f"Error: {e}")
587
+
588
+ # Example 3: Streaming response context manager
589
+ print("\n=== Example 3: Streaming response ===")
590
+ try:
591
+ with speechma.with_streaming_response() as streaming:
592
+ audio_stream = streaming.create_speech(
593
+ input="This demonstrates streaming response handling.",
594
+ voice="aria",
595
+ model="gpt-4o-mini-tts"
596
+ )
597
+ audio_data = audio_stream.read()
598
+ print(f"Streamed {len(audio_data)} bytes of audio data")
599
+ except Exception as e:
600
+ print(f"Error: {e}")
601
+
602
+ # Example 4: Voice and model validation
603
+ print("\n=== Example 4: Parameter validation ===")
604
+ try:
605
+ # Test supported voices
606
+ print("Supported voices:", speechma.SUPPORTED_VOICES[:5], "...")
607
+ print("Supported models:", speechma.SUPPORTED_MODELS)
608
+
609
+ # Test with different parameters
610
+ audio_file = speechma.tts(
611
+ text="Testing different voice parameters.",
612
+ voice="christopher",
613
+ model="tts-1",
614
+ pitch=2,
615
+ rate=-1,
616
+ verbose=True
617
+ )
618
+ print(f"Audio with custom parameters saved to: {audio_file}")
619
+ except Exception as e:
620
+ print(f"Error: {e}")