webscout 8.3.4__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.
- webscout/AIutel.py +52 -1016
- webscout/Bard.py +12 -6
- webscout/DWEBS.py +66 -57
- webscout/Provider/AISEARCH/PERPLEXED_search.py +214 -0
- webscout/Provider/AISEARCH/__init__.py +11 -10
- webscout/Provider/AISEARCH/felo_search.py +7 -3
- webscout/Provider/AISEARCH/scira_search.py +2 -0
- webscout/Provider/AISEARCH/stellar_search.py +53 -8
- webscout/Provider/Deepinfra.py +13 -1
- webscout/Provider/Flowith.py +6 -1
- webscout/Provider/GithubChat.py +1 -0
- webscout/Provider/GptOss.py +207 -0
- webscout/Provider/Kimi.py +445 -0
- webscout/Provider/Netwrck.py +3 -6
- webscout/Provider/OPENAI/README.md +2 -1
- webscout/Provider/OPENAI/TogetherAI.py +12 -8
- webscout/Provider/OPENAI/TwoAI.py +94 -1
- webscout/Provider/OPENAI/__init__.py +4 -4
- webscout/Provider/OPENAI/copilot.py +20 -4
- webscout/Provider/OPENAI/deepinfra.py +12 -0
- webscout/Provider/OPENAI/e2b.py +60 -8
- webscout/Provider/OPENAI/flowith.py +4 -3
- webscout/Provider/OPENAI/generate_api_key.py +48 -0
- webscout/Provider/OPENAI/gptoss.py +288 -0
- webscout/Provider/OPENAI/kimi.py +469 -0
- webscout/Provider/OPENAI/netwrck.py +8 -12
- webscout/Provider/OPENAI/refact.py +274 -0
- webscout/Provider/OPENAI/scirachat.py +4 -0
- webscout/Provider/OPENAI/textpollinations.py +11 -10
- webscout/Provider/OPENAI/toolbaz.py +1 -0
- webscout/Provider/OPENAI/venice.py +1 -0
- webscout/Provider/Perplexitylabs.py +163 -147
- webscout/Provider/Qodo.py +30 -6
- webscout/Provider/TTI/__init__.py +1 -0
- webscout/Provider/TTI/bing.py +14 -2
- webscout/Provider/TTI/together.py +11 -9
- webscout/Provider/TTI/venice.py +368 -0
- webscout/Provider/TTS/README.md +0 -1
- webscout/Provider/TTS/__init__.py +0 -1
- webscout/Provider/TTS/base.py +479 -159
- webscout/Provider/TTS/deepgram.py +409 -156
- webscout/Provider/TTS/elevenlabs.py +425 -111
- webscout/Provider/TTS/freetts.py +317 -140
- webscout/Provider/TTS/gesserit.py +192 -128
- webscout/Provider/TTS/murfai.py +248 -113
- webscout/Provider/TTS/openai_fm.py +347 -129
- webscout/Provider/TTS/speechma.py +620 -586
- webscout/Provider/TextPollinationsAI.py +11 -10
- webscout/Provider/TogetherAI.py +12 -4
- webscout/Provider/TwoAI.py +96 -2
- webscout/Provider/TypliAI.py +33 -27
- webscout/Provider/UNFINISHED/VercelAIGateway.py +339 -0
- webscout/Provider/UNFINISHED/fetch_together_models.py +6 -11
- webscout/Provider/Venice.py +1 -0
- webscout/Provider/WiseCat.py +18 -20
- webscout/Provider/__init__.py +2 -96
- webscout/Provider/cerebras.py +83 -33
- webscout/Provider/copilot.py +42 -23
- webscout/Provider/scira_chat.py +4 -0
- webscout/Provider/toolbaz.py +6 -10
- webscout/Provider/typefully.py +1 -11
- webscout/__init__.py +3 -15
- webscout/auth/__init__.py +19 -4
- webscout/auth/api_key_manager.py +189 -189
- webscout/auth/auth_system.py +25 -40
- webscout/auth/config.py +105 -6
- webscout/auth/database.py +377 -22
- webscout/auth/models.py +185 -130
- webscout/auth/request_processing.py +175 -11
- webscout/auth/routes.py +99 -2
- webscout/auth/server.py +9 -2
- webscout/auth/simple_logger.py +236 -0
- webscout/conversation.py +22 -20
- webscout/sanitize.py +1078 -0
- webscout/scout/README.md +20 -23
- webscout/scout/core/crawler.py +125 -38
- webscout/scout/core/scout.py +26 -5
- webscout/version.py +1 -1
- webscout/webscout_search.py +13 -6
- webscout/webscout_search_async.py +10 -8
- webscout/yep_search.py +13 -5
- {webscout-8.3.4.dist-info → webscout-8.3.6.dist-info}/METADATA +10 -149
- {webscout-8.3.4.dist-info → webscout-8.3.6.dist-info}/RECORD +88 -87
- webscout/Provider/Glider.py +0 -225
- webscout/Provider/OPENAI/README_AUTOPROXY.md +0 -238
- webscout/Provider/OPENAI/c4ai.py +0 -394
- webscout/Provider/OPENAI/glider.py +0 -330
- webscout/Provider/OPENAI/typegpt.py +0 -368
- webscout/Provider/OPENAI/uncovrAI.py +0 -477
- webscout/Provider/TTS/sthir.py +0 -94
- webscout/Provider/WritingMate.py +0 -273
- webscout/Provider/typegpt.py +0 -284
- webscout/Provider/uncovr.py +0 -333
- /webscout/Provider/{samurai.py → UNFINISHED/samurai.py} +0 -0
- {webscout-8.3.4.dist-info → webscout-8.3.6.dist-info}/WHEEL +0 -0
- {webscout-8.3.4.dist-info → webscout-8.3.6.dist-info}/entry_points.txt +0 -0
- {webscout-8.3.4.dist-info → webscout-8.3.6.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.3.4.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
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
from
|
|
9
|
-
from webscout
|
|
10
|
-
from webscout.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
|
|
73
|
-
"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
"
|
|
84
|
-
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
|
|
100
|
-
"
|
|
101
|
-
"
|
|
102
|
-
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
|
|
109
|
-
"
|
|
110
|
-
"
|
|
111
|
-
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
|
|
121
|
-
"
|
|
122
|
-
"
|
|
123
|
-
"
|
|
124
|
-
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
|
|
128
|
-
"
|
|
129
|
-
"
|
|
130
|
-
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
|
|
134
|
-
"
|
|
135
|
-
"
|
|
136
|
-
|
|
137
|
-
"
|
|
138
|
-
"
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
"
|
|
142
|
-
|
|
143
|
-
"
|
|
144
|
-
"
|
|
145
|
-
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
|
|
149
|
-
"
|
|
150
|
-
"
|
|
151
|
-
|
|
152
|
-
"
|
|
153
|
-
"
|
|
154
|
-
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
|
|
161
|
-
"
|
|
162
|
-
"
|
|
163
|
-
|
|
164
|
-
"
|
|
165
|
-
"
|
|
166
|
-
"
|
|
167
|
-
"
|
|
168
|
-
"
|
|
169
|
-
"
|
|
170
|
-
|
|
171
|
-
"
|
|
172
|
-
|
|
173
|
-
"
|
|
174
|
-
"
|
|
175
|
-
"
|
|
176
|
-
|
|
177
|
-
"
|
|
178
|
-
"
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
"
|
|
182
|
-
"
|
|
183
|
-
|
|
184
|
-
"
|
|
185
|
-
"
|
|
186
|
-
"
|
|
187
|
-
"
|
|
188
|
-
#
|
|
189
|
-
"
|
|
190
|
-
"
|
|
191
|
-
|
|
192
|
-
"
|
|
193
|
-
"
|
|
194
|
-
"
|
|
195
|
-
|
|
196
|
-
"
|
|
197
|
-
"
|
|
198
|
-
|
|
199
|
-
"
|
|
200
|
-
"
|
|
201
|
-
|
|
202
|
-
"
|
|
203
|
-
"
|
|
204
|
-
"
|
|
205
|
-
"
|
|
206
|
-
|
|
207
|
-
"
|
|
208
|
-
"
|
|
209
|
-
|
|
210
|
-
"
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
"
|
|
214
|
-
"
|
|
215
|
-
|
|
216
|
-
"
|
|
217
|
-
"
|
|
218
|
-
|
|
219
|
-
"
|
|
220
|
-
"
|
|
221
|
-
|
|
222
|
-
"
|
|
223
|
-
"
|
|
224
|
-
|
|
225
|
-
"
|
|
226
|
-
"
|
|
227
|
-
|
|
228
|
-
"
|
|
229
|
-
"
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
#
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
"
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
"
|
|
284
|
-
#
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
"
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
"
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
#
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
#
|
|
395
|
-
"
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
#
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
#
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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}")
|