webscout 8.2.9__py3-none-any.whl → 2026.1.19__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.
- webscout/AIauto.py +524 -251
- webscout/AIbase.py +247 -319
- webscout/AIutel.py +68 -703
- webscout/Bard.py +1072 -1026
- webscout/Extra/GitToolkit/__init__.py +10 -10
- webscout/Extra/GitToolkit/gitapi/__init__.py +20 -12
- webscout/Extra/GitToolkit/gitapi/gist.py +142 -0
- webscout/Extra/GitToolkit/gitapi/organization.py +91 -0
- webscout/Extra/GitToolkit/gitapi/repository.py +308 -195
- webscout/Extra/GitToolkit/gitapi/search.py +162 -0
- webscout/Extra/GitToolkit/gitapi/trending.py +236 -0
- webscout/Extra/GitToolkit/gitapi/user.py +128 -96
- webscout/Extra/GitToolkit/gitapi/utils.py +82 -62
- webscout/Extra/YTToolkit/README.md +443 -375
- webscout/Extra/YTToolkit/YTdownloader.py +953 -957
- webscout/Extra/YTToolkit/__init__.py +3 -3
- webscout/Extra/YTToolkit/transcriber.py +595 -476
- webscout/Extra/YTToolkit/ytapi/README.md +230 -44
- webscout/Extra/YTToolkit/ytapi/__init__.py +22 -6
- webscout/Extra/YTToolkit/ytapi/captions.py +190 -0
- webscout/Extra/YTToolkit/ytapi/channel.py +302 -307
- webscout/Extra/YTToolkit/ytapi/errors.py +13 -13
- webscout/Extra/YTToolkit/ytapi/extras.py +178 -118
- webscout/Extra/YTToolkit/ytapi/hashtag.py +120 -0
- webscout/Extra/YTToolkit/ytapi/https.py +89 -88
- webscout/Extra/YTToolkit/ytapi/patterns.py +61 -61
- webscout/Extra/YTToolkit/ytapi/playlist.py +59 -59
- webscout/Extra/YTToolkit/ytapi/pool.py +8 -8
- webscout/Extra/YTToolkit/ytapi/query.py +143 -40
- webscout/Extra/YTToolkit/ytapi/shorts.py +122 -0
- webscout/Extra/YTToolkit/ytapi/stream.py +68 -63
- webscout/Extra/YTToolkit/ytapi/suggestions.py +97 -0
- webscout/Extra/YTToolkit/ytapi/utils.py +66 -62
- webscout/Extra/YTToolkit/ytapi/video.py +403 -232
- webscout/Extra/__init__.py +2 -3
- webscout/Extra/gguf.py +1298 -684
- webscout/Extra/tempmail/README.md +487 -487
- webscout/Extra/tempmail/__init__.py +28 -28
- webscout/Extra/tempmail/async_utils.py +143 -141
- webscout/Extra/tempmail/base.py +172 -161
- webscout/Extra/tempmail/cli.py +191 -187
- webscout/Extra/tempmail/emailnator.py +88 -84
- webscout/Extra/tempmail/mail_tm.py +378 -361
- webscout/Extra/tempmail/temp_mail_io.py +304 -292
- webscout/Extra/weather.py +196 -194
- webscout/Extra/weather_ascii.py +17 -15
- webscout/Provider/AISEARCH/PERPLEXED_search.py +175 -0
- webscout/Provider/AISEARCH/Perplexity.py +292 -333
- webscout/Provider/AISEARCH/README.md +106 -279
- webscout/Provider/AISEARCH/__init__.py +16 -9
- webscout/Provider/AISEARCH/brave_search.py +298 -0
- webscout/Provider/AISEARCH/iask_search.py +357 -410
- webscout/Provider/AISEARCH/monica_search.py +200 -220
- webscout/Provider/AISEARCH/webpilotai_search.py +242 -255
- webscout/Provider/Algion.py +413 -0
- webscout/Provider/Andi.py +74 -69
- webscout/Provider/Apriel.py +313 -0
- webscout/Provider/Ayle.py +323 -0
- webscout/Provider/ChatSandbox.py +329 -342
- webscout/Provider/ClaudeOnline.py +365 -0
- webscout/Provider/Cohere.py +232 -208
- webscout/Provider/DeepAI.py +367 -0
- webscout/Provider/Deepinfra.py +467 -340
- webscout/Provider/EssentialAI.py +217 -0
- webscout/Provider/ExaAI.py +274 -261
- webscout/Provider/Gemini.py +175 -169
- webscout/Provider/GithubChat.py +385 -369
- webscout/Provider/Gradient.py +286 -0
- webscout/Provider/Groq.py +556 -801
- webscout/Provider/HadadXYZ.py +323 -0
- webscout/Provider/HeckAI.py +392 -375
- webscout/Provider/HuggingFace.py +387 -0
- webscout/Provider/IBM.py +340 -0
- webscout/Provider/Jadve.py +317 -291
- webscout/Provider/K2Think.py +306 -0
- webscout/Provider/Koboldai.py +221 -384
- webscout/Provider/Netwrck.py +273 -270
- webscout/Provider/Nvidia.py +310 -0
- webscout/Provider/OPENAI/DeepAI.py +489 -0
- webscout/Provider/OPENAI/K2Think.py +423 -0
- webscout/Provider/OPENAI/PI.py +463 -0
- webscout/Provider/OPENAI/README.md +890 -952
- webscout/Provider/OPENAI/TogetherAI.py +405 -0
- webscout/Provider/OPENAI/TwoAI.py +255 -357
- webscout/Provider/OPENAI/__init__.py +148 -40
- webscout/Provider/OPENAI/ai4chat.py +348 -293
- webscout/Provider/OPENAI/akashgpt.py +436 -0
- webscout/Provider/OPENAI/algion.py +303 -0
- webscout/Provider/OPENAI/{exachat.py → ayle.py} +365 -444
- webscout/Provider/OPENAI/base.py +253 -249
- webscout/Provider/OPENAI/cerebras.py +296 -0
- webscout/Provider/OPENAI/chatgpt.py +870 -556
- webscout/Provider/OPENAI/chatsandbox.py +233 -173
- webscout/Provider/OPENAI/deepinfra.py +403 -322
- webscout/Provider/OPENAI/e2b.py +2370 -1414
- webscout/Provider/OPENAI/elmo.py +278 -0
- webscout/Provider/OPENAI/exaai.py +452 -417
- webscout/Provider/OPENAI/freeassist.py +446 -0
- webscout/Provider/OPENAI/gradient.py +448 -0
- webscout/Provider/OPENAI/groq.py +380 -364
- webscout/Provider/OPENAI/hadadxyz.py +292 -0
- webscout/Provider/OPENAI/heckai.py +333 -308
- webscout/Provider/OPENAI/huggingface.py +321 -0
- webscout/Provider/OPENAI/ibm.py +425 -0
- webscout/Provider/OPENAI/llmchat.py +253 -0
- webscout/Provider/OPENAI/llmchatco.py +378 -335
- webscout/Provider/OPENAI/meta.py +541 -0
- webscout/Provider/OPENAI/netwrck.py +374 -357
- webscout/Provider/OPENAI/nvidia.py +317 -0
- webscout/Provider/OPENAI/oivscode.py +348 -287
- webscout/Provider/OPENAI/openrouter.py +328 -0
- webscout/Provider/OPENAI/pydantic_imports.py +1 -172
- webscout/Provider/OPENAI/sambanova.py +397 -0
- webscout/Provider/OPENAI/sonus.py +305 -304
- webscout/Provider/OPENAI/textpollinations.py +370 -339
- webscout/Provider/OPENAI/toolbaz.py +375 -413
- webscout/Provider/OPENAI/typefully.py +419 -355
- webscout/Provider/OPENAI/typliai.py +279 -0
- webscout/Provider/OPENAI/utils.py +314 -318
- webscout/Provider/OPENAI/wisecat.py +359 -387
- webscout/Provider/OPENAI/writecream.py +185 -163
- webscout/Provider/OPENAI/x0gpt.py +462 -365
- webscout/Provider/OPENAI/zenmux.py +380 -0
- webscout/Provider/OpenRouter.py +386 -0
- webscout/Provider/Openai.py +337 -496
- webscout/Provider/PI.py +443 -429
- webscout/Provider/QwenLM.py +346 -254
- webscout/Provider/STT/__init__.py +28 -0
- webscout/Provider/STT/base.py +303 -0
- webscout/Provider/STT/elevenlabs.py +264 -0
- webscout/Provider/Sambanova.py +317 -0
- webscout/Provider/TTI/README.md +69 -82
- webscout/Provider/TTI/__init__.py +37 -7
- webscout/Provider/TTI/base.py +147 -64
- webscout/Provider/TTI/claudeonline.py +393 -0
- webscout/Provider/TTI/magicstudio.py +292 -201
- webscout/Provider/TTI/miragic.py +180 -0
- webscout/Provider/TTI/pollinations.py +331 -221
- webscout/Provider/TTI/together.py +334 -0
- webscout/Provider/TTI/utils.py +14 -11
- webscout/Provider/TTS/README.md +186 -192
- webscout/Provider/TTS/__init__.py +43 -10
- webscout/Provider/TTS/base.py +523 -159
- webscout/Provider/TTS/deepgram.py +286 -156
- webscout/Provider/TTS/elevenlabs.py +189 -111
- webscout/Provider/TTS/freetts.py +218 -0
- webscout/Provider/TTS/murfai.py +288 -113
- webscout/Provider/TTS/openai_fm.py +364 -129
- webscout/Provider/TTS/parler.py +203 -111
- webscout/Provider/TTS/qwen.py +334 -0
- webscout/Provider/TTS/sherpa.py +286 -0
- webscout/Provider/TTS/speechma.py +693 -580
- webscout/Provider/TTS/streamElements.py +275 -333
- webscout/Provider/TTS/utils.py +280 -280
- webscout/Provider/TextPollinationsAI.py +331 -308
- webscout/Provider/TogetherAI.py +450 -0
- webscout/Provider/TwoAI.py +309 -475
- webscout/Provider/TypliAI.py +311 -305
- webscout/Provider/UNFINISHED/ChatHub.py +219 -209
- webscout/Provider/{OPENAI/glider.py → UNFINISHED/ChutesAI.py} +331 -326
- webscout/Provider/{GizAI.py → UNFINISHED/GizAI.py} +300 -295
- webscout/Provider/{Marcus.py → UNFINISHED/Marcus.py} +218 -198
- webscout/Provider/UNFINISHED/Qodo.py +481 -0
- webscout/Provider/{MCPCore.py → UNFINISHED/XenAI.py} +330 -315
- webscout/Provider/UNFINISHED/Youchat.py +347 -330
- webscout/Provider/UNFINISHED/aihumanizer.py +41 -0
- webscout/Provider/UNFINISHED/grammerchecker.py +37 -0
- webscout/Provider/UNFINISHED/liner.py +342 -0
- webscout/Provider/UNFINISHED/liner_api_request.py +246 -263
- webscout/Provider/{samurai.py → UNFINISHED/samurai.py} +231 -224
- webscout/Provider/WiseCat.py +256 -233
- webscout/Provider/WrDoChat.py +390 -370
- webscout/Provider/__init__.py +115 -174
- webscout/Provider/ai4chat.py +181 -174
- webscout/Provider/akashgpt.py +330 -335
- webscout/Provider/cerebras.py +397 -290
- webscout/Provider/cleeai.py +236 -213
- webscout/Provider/elmo.py +291 -283
- webscout/Provider/geminiapi.py +343 -208
- webscout/Provider/julius.py +245 -223
- webscout/Provider/learnfastai.py +333 -325
- webscout/Provider/llama3mitril.py +230 -215
- webscout/Provider/llmchat.py +308 -258
- webscout/Provider/llmchatco.py +321 -306
- webscout/Provider/meta.py +996 -801
- webscout/Provider/oivscode.py +332 -309
- webscout/Provider/searchchat.py +316 -292
- webscout/Provider/sonus.py +264 -258
- webscout/Provider/toolbaz.py +359 -353
- webscout/Provider/turboseek.py +332 -266
- webscout/Provider/typefully.py +262 -202
- webscout/Provider/x0gpt.py +332 -299
- webscout/__init__.py +31 -39
- webscout/__main__.py +5 -5
- webscout/cli.py +585 -524
- webscout/client.py +1497 -70
- webscout/conversation.py +140 -436
- webscout/exceptions.py +383 -362
- webscout/litagent/__init__.py +29 -29
- webscout/litagent/agent.py +492 -455
- webscout/litagent/constants.py +60 -60
- webscout/models.py +505 -181
- webscout/optimizers.py +74 -420
- webscout/prompt_manager.py +376 -288
- webscout/sanitize.py +1514 -0
- webscout/scout/README.md +452 -404
- webscout/scout/__init__.py +8 -8
- webscout/scout/core/__init__.py +7 -7
- webscout/scout/core/crawler.py +330 -210
- webscout/scout/core/scout.py +800 -607
- webscout/scout/core/search_result.py +51 -96
- webscout/scout/core/text_analyzer.py +64 -63
- webscout/scout/core/text_utils.py +412 -277
- webscout/scout/core/web_analyzer.py +54 -52
- webscout/scout/element.py +872 -478
- webscout/scout/parsers/__init__.py +70 -69
- webscout/scout/parsers/html5lib_parser.py +182 -172
- webscout/scout/parsers/html_parser.py +238 -236
- webscout/scout/parsers/lxml_parser.py +203 -178
- webscout/scout/utils.py +38 -37
- webscout/search/__init__.py +47 -0
- webscout/search/base.py +201 -0
- webscout/search/bing_main.py +45 -0
- webscout/search/brave_main.py +92 -0
- webscout/search/duckduckgo_main.py +57 -0
- webscout/search/engines/__init__.py +127 -0
- webscout/search/engines/bing/__init__.py +15 -0
- webscout/search/engines/bing/base.py +35 -0
- webscout/search/engines/bing/images.py +114 -0
- webscout/search/engines/bing/news.py +96 -0
- webscout/search/engines/bing/suggestions.py +36 -0
- webscout/search/engines/bing/text.py +109 -0
- webscout/search/engines/brave/__init__.py +19 -0
- webscout/search/engines/brave/base.py +47 -0
- webscout/search/engines/brave/images.py +213 -0
- webscout/search/engines/brave/news.py +353 -0
- webscout/search/engines/brave/suggestions.py +318 -0
- webscout/search/engines/brave/text.py +167 -0
- webscout/search/engines/brave/videos.py +364 -0
- webscout/search/engines/duckduckgo/__init__.py +25 -0
- webscout/search/engines/duckduckgo/answers.py +80 -0
- webscout/search/engines/duckduckgo/base.py +189 -0
- webscout/search/engines/duckduckgo/images.py +100 -0
- webscout/search/engines/duckduckgo/maps.py +183 -0
- webscout/search/engines/duckduckgo/news.py +70 -0
- webscout/search/engines/duckduckgo/suggestions.py +22 -0
- webscout/search/engines/duckduckgo/text.py +221 -0
- webscout/search/engines/duckduckgo/translate.py +48 -0
- webscout/search/engines/duckduckgo/videos.py +80 -0
- webscout/search/engines/duckduckgo/weather.py +84 -0
- webscout/search/engines/mojeek.py +61 -0
- webscout/search/engines/wikipedia.py +77 -0
- webscout/search/engines/yahoo/__init__.py +41 -0
- webscout/search/engines/yahoo/answers.py +19 -0
- webscout/search/engines/yahoo/base.py +34 -0
- webscout/search/engines/yahoo/images.py +323 -0
- webscout/search/engines/yahoo/maps.py +19 -0
- webscout/search/engines/yahoo/news.py +258 -0
- webscout/search/engines/yahoo/suggestions.py +140 -0
- webscout/search/engines/yahoo/text.py +273 -0
- webscout/search/engines/yahoo/translate.py +19 -0
- webscout/search/engines/yahoo/videos.py +302 -0
- webscout/search/engines/yahoo/weather.py +220 -0
- webscout/search/engines/yandex.py +67 -0
- webscout/search/engines/yep/__init__.py +13 -0
- webscout/search/engines/yep/base.py +34 -0
- webscout/search/engines/yep/images.py +101 -0
- webscout/search/engines/yep/suggestions.py +38 -0
- webscout/search/engines/yep/text.py +99 -0
- webscout/search/http_client.py +172 -0
- webscout/search/results.py +141 -0
- webscout/search/yahoo_main.py +57 -0
- webscout/search/yep_main.py +48 -0
- webscout/server/__init__.py +48 -0
- webscout/server/config.py +78 -0
- webscout/server/exceptions.py +69 -0
- webscout/server/providers.py +286 -0
- webscout/server/request_models.py +131 -0
- webscout/server/request_processing.py +404 -0
- webscout/server/routes.py +642 -0
- webscout/server/server.py +351 -0
- webscout/server/ui_templates.py +1171 -0
- webscout/swiftcli/__init__.py +79 -95
- webscout/swiftcli/core/__init__.py +7 -7
- webscout/swiftcli/core/cli.py +574 -297
- webscout/swiftcli/core/context.py +98 -104
- webscout/swiftcli/core/group.py +268 -241
- webscout/swiftcli/decorators/__init__.py +28 -28
- webscout/swiftcli/decorators/command.py +243 -221
- webscout/swiftcli/decorators/options.py +247 -220
- webscout/swiftcli/decorators/output.py +392 -252
- webscout/swiftcli/exceptions.py +21 -21
- webscout/swiftcli/plugins/__init__.py +9 -9
- webscout/swiftcli/plugins/base.py +134 -135
- webscout/swiftcli/plugins/manager.py +269 -269
- webscout/swiftcli/utils/__init__.py +58 -59
- webscout/swiftcli/utils/formatting.py +251 -252
- webscout/swiftcli/utils/parsing.py +368 -267
- webscout/update_checker.py +280 -136
- webscout/utils.py +28 -14
- webscout/version.py +2 -1
- webscout/version.py.bak +3 -0
- webscout/zeroart/__init__.py +218 -135
- webscout/zeroart/base.py +70 -66
- webscout/zeroart/effects.py +155 -101
- webscout/zeroart/fonts.py +1799 -1239
- webscout-2026.1.19.dist-info/METADATA +638 -0
- webscout-2026.1.19.dist-info/RECORD +312 -0
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/WHEEL +1 -1
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/entry_points.txt +1 -1
- webscout/DWEBS.py +0 -520
- webscout/Extra/Act.md +0 -309
- webscout/Extra/GitToolkit/gitapi/README.md +0 -110
- webscout/Extra/autocoder/__init__.py +0 -9
- webscout/Extra/autocoder/autocoder.py +0 -1105
- webscout/Extra/autocoder/autocoder_utiles.py +0 -332
- webscout/Extra/gguf.md +0 -430
- webscout/Extra/weather.md +0 -281
- webscout/Litlogger/README.md +0 -10
- webscout/Litlogger/__init__.py +0 -15
- webscout/Litlogger/formats.py +0 -4
- webscout/Litlogger/handlers.py +0 -103
- webscout/Litlogger/levels.py +0 -13
- webscout/Litlogger/logger.py +0 -92
- webscout/Provider/AI21.py +0 -177
- webscout/Provider/AISEARCH/DeepFind.py +0 -254
- webscout/Provider/AISEARCH/felo_search.py +0 -202
- webscout/Provider/AISEARCH/genspark_search.py +0 -324
- webscout/Provider/AISEARCH/hika_search.py +0 -186
- webscout/Provider/AISEARCH/scira_search.py +0 -298
- webscout/Provider/Aitopia.py +0 -316
- webscout/Provider/AllenAI.py +0 -440
- webscout/Provider/Blackboxai.py +0 -791
- webscout/Provider/ChatGPTClone.py +0 -237
- webscout/Provider/ChatGPTGratis.py +0 -194
- webscout/Provider/Cloudflare.py +0 -324
- webscout/Provider/ExaChat.py +0 -358
- webscout/Provider/Flowith.py +0 -217
- webscout/Provider/FreeGemini.py +0 -250
- webscout/Provider/Glider.py +0 -225
- webscout/Provider/HF_space/__init__.py +0 -0
- webscout/Provider/HF_space/qwen_qwen2.py +0 -206
- webscout/Provider/HuggingFaceChat.py +0 -469
- webscout/Provider/Hunyuan.py +0 -283
- webscout/Provider/LambdaChat.py +0 -411
- webscout/Provider/Llama3.py +0 -259
- webscout/Provider/Nemotron.py +0 -218
- webscout/Provider/OLLAMA.py +0 -396
- webscout/Provider/OPENAI/BLACKBOXAI.py +0 -766
- webscout/Provider/OPENAI/Cloudflare.py +0 -378
- webscout/Provider/OPENAI/FreeGemini.py +0 -283
- webscout/Provider/OPENAI/NEMOTRON.py +0 -232
- webscout/Provider/OPENAI/Qwen3.py +0 -283
- webscout/Provider/OPENAI/api.py +0 -969
- webscout/Provider/OPENAI/c4ai.py +0 -373
- webscout/Provider/OPENAI/chatgptclone.py +0 -494
- webscout/Provider/OPENAI/copilot.py +0 -242
- webscout/Provider/OPENAI/flowith.py +0 -162
- webscout/Provider/OPENAI/freeaichat.py +0 -359
- webscout/Provider/OPENAI/mcpcore.py +0 -389
- webscout/Provider/OPENAI/multichat.py +0 -376
- webscout/Provider/OPENAI/opkfc.py +0 -496
- webscout/Provider/OPENAI/scirachat.py +0 -477
- webscout/Provider/OPENAI/standardinput.py +0 -433
- webscout/Provider/OPENAI/typegpt.py +0 -364
- webscout/Provider/OPENAI/uncovrAI.py +0 -463
- webscout/Provider/OPENAI/venice.py +0 -431
- webscout/Provider/OPENAI/yep.py +0 -382
- webscout/Provider/OpenGPT.py +0 -209
- webscout/Provider/Perplexitylabs.py +0 -415
- webscout/Provider/Reka.py +0 -214
- webscout/Provider/StandardInput.py +0 -290
- webscout/Provider/TTI/aiarta.py +0 -365
- webscout/Provider/TTI/artbit.py +0 -0
- webscout/Provider/TTI/fastflux.py +0 -200
- webscout/Provider/TTI/piclumen.py +0 -203
- webscout/Provider/TTI/pixelmuse.py +0 -225
- webscout/Provider/TTS/gesserit.py +0 -128
- webscout/Provider/TTS/sthir.py +0 -94
- webscout/Provider/TeachAnything.py +0 -229
- webscout/Provider/UNFINISHED/puterjs.py +0 -635
- webscout/Provider/UNFINISHED/test_lmarena.py +0 -119
- webscout/Provider/Venice.py +0 -258
- webscout/Provider/VercelAI.py +0 -253
- webscout/Provider/Writecream.py +0 -246
- webscout/Provider/WritingMate.py +0 -269
- webscout/Provider/asksteve.py +0 -220
- webscout/Provider/chatglm.py +0 -215
- webscout/Provider/copilot.py +0 -425
- webscout/Provider/freeaichat.py +0 -285
- webscout/Provider/granite.py +0 -235
- webscout/Provider/hermes.py +0 -266
- webscout/Provider/koala.py +0 -170
- webscout/Provider/lmarena.py +0 -198
- webscout/Provider/multichat.py +0 -364
- webscout/Provider/scira_chat.py +0 -299
- webscout/Provider/scnet.py +0 -243
- webscout/Provider/talkai.py +0 -194
- webscout/Provider/typegpt.py +0 -289
- webscout/Provider/uncovr.py +0 -368
- webscout/Provider/yep.py +0 -389
- webscout/litagent/Readme.md +0 -276
- webscout/litprinter/__init__.py +0 -59
- webscout/swiftcli/Readme.md +0 -323
- webscout/tempid.py +0 -128
- webscout/webscout_search.py +0 -1184
- webscout/webscout_search_async.py +0 -654
- webscout/yep_search.py +0 -347
- webscout/zeroart/README.md +0 -89
- webscout-8.2.9.dist-info/METADATA +0 -1033
- webscout-8.2.9.dist-info/RECORD +0 -289
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/top_level.txt +0 -0
webscout/Provider/TTS/parler.py
CHANGED
|
@@ -1,111 +1,203 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
1
|
+
##################################################################################
|
|
2
|
+
## ParlerTTS Provider ##
|
|
3
|
+
##################################################################################
|
|
4
|
+
import json
|
|
5
|
+
import pathlib
|
|
6
|
+
import random
|
|
7
|
+
import string
|
|
8
|
+
import tempfile
|
|
9
|
+
from typing import Any, Optional, Union, cast
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
from litprinter import ic
|
|
13
|
+
|
|
14
|
+
from webscout import exceptions
|
|
15
|
+
from webscout.litagent import LitAgent
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from . import utils
|
|
19
|
+
from .base import BaseTTSProvider
|
|
20
|
+
except ImportError:
|
|
21
|
+
# Handle direct execution
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
|
|
25
|
+
from webscout.Provider.TTS.base import BaseTTSProvider
|
|
26
|
+
|
|
27
|
+
class ParlerTTS(BaseTTSProvider):
|
|
28
|
+
"""
|
|
29
|
+
Text-to-speech provider using the Parler-TTS API (Hugging Face Spaces).
|
|
30
|
+
|
|
31
|
+
Features:
|
|
32
|
+
- High-fidelity speech generation
|
|
33
|
+
- Controllable via simple text prompts (description)
|
|
34
|
+
- Manual polling logic for robustness
|
|
35
|
+
"""
|
|
36
|
+
required_auth = False
|
|
37
|
+
|
|
38
|
+
BASE_URL = "https://parler-tts-parler-tts.hf.space"
|
|
39
|
+
|
|
40
|
+
# Request headers
|
|
41
|
+
headers: dict[str, str] = {
|
|
42
|
+
"User-Agent": LitAgent().random(),
|
|
43
|
+
"origin": BASE_URL,
|
|
44
|
+
"referer": f"{BASE_URL}/",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
SUPPORTED_MODELS = ["parler-mini-v1", "parler-large-v1"]
|
|
48
|
+
|
|
49
|
+
def __init__(self, timeout: int = 120, proxy: Optional[str] = None):
|
|
50
|
+
"""
|
|
51
|
+
Initialize the ParlerTTS client.
|
|
52
|
+
"""
|
|
53
|
+
super().__init__()
|
|
54
|
+
self.timeout = timeout
|
|
55
|
+
self.proxy = proxy
|
|
56
|
+
self.default_model = "parler-mini-v1"
|
|
57
|
+
|
|
58
|
+
def _generate_session_hash(self) -> str:
|
|
59
|
+
return "".join(random.choices(string.ascii_lowercase + string.digits, k=10))
|
|
60
|
+
|
|
61
|
+
def tts(self, text: str, voice: Optional[str] = None, verbose: bool = False, **kwargs) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Convert text to speech using Parler-TTS API.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
text (str): The text to convert to speech
|
|
67
|
+
voice (str): The voice to use
|
|
68
|
+
verbose (bool): Whether to print debug information
|
|
69
|
+
**kwargs: Additional parameters
|
|
70
|
+
"""
|
|
71
|
+
# Extract parameters from kwargs with defaults
|
|
72
|
+
description = kwargs.get('description', "A female speaker delivers a slightly expressive and animated speech with a moderate speed. The recording features a low-pitch voice and very clear audio.")
|
|
73
|
+
use_large = kwargs.get('use_large', False)
|
|
74
|
+
response_format = kwargs.get('response_format', "wav")
|
|
75
|
+
verbose = verbose if verbose is not None else kwargs.get('verbose', True)
|
|
76
|
+
|
|
77
|
+
if not text:
|
|
78
|
+
raise ValueError("Input text must be a non-empty string")
|
|
79
|
+
|
|
80
|
+
session_hash = self._generate_session_hash()
|
|
81
|
+
filename = pathlib.Path(tempfile.NamedTemporaryFile(suffix=f".{response_format}", dir=self.temp_dir, delete=False).name)
|
|
82
|
+
|
|
83
|
+
if verbose:
|
|
84
|
+
ic.configureOutput(prefix='DEBUG| ')
|
|
85
|
+
ic(f"ParlerTTS: Generating speech for '{text[:20]}...'")
|
|
86
|
+
|
|
87
|
+
client_kwargs: dict[str, Any] = {"headers": self.headers, "timeout": self.timeout}
|
|
88
|
+
if self.proxy:
|
|
89
|
+
client_kwargs["proxy"] = self.proxy
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
with httpx.Client(**client_kwargs) as client:
|
|
93
|
+
# Step 1: Join the queue
|
|
94
|
+
join_url = f"{self.BASE_URL}/queue/join?__theme=system"
|
|
95
|
+
# fn_index 0 is for the main generation task
|
|
96
|
+
payload = {
|
|
97
|
+
"data": [text, description, use_large],
|
|
98
|
+
"event_data": None,
|
|
99
|
+
"fn_index": 0,
|
|
100
|
+
"trigger_id": 8,
|
|
101
|
+
"session_hash": session_hash
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
response = client.post(join_url, json=payload)
|
|
105
|
+
response.raise_for_status()
|
|
106
|
+
|
|
107
|
+
# Step 2: Poll for data
|
|
108
|
+
data_url = f"{self.BASE_URL}/queue/data?session_hash={session_hash}"
|
|
109
|
+
audio_url = None
|
|
110
|
+
|
|
111
|
+
# Gradio Spaces can take time to wake up or process
|
|
112
|
+
with client.stream("GET", data_url) as stream:
|
|
113
|
+
for line in stream.iter_lines():
|
|
114
|
+
if not line:
|
|
115
|
+
continue
|
|
116
|
+
if line.startswith("data: "):
|
|
117
|
+
try:
|
|
118
|
+
data = json.loads(line[6:])
|
|
119
|
+
except json.JSONDecodeError:
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
msg = data.get("msg")
|
|
123
|
+
if msg == "process_completed":
|
|
124
|
+
if data.get("success"):
|
|
125
|
+
output_data = data.get("output", {}).get("data", [])
|
|
126
|
+
if output_data:
|
|
127
|
+
audio_info = output_data[0]
|
|
128
|
+
path = audio_info["path"] if isinstance(audio_info, dict) else audio_info
|
|
129
|
+
audio_url = f"{self.BASE_URL}/file={path}"
|
|
130
|
+
break
|
|
131
|
+
else:
|
|
132
|
+
raise exceptions.FailedToGenerateResponseError(f"Generation failed: {data}")
|
|
133
|
+
elif msg == "queue_full":
|
|
134
|
+
raise exceptions.FailedToGenerateResponseError("Queue is full")
|
|
135
|
+
elif msg == "send_hash":
|
|
136
|
+
# Normal handshake
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
if not audio_url:
|
|
140
|
+
raise exceptions.FailedToGenerateResponseError("Failed to get audio URL from stream")
|
|
141
|
+
|
|
142
|
+
# Step 3: Download the audio file
|
|
143
|
+
audio_response = client.get(audio_url)
|
|
144
|
+
audio_response.raise_for_status()
|
|
145
|
+
|
|
146
|
+
with open(filename, "wb") as f:
|
|
147
|
+
f.write(audio_response.content)
|
|
148
|
+
|
|
149
|
+
if verbose:
|
|
150
|
+
ic.configureOutput(prefix='DEBUG| ')
|
|
151
|
+
ic(f"Speech generated successfully: {filename}")
|
|
152
|
+
|
|
153
|
+
return filename.as_posix()
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
if verbose:
|
|
157
|
+
ic.configureOutput(prefix='DEBUG| ')
|
|
158
|
+
ic(f"Error in ParlerTTS: {e}")
|
|
159
|
+
raise exceptions.FailedToGenerateResponseError(f"Failed to generate audio: {e}")
|
|
160
|
+
|
|
161
|
+
def create_speech(
|
|
162
|
+
self,
|
|
163
|
+
input_text: str,
|
|
164
|
+
model: Optional[str] = "parler-mini-v1",
|
|
165
|
+
voice: Optional[str] = None,
|
|
166
|
+
response_format: Optional[str] = "mp3",
|
|
167
|
+
instructions: Optional[str] = None,
|
|
168
|
+
verbose: bool = False
|
|
169
|
+
) -> str:
|
|
170
|
+
"""
|
|
171
|
+
OpenAI-compatible speech creation interface.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
input_text (str): The text to convert to speech
|
|
175
|
+
model (str): The TTS model to use
|
|
176
|
+
voice (str): The voice to use (not used by ParlerAI directly)
|
|
177
|
+
response_format (str): Audio format
|
|
178
|
+
instructions (str): Voice instructions (used as description)
|
|
179
|
+
verbose (bool): Whether to print debug information
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
str: Path to the generated audio file
|
|
183
|
+
"""
|
|
184
|
+
description = instructions or "A female speaker delivers a slightly expressive and animated speech with a moderate speed. The recording features a low-pitch voice and very clear audio."
|
|
185
|
+
use_large = (model == "parler-large-v1")
|
|
186
|
+
|
|
187
|
+
return self.tts(
|
|
188
|
+
text=input_text,
|
|
189
|
+
description=description,
|
|
190
|
+
use_large=use_large,
|
|
191
|
+
response_format=response_format or "mp3",
|
|
192
|
+
verbose=verbose
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if __name__ == "__main__":
|
|
196
|
+
tts = ParlerTTS()
|
|
197
|
+
try:
|
|
198
|
+
path = tts.tts("Testing Parler-TTS with manual polling.", verbose=True)
|
|
199
|
+
ic.configureOutput(prefix='INFO| ')
|
|
200
|
+
ic(f"Saved to {path}")
|
|
201
|
+
except Exception as e:
|
|
202
|
+
ic.configureOutput(prefix='ERROR| ')
|
|
203
|
+
ic(f"Error: {e}")
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
##################################################################################
|
|
2
|
+
## Qwen3-TTS Provider ##
|
|
3
|
+
##################################################################################
|
|
4
|
+
import json
|
|
5
|
+
import pathlib
|
|
6
|
+
import random
|
|
7
|
+
import string
|
|
8
|
+
import tempfile
|
|
9
|
+
from typing import Any, Generator, Optional, Union, cast
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
from litprinter import ic
|
|
13
|
+
|
|
14
|
+
from webscout import exceptions
|
|
15
|
+
from webscout.litagent import LitAgent
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from . import utils
|
|
19
|
+
from .base import BaseTTSProvider
|
|
20
|
+
except ImportError:
|
|
21
|
+
# Handle direct execution
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
|
|
25
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", ".."))
|
|
26
|
+
from webscout.Provider.TTS.base import BaseTTSProvider
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class QwenTTS(BaseTTSProvider):
|
|
30
|
+
"""
|
|
31
|
+
Text-to-speech provider using the Qwen3-TTS API (Hugging Face Spaces).
|
|
32
|
+
|
|
33
|
+
This provider follows the OpenAI TTS API structure with support for:
|
|
34
|
+
- Multiple TTS models (mapped to Gradio fn_index)
|
|
35
|
+
- 40+ high-quality voices across multiple languages
|
|
36
|
+
- Automatic language detection or manual selection
|
|
37
|
+
- Multiple output formats
|
|
38
|
+
- Streaming response simulation
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
required_auth = False
|
|
42
|
+
|
|
43
|
+
BASE_URL = "https://qwen-qwen3-tts-demo.hf.space"
|
|
44
|
+
|
|
45
|
+
# Request headers
|
|
46
|
+
headers: dict[str, str] = {
|
|
47
|
+
"User-Agent": LitAgent().random(),
|
|
48
|
+
"origin": BASE_URL,
|
|
49
|
+
"referer": f"{BASE_URL}/",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Override supported models
|
|
53
|
+
SUPPORTED_MODELS = ["qwen3-tts"]
|
|
54
|
+
|
|
55
|
+
# Supported voices
|
|
56
|
+
SUPPORTED_VOICES = [
|
|
57
|
+
"cherry",
|
|
58
|
+
"serena",
|
|
59
|
+
"ethan",
|
|
60
|
+
"chelsie",
|
|
61
|
+
"momo",
|
|
62
|
+
"vivian",
|
|
63
|
+
"moon",
|
|
64
|
+
"maia",
|
|
65
|
+
"kai",
|
|
66
|
+
"nofish",
|
|
67
|
+
"bella",
|
|
68
|
+
"jennifer",
|
|
69
|
+
"ryan",
|
|
70
|
+
"katerina",
|
|
71
|
+
"aiden",
|
|
72
|
+
"bodega",
|
|
73
|
+
"alek",
|
|
74
|
+
"dolce",
|
|
75
|
+
"sohee",
|
|
76
|
+
"ono_anna",
|
|
77
|
+
"lenn",
|
|
78
|
+
"sonrisa",
|
|
79
|
+
"emilien",
|
|
80
|
+
"andre",
|
|
81
|
+
"radio_gol",
|
|
82
|
+
"eldric_sage",
|
|
83
|
+
"mia",
|
|
84
|
+
"mochi",
|
|
85
|
+
"bellona",
|
|
86
|
+
"vincent",
|
|
87
|
+
"bunny",
|
|
88
|
+
"neil",
|
|
89
|
+
"elias",
|
|
90
|
+
"arthur",
|
|
91
|
+
"nini",
|
|
92
|
+
"ebona",
|
|
93
|
+
"seren",
|
|
94
|
+
"pip",
|
|
95
|
+
"stella",
|
|
96
|
+
"li",
|
|
97
|
+
"marcus",
|
|
98
|
+
"roy",
|
|
99
|
+
"peter",
|
|
100
|
+
"eric",
|
|
101
|
+
"rocky",
|
|
102
|
+
"kiki",
|
|
103
|
+
"sunny",
|
|
104
|
+
"jada",
|
|
105
|
+
"dylan",
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
# Voice mapping for API compatibility
|
|
109
|
+
voice_mapping = {
|
|
110
|
+
"cherry": "Cherry / 芊悦",
|
|
111
|
+
"serena": "Serena / 苏瑶",
|
|
112
|
+
"ethan": "Ethan / 晨煦",
|
|
113
|
+
"chelsie": "Chelsie / 千雪",
|
|
114
|
+
"momo": "Momo / 茉兔",
|
|
115
|
+
"vivian": "Vivian / 十三",
|
|
116
|
+
"moon": "Moon / 月白",
|
|
117
|
+
"maia": "Maia / 四月",
|
|
118
|
+
"kai": "Kai / 凯",
|
|
119
|
+
"nofish": "Nofish / 不吃鱼",
|
|
120
|
+
"bella": "Bella / 萌宝",
|
|
121
|
+
"jennifer": "Jennifer / 詹妮弗",
|
|
122
|
+
"ryan": "Ryan / 甜茶",
|
|
123
|
+
"katerina": "Katerina / 卡捷琳娜",
|
|
124
|
+
"aiden": "Aiden / 艾登",
|
|
125
|
+
"bodega": "Bodega / 西班牙语-博德加",
|
|
126
|
+
"alek": "Alek / 俄语-阿列克",
|
|
127
|
+
"dolce": "Dolce / 意大利语-多尔切",
|
|
128
|
+
"sohee": "Sohee / 韩语-素熙",
|
|
129
|
+
"ono_anna": "Ono Anna / 日语-小野杏",
|
|
130
|
+
"lenn": "Lenn / 德语-莱恩",
|
|
131
|
+
"sonrisa": "Sonrisa / 西班牙语拉美-索尼莎",
|
|
132
|
+
"emilien": "Emilien / 法语-埃米尔安",
|
|
133
|
+
"andre": "Andre / 葡萄牙语欧-安德雷",
|
|
134
|
+
"radio_gol": "Radio Gol / 葡萄牙语巴-拉迪奥·戈尔",
|
|
135
|
+
"eldric_sage": "Eldric Sage / 精品百人-沧明子",
|
|
136
|
+
"mia": "Mia / 精品百人-乖小妹",
|
|
137
|
+
"mochi": "Mochi / 精品百人-沙小弥",
|
|
138
|
+
"bellona": "Bellona / 精品百人-燕铮莺",
|
|
139
|
+
"vincent": "Vincent / 精品百人-田叔",
|
|
140
|
+
"bunny": "Bunny / 精品百人-萌小姬",
|
|
141
|
+
"neil": "Neil / 精品百人-阿闻",
|
|
142
|
+
"elias": "Elias / 墨讲师",
|
|
143
|
+
"arthur": "Arthur / 精品百人-徐大爷",
|
|
144
|
+
"nini": "Nini / 精品百人-邻家妹妹",
|
|
145
|
+
"ebona": "Ebona / 精品百人-诡婆婆",
|
|
146
|
+
"seren": "Seren / 精品百人-小婉",
|
|
147
|
+
"pip": "Pip / 精品百人-调皮小新",
|
|
148
|
+
"stella": "Stella / 精品百人-美少女阿月",
|
|
149
|
+
"li": "Li / 南京-老李",
|
|
150
|
+
"marcus": "Marcus / 陕西-秦川",
|
|
151
|
+
"roy": "Roy / 闽南-阿杰",
|
|
152
|
+
"peter": "Peter / 天津-李彼得",
|
|
153
|
+
"eric": "Eric / 四川-程川",
|
|
154
|
+
"rocky": "Rocky / 粤语-阿强",
|
|
155
|
+
"kiki": "Kiki / 粤语-阿清",
|
|
156
|
+
"sunny": "Sunny / 四川-晴儿",
|
|
157
|
+
"jada": "Jada / 上海-阿珍",
|
|
158
|
+
"dylan": "Dylan / 北京-晓东",
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
def __init__(self, timeout: int = 60, proxy: Optional[str] = None):
|
|
162
|
+
"""
|
|
163
|
+
Initialize the QwenTTS client.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
timeout (int): Request timeout in seconds
|
|
167
|
+
proxy (str): Proxy configuration string
|
|
168
|
+
"""
|
|
169
|
+
super().__init__()
|
|
170
|
+
self.timeout = timeout
|
|
171
|
+
self.proxy = proxy
|
|
172
|
+
self.default_voice = "cherry"
|
|
173
|
+
self.default_model = "qwen3-tts"
|
|
174
|
+
|
|
175
|
+
def _generate_session_hash(self) -> str:
|
|
176
|
+
"""Generates a random session hash for Gradio."""
|
|
177
|
+
return "".join(random.choices(string.ascii_lowercase + string.digits, k=10))
|
|
178
|
+
|
|
179
|
+
def tts(self, text: str, voice: Optional[str] = None, verbose: bool = False, **kwargs) -> str:
|
|
180
|
+
"""
|
|
181
|
+
Convert text to speech using Qwen3-TTS API with OpenAI-compatible parameters.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
text (str): The text to convert to speech (max 10,000 characters)
|
|
185
|
+
**kwargs: Additional parameters (model, voice, response_format, language, verbose)
|
|
186
|
+
"""
|
|
187
|
+
# Extract parameters from kwargs with defaults
|
|
188
|
+
voice = kwargs.get("voice", "cherry")
|
|
189
|
+
response_format = kwargs.get("response_format", "wav")
|
|
190
|
+
language = kwargs.get("language", "Auto / 自动")
|
|
191
|
+
verbose = kwargs.get("verbose", True)
|
|
192
|
+
if not text or not isinstance(text, str):
|
|
193
|
+
raise ValueError("Input text must be a non-empty string")
|
|
194
|
+
|
|
195
|
+
voice = self.validate_voice(voice)
|
|
196
|
+
qwen_voice = self.voice_mapping.get(voice, self.voice_mapping["cherry"])
|
|
197
|
+
|
|
198
|
+
# Create temporary file
|
|
199
|
+
file_extension = f".{response_format}"
|
|
200
|
+
filename = pathlib.Path(
|
|
201
|
+
tempfile.NamedTemporaryFile(suffix=file_extension, dir=self.temp_dir, delete=False).name
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
session_hash = self._generate_session_hash()
|
|
205
|
+
|
|
206
|
+
if verbose:
|
|
207
|
+
ic.configureOutput(prefix="DEBUG| ")
|
|
208
|
+
ic(f"Joining queue for voice: {voice} ({qwen_voice})")
|
|
209
|
+
|
|
210
|
+
client_kwargs: dict[str, Any] = {"headers": self.headers, "timeout": self.timeout}
|
|
211
|
+
if self.proxy:
|
|
212
|
+
client_kwargs["proxy"] = self.proxy
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
with httpx.Client(**client_kwargs) as client:
|
|
216
|
+
# Step 1: Join the queue
|
|
217
|
+
join_url = f"{self.BASE_URL}/gradio_api/queue/join?"
|
|
218
|
+
payload = {
|
|
219
|
+
"data": [text, qwen_voice, language],
|
|
220
|
+
"event_data": None,
|
|
221
|
+
"fn_index": 1,
|
|
222
|
+
"trigger_id": 7,
|
|
223
|
+
"session_hash": session_hash,
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
response = client.post(join_url, json=payload)
|
|
227
|
+
response.raise_for_status()
|
|
228
|
+
|
|
229
|
+
# Step 2: Poll for data (SSE)
|
|
230
|
+
data_url = f"{self.BASE_URL}/gradio_api/queue/data?session_hash={session_hash}"
|
|
231
|
+
audio_url = None
|
|
232
|
+
|
|
233
|
+
with client.stream("GET", data_url) as stream:
|
|
234
|
+
for line in stream.iter_lines():
|
|
235
|
+
if not line:
|
|
236
|
+
continue
|
|
237
|
+
if line.startswith("data: "):
|
|
238
|
+
try:
|
|
239
|
+
data = json.loads(line[6:])
|
|
240
|
+
except json.JSONDecodeError:
|
|
241
|
+
continue
|
|
242
|
+
|
|
243
|
+
msg = data.get("msg")
|
|
244
|
+
if msg == "process_completed":
|
|
245
|
+
if data.get("success"):
|
|
246
|
+
output_data = data.get("output", {}).get("data", [])
|
|
247
|
+
if output_data:
|
|
248
|
+
audio_info = output_data[0]
|
|
249
|
+
path = (
|
|
250
|
+
audio_info["path"]
|
|
251
|
+
if isinstance(audio_info, dict)
|
|
252
|
+
else audio_info
|
|
253
|
+
)
|
|
254
|
+
audio_url = f"{self.BASE_URL}/gradio_api/file={path}"
|
|
255
|
+
break
|
|
256
|
+
else:
|
|
257
|
+
raise exceptions.FailedToGenerateResponseError(
|
|
258
|
+
f"Generation failed: {data}"
|
|
259
|
+
)
|
|
260
|
+
elif msg == "queue_full":
|
|
261
|
+
raise exceptions.FailedToGenerateResponseError("Queue is full")
|
|
262
|
+
|
|
263
|
+
if not audio_url:
|
|
264
|
+
raise exceptions.FailedToGenerateResponseError(
|
|
265
|
+
"Failed to get audio URL from stream"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Step 3: Download the audio file
|
|
269
|
+
audio_response = client.get(audio_url)
|
|
270
|
+
audio_response.raise_for_status()
|
|
271
|
+
|
|
272
|
+
with open(filename, "wb") as f:
|
|
273
|
+
f.write(audio_response.content)
|
|
274
|
+
|
|
275
|
+
if verbose:
|
|
276
|
+
ic.configureOutput(prefix="DEBUG| ")
|
|
277
|
+
ic(f"Speech generated successfully: {filename}")
|
|
278
|
+
|
|
279
|
+
return filename.as_posix()
|
|
280
|
+
|
|
281
|
+
except Exception as e:
|
|
282
|
+
if verbose:
|
|
283
|
+
ic.configureOutput(prefix="DEBUG| ")
|
|
284
|
+
ic(f"Error in QwenTTS: {e}")
|
|
285
|
+
raise exceptions.FailedToGenerateResponseError(f"Failed to generate audio: {e}")
|
|
286
|
+
|
|
287
|
+
def create_speech(
|
|
288
|
+
self,
|
|
289
|
+
input_text: str,
|
|
290
|
+
model: Optional[str] = "gpt-4o-mini-tts",
|
|
291
|
+
voice: Optional[str] = "alloy",
|
|
292
|
+
response_format: Optional[str] = "mp3",
|
|
293
|
+
instructions: Optional[str] = None,
|
|
294
|
+
verbose: bool = False,
|
|
295
|
+
) -> str:
|
|
296
|
+
"""
|
|
297
|
+
OpenAI-compatible speech creation interface.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
input_text (str): The text to convert to speech
|
|
301
|
+
model (str): The TTS model to use
|
|
302
|
+
voice (str): The voice to use
|
|
303
|
+
response_format (str): Audio format
|
|
304
|
+
instructions (str): Voice instructions
|
|
305
|
+
verbose (bool): Whether to print debug information
|
|
306
|
+
**kwargs: Additional parameters
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
str: Path to the generated audio file
|
|
310
|
+
"""
|
|
311
|
+
return self.tts(
|
|
312
|
+
text=input_text,
|
|
313
|
+
voice=voice or "alloy",
|
|
314
|
+
model=model or "gpt-4o-mini-tts",
|
|
315
|
+
response_format=response_format or "mp3",
|
|
316
|
+
verbose=verbose,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
# ... (keep other classes as is)
|
|
321
|
+
|
|
322
|
+
if __name__ == "__main__":
|
|
323
|
+
qwen = QwenTTS()
|
|
324
|
+
try:
|
|
325
|
+
ic.configureOutput(prefix="DEBUG| ")
|
|
326
|
+
ic("Testing Qwen3-TTS...")
|
|
327
|
+
path = qwen.create_speech(
|
|
328
|
+
input_text="Hello, this is a test.", voice="jennifer", verbose=True
|
|
329
|
+
)
|
|
330
|
+
ic.configureOutput(prefix="INFO| ")
|
|
331
|
+
ic(f"Saved to {path}")
|
|
332
|
+
except Exception as e:
|
|
333
|
+
ic.configureOutput(prefix="ERROR| ")
|
|
334
|
+
ic(f"Error: {e}")
|