webscout 8.2.8__py3-none-any.whl → 8.3__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/AIauto.py +34 -16
- webscout/AIbase.py +96 -37
- webscout/AIutel.py +491 -87
- webscout/Bard.py +441 -323
- webscout/Extra/GitToolkit/__init__.py +10 -10
- webscout/Extra/YTToolkit/ytapi/video.py +232 -232
- webscout/Litlogger/README.md +10 -0
- webscout/Litlogger/__init__.py +7 -59
- webscout/Litlogger/formats.py +4 -0
- webscout/Litlogger/handlers.py +103 -0
- webscout/Litlogger/levels.py +13 -0
- webscout/Litlogger/logger.py +92 -0
- webscout/Provider/AISEARCH/Perplexity.py +332 -358
- webscout/Provider/AISEARCH/felo_search.py +9 -35
- webscout/Provider/AISEARCH/genspark_search.py +30 -56
- webscout/Provider/AISEARCH/hika_search.py +4 -16
- webscout/Provider/AISEARCH/iask_search.py +410 -436
- webscout/Provider/AISEARCH/monica_search.py +4 -30
- webscout/Provider/AISEARCH/scira_search.py +6 -32
- webscout/Provider/AISEARCH/webpilotai_search.py +38 -64
- webscout/Provider/Blackboxai.py +155 -35
- webscout/Provider/ChatSandbox.py +2 -1
- webscout/Provider/Deepinfra.py +339 -339
- webscout/Provider/ExaChat.py +358 -358
- webscout/Provider/Gemini.py +169 -169
- webscout/Provider/GithubChat.py +1 -2
- webscout/Provider/Glider.py +3 -3
- webscout/Provider/HeckAI.py +172 -82
- webscout/Provider/LambdaChat.py +1 -0
- webscout/Provider/MCPCore.py +7 -3
- webscout/Provider/OPENAI/BLACKBOXAI.py +421 -139
- webscout/Provider/OPENAI/Cloudflare.py +38 -21
- webscout/Provider/OPENAI/FalconH1.py +457 -0
- webscout/Provider/OPENAI/FreeGemini.py +35 -18
- webscout/Provider/OPENAI/NEMOTRON.py +34 -34
- webscout/Provider/OPENAI/PI.py +427 -0
- webscout/Provider/OPENAI/Qwen3.py +304 -0
- webscout/Provider/OPENAI/README.md +952 -1253
- webscout/Provider/OPENAI/TwoAI.py +374 -0
- webscout/Provider/OPENAI/__init__.py +7 -1
- webscout/Provider/OPENAI/ai4chat.py +73 -63
- webscout/Provider/OPENAI/api.py +869 -644
- webscout/Provider/OPENAI/base.py +2 -0
- webscout/Provider/OPENAI/c4ai.py +34 -13
- webscout/Provider/OPENAI/chatgpt.py +575 -556
- webscout/Provider/OPENAI/chatgptclone.py +512 -487
- webscout/Provider/OPENAI/chatsandbox.py +11 -6
- webscout/Provider/OPENAI/copilot.py +258 -0
- webscout/Provider/OPENAI/deepinfra.py +327 -318
- webscout/Provider/OPENAI/e2b.py +140 -104
- webscout/Provider/OPENAI/exaai.py +420 -411
- webscout/Provider/OPENAI/exachat.py +448 -443
- webscout/Provider/OPENAI/flowith.py +7 -3
- webscout/Provider/OPENAI/freeaichat.py +12 -8
- webscout/Provider/OPENAI/glider.py +15 -8
- webscout/Provider/OPENAI/groq.py +5 -2
- webscout/Provider/OPENAI/heckai.py +311 -307
- webscout/Provider/OPENAI/llmchatco.py +9 -7
- webscout/Provider/OPENAI/mcpcore.py +18 -9
- webscout/Provider/OPENAI/multichat.py +7 -5
- webscout/Provider/OPENAI/netwrck.py +16 -11
- webscout/Provider/OPENAI/oivscode.py +290 -0
- webscout/Provider/OPENAI/opkfc.py +507 -496
- webscout/Provider/OPENAI/pydantic_imports.py +172 -0
- webscout/Provider/OPENAI/scirachat.py +29 -17
- webscout/Provider/OPENAI/sonus.py +308 -303
- webscout/Provider/OPENAI/standardinput.py +442 -433
- webscout/Provider/OPENAI/textpollinations.py +18 -11
- webscout/Provider/OPENAI/toolbaz.py +419 -413
- webscout/Provider/OPENAI/typefully.py +17 -10
- webscout/Provider/OPENAI/typegpt.py +21 -11
- webscout/Provider/OPENAI/uncovrAI.py +477 -462
- webscout/Provider/OPENAI/utils.py +90 -79
- webscout/Provider/OPENAI/venice.py +435 -425
- webscout/Provider/OPENAI/wisecat.py +387 -381
- webscout/Provider/OPENAI/writecream.py +166 -163
- webscout/Provider/OPENAI/x0gpt.py +26 -37
- webscout/Provider/OPENAI/yep.py +384 -356
- webscout/Provider/PI.py +2 -1
- webscout/Provider/TTI/README.md +55 -101
- webscout/Provider/TTI/__init__.py +4 -9
- webscout/Provider/TTI/aiarta.py +365 -0
- webscout/Provider/TTI/artbit.py +0 -0
- webscout/Provider/TTI/base.py +64 -0
- webscout/Provider/TTI/fastflux.py +200 -0
- webscout/Provider/TTI/magicstudio.py +201 -0
- webscout/Provider/TTI/piclumen.py +203 -0
- webscout/Provider/TTI/pixelmuse.py +225 -0
- webscout/Provider/TTI/pollinations.py +221 -0
- webscout/Provider/TTI/utils.py +11 -0
- webscout/Provider/TTS/__init__.py +2 -1
- webscout/Provider/TTS/base.py +159 -159
- webscout/Provider/TTS/openai_fm.py +129 -0
- webscout/Provider/TextPollinationsAI.py +308 -308
- webscout/Provider/TwoAI.py +239 -44
- webscout/Provider/UNFINISHED/Youchat.py +330 -330
- webscout/Provider/UNFINISHED/puterjs.py +635 -0
- webscout/Provider/UNFINISHED/test_lmarena.py +119 -119
- webscout/Provider/Writecream.py +246 -246
- webscout/Provider/__init__.py +2 -2
- webscout/Provider/ai4chat.py +33 -8
- webscout/Provider/granite.py +41 -6
- webscout/Provider/koala.py +169 -169
- webscout/Provider/oivscode.py +309 -0
- webscout/Provider/samurai.py +3 -2
- webscout/Provider/scnet.py +1 -0
- webscout/Provider/typegpt.py +3 -3
- webscout/Provider/uncovr.py +368 -368
- webscout/client.py +70 -0
- webscout/litprinter/__init__.py +58 -58
- webscout/optimizers.py +419 -419
- webscout/scout/README.md +3 -1
- webscout/scout/core/crawler.py +134 -64
- webscout/scout/core/scout.py +148 -109
- webscout/scout/element.py +106 -88
- webscout/swiftcli/Readme.md +323 -323
- webscout/swiftcli/plugins/manager.py +9 -2
- webscout/version.py +1 -1
- webscout/zeroart/__init__.py +134 -134
- webscout/zeroart/effects.py +100 -100
- webscout/zeroart/fonts.py +1238 -1238
- {webscout-8.2.8.dist-info → webscout-8.3.dist-info}/METADATA +160 -35
- webscout-8.3.dist-info/RECORD +290 -0
- {webscout-8.2.8.dist-info → webscout-8.3.dist-info}/WHEEL +1 -1
- {webscout-8.2.8.dist-info → webscout-8.3.dist-info}/entry_points.txt +1 -0
- webscout/Litlogger/Readme.md +0 -175
- webscout/Litlogger/core/__init__.py +0 -6
- webscout/Litlogger/core/level.py +0 -23
- webscout/Litlogger/core/logger.py +0 -165
- webscout/Litlogger/handlers/__init__.py +0 -12
- webscout/Litlogger/handlers/console.py +0 -33
- webscout/Litlogger/handlers/file.py +0 -143
- webscout/Litlogger/handlers/network.py +0 -173
- webscout/Litlogger/styles/__init__.py +0 -7
- webscout/Litlogger/styles/colors.py +0 -249
- webscout/Litlogger/styles/formats.py +0 -458
- webscout/Litlogger/styles/text.py +0 -87
- webscout/Litlogger/utils/__init__.py +0 -6
- webscout/Litlogger/utils/detectors.py +0 -153
- webscout/Litlogger/utils/formatters.py +0 -200
- webscout/Provider/ChatGPTGratis.py +0 -194
- webscout/Provider/TTI/AiForce/README.md +0 -159
- webscout/Provider/TTI/AiForce/__init__.py +0 -22
- webscout/Provider/TTI/AiForce/async_aiforce.py +0 -224
- webscout/Provider/TTI/AiForce/sync_aiforce.py +0 -245
- webscout/Provider/TTI/FreeAIPlayground/README.md +0 -99
- webscout/Provider/TTI/FreeAIPlayground/__init__.py +0 -9
- webscout/Provider/TTI/FreeAIPlayground/async_freeaiplayground.py +0 -181
- webscout/Provider/TTI/FreeAIPlayground/sync_freeaiplayground.py +0 -180
- webscout/Provider/TTI/ImgSys/README.md +0 -174
- webscout/Provider/TTI/ImgSys/__init__.py +0 -23
- webscout/Provider/TTI/ImgSys/async_imgsys.py +0 -202
- webscout/Provider/TTI/ImgSys/sync_imgsys.py +0 -195
- webscout/Provider/TTI/MagicStudio/README.md +0 -101
- webscout/Provider/TTI/MagicStudio/__init__.py +0 -2
- webscout/Provider/TTI/MagicStudio/async_magicstudio.py +0 -111
- webscout/Provider/TTI/MagicStudio/sync_magicstudio.py +0 -109
- webscout/Provider/TTI/Nexra/README.md +0 -155
- webscout/Provider/TTI/Nexra/__init__.py +0 -22
- webscout/Provider/TTI/Nexra/async_nexra.py +0 -286
- webscout/Provider/TTI/Nexra/sync_nexra.py +0 -258
- webscout/Provider/TTI/PollinationsAI/README.md +0 -146
- webscout/Provider/TTI/PollinationsAI/__init__.py +0 -23
- webscout/Provider/TTI/PollinationsAI/async_pollinations.py +0 -311
- webscout/Provider/TTI/PollinationsAI/sync_pollinations.py +0 -265
- webscout/Provider/TTI/aiarta/README.md +0 -134
- webscout/Provider/TTI/aiarta/__init__.py +0 -2
- webscout/Provider/TTI/aiarta/async_aiarta.py +0 -482
- webscout/Provider/TTI/aiarta/sync_aiarta.py +0 -440
- webscout/Provider/TTI/artbit/README.md +0 -100
- webscout/Provider/TTI/artbit/__init__.py +0 -22
- webscout/Provider/TTI/artbit/async_artbit.py +0 -155
- webscout/Provider/TTI/artbit/sync_artbit.py +0 -148
- webscout/Provider/TTI/fastflux/README.md +0 -129
- webscout/Provider/TTI/fastflux/__init__.py +0 -22
- webscout/Provider/TTI/fastflux/async_fastflux.py +0 -261
- webscout/Provider/TTI/fastflux/sync_fastflux.py +0 -252
- webscout/Provider/TTI/huggingface/README.md +0 -114
- webscout/Provider/TTI/huggingface/__init__.py +0 -22
- webscout/Provider/TTI/huggingface/async_huggingface.py +0 -199
- webscout/Provider/TTI/huggingface/sync_huggingface.py +0 -195
- webscout/Provider/TTI/piclumen/README.md +0 -161
- webscout/Provider/TTI/piclumen/__init__.py +0 -23
- webscout/Provider/TTI/piclumen/async_piclumen.py +0 -268
- webscout/Provider/TTI/piclumen/sync_piclumen.py +0 -233
- webscout/Provider/TTI/pixelmuse/README.md +0 -79
- webscout/Provider/TTI/pixelmuse/__init__.py +0 -4
- webscout/Provider/TTI/pixelmuse/async_pixelmuse.py +0 -249
- webscout/Provider/TTI/pixelmuse/sync_pixelmuse.py +0 -182
- webscout/Provider/TTI/talkai/README.md +0 -139
- webscout/Provider/TTI/talkai/__init__.py +0 -4
- webscout/Provider/TTI/talkai/async_talkai.py +0 -229
- webscout/Provider/TTI/talkai/sync_talkai.py +0 -207
- webscout/Provider/UNFINISHED/oivscode.py +0 -351
- webscout-8.2.8.dist-info/RECORD +0 -334
- {webscout-8.2.8.dist-info → webscout-8.3.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.2.8.dist-info → webscout-8.3.dist-info}/top_level.txt +0 -0
webscout/Provider/TTS/base.py
CHANGED
|
@@ -1,159 +1,159 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Base class for TTS providers with common functionality.
|
|
3
|
-
"""
|
|
4
|
-
import os
|
|
5
|
-
import tempfile
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Generator, Optional
|
|
8
|
-
from webscout.AIbase import TTSProvider
|
|
9
|
-
|
|
10
|
-
class BaseTTSProvider(TTSProvider):
|
|
11
|
-
"""
|
|
12
|
-
Base class for TTS providers with common functionality.
|
|
13
|
-
|
|
14
|
-
This class implements common methods like save_audio and stream_audio
|
|
15
|
-
that can be used by all TTS providers.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
def __init__(self):
|
|
19
|
-
"""Initialize the base TTS provider."""
|
|
20
|
-
self.temp_dir = tempfile.mkdtemp(prefix="webscout_tts_")
|
|
21
|
-
|
|
22
|
-
def save_audio(self, audio_file: str, destination: str = None, verbose: bool = False) -> str:
|
|
23
|
-
"""
|
|
24
|
-
Save audio to a specific destination.
|
|
25
|
-
|
|
26
|
-
Args:
|
|
27
|
-
audio_file (str): Path to the source audio file
|
|
28
|
-
destination (str, optional): Destination path. Defaults to current directory with timestamp.
|
|
29
|
-
verbose (bool, optional): Whether to print debug information. Defaults to False.
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
str: Path to the saved audio file
|
|
33
|
-
|
|
34
|
-
Raises:
|
|
35
|
-
FileNotFoundError: If the audio file doesn't exist
|
|
36
|
-
"""
|
|
37
|
-
import shutil
|
|
38
|
-
import time
|
|
39
|
-
|
|
40
|
-
source_path = Path(audio_file)
|
|
41
|
-
|
|
42
|
-
if not source_path.exists():
|
|
43
|
-
raise FileNotFoundError(f"Audio file not found: {audio_file}")
|
|
44
|
-
|
|
45
|
-
if destination is None:
|
|
46
|
-
# Create a default destination with timestamp in current directory
|
|
47
|
-
timestamp = int(time.time())
|
|
48
|
-
destination = os.path.join(os.getcwd(), f"tts_audio_{timestamp}{source_path.suffix}")
|
|
49
|
-
|
|
50
|
-
# Ensure the destination directory exists
|
|
51
|
-
os.makedirs(os.path.dirname(os.path.abspath(destination)), exist_ok=True)
|
|
52
|
-
|
|
53
|
-
# Copy the file
|
|
54
|
-
shutil.copy2(source_path, destination)
|
|
55
|
-
|
|
56
|
-
if verbose:
|
|
57
|
-
print(f"[debug] Audio saved to {destination}")
|
|
58
|
-
|
|
59
|
-
return destination
|
|
60
|
-
|
|
61
|
-
def stream_audio(self, text: str, voice: str = None, chunk_size: int = 1024, verbose: bool = False) -> Generator[bytes, None, None]:
|
|
62
|
-
"""
|
|
63
|
-
Stream audio in chunks.
|
|
64
|
-
|
|
65
|
-
Args:
|
|
66
|
-
text (str): The text to convert to speech
|
|
67
|
-
voice (str, optional): The voice to use. Defaults to provider's default voice.
|
|
68
|
-
chunk_size (int, optional): Size of audio chunks to yield. Defaults to 1024.
|
|
69
|
-
verbose (bool, optional): Whether to print debug information. Defaults to False.
|
|
70
|
-
|
|
71
|
-
Yields:
|
|
72
|
-
Generator[bytes, None, None]: Audio data chunks
|
|
73
|
-
"""
|
|
74
|
-
# Generate the audio file
|
|
75
|
-
audio_file = self.tts(text, voice=voice, verbose=verbose)
|
|
76
|
-
|
|
77
|
-
# Stream the file in chunks
|
|
78
|
-
with open(audio_file, 'rb') as f:
|
|
79
|
-
while chunk := f.read(chunk_size):
|
|
80
|
-
yield chunk
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
class AsyncBaseTTSProvider:
|
|
84
|
-
"""
|
|
85
|
-
Base class for async TTS providers with common functionality.
|
|
86
|
-
|
|
87
|
-
This class implements common async methods like save_audio and stream_audio
|
|
88
|
-
that can be used by all async TTS providers.
|
|
89
|
-
"""
|
|
90
|
-
|
|
91
|
-
def __init__(self):
|
|
92
|
-
"""Initialize the async base TTS provider."""
|
|
93
|
-
self.temp_dir = tempfile.mkdtemp(prefix="webscout_tts_")
|
|
94
|
-
|
|
95
|
-
async def save_audio(self, audio_file: str, destination: str = None, verbose: bool = False) -> str:
|
|
96
|
-
"""
|
|
97
|
-
Save audio to a specific destination asynchronously.
|
|
98
|
-
|
|
99
|
-
Args:
|
|
100
|
-
audio_file (str): Path to the source audio file
|
|
101
|
-
destination (str, optional): Destination path. Defaults to current directory with timestamp.
|
|
102
|
-
verbose (bool, optional): Whether to print debug information. Defaults to False.
|
|
103
|
-
|
|
104
|
-
Returns:
|
|
105
|
-
str: Path to the saved audio file
|
|
106
|
-
|
|
107
|
-
Raises:
|
|
108
|
-
FileNotFoundError: If the audio file doesn't exist
|
|
109
|
-
"""
|
|
110
|
-
import shutil
|
|
111
|
-
import time
|
|
112
|
-
import asyncio
|
|
113
|
-
|
|
114
|
-
source_path = Path(audio_file)
|
|
115
|
-
|
|
116
|
-
if not source_path.exists():
|
|
117
|
-
raise FileNotFoundError(f"Audio file not found: {audio_file}")
|
|
118
|
-
|
|
119
|
-
if destination is None:
|
|
120
|
-
# Create a default destination with timestamp in current directory
|
|
121
|
-
timestamp = int(time.time())
|
|
122
|
-
destination = os.path.join(os.getcwd(), f"tts_audio_{timestamp}{source_path.suffix}")
|
|
123
|
-
|
|
124
|
-
# Ensure the destination directory exists
|
|
125
|
-
os.makedirs(os.path.dirname(os.path.abspath(destination)), exist_ok=True)
|
|
126
|
-
|
|
127
|
-
# Copy the file using asyncio to avoid blocking
|
|
128
|
-
await asyncio.to_thread(shutil.copy2, source_path, destination)
|
|
129
|
-
|
|
130
|
-
if verbose:
|
|
131
|
-
print(f"[debug] Audio saved to {destination}")
|
|
132
|
-
|
|
133
|
-
return destination
|
|
134
|
-
|
|
135
|
-
async def stream_audio(self, text: str, voice: str = None, chunk_size: int = 1024, verbose: bool = False):
|
|
136
|
-
"""
|
|
137
|
-
Stream audio in chunks asynchronously.
|
|
138
|
-
|
|
139
|
-
Args:
|
|
140
|
-
text (str): The text to convert to speech
|
|
141
|
-
voice (str, optional): The voice to use. Defaults to provider's default voice.
|
|
142
|
-
chunk_size (int, optional): Size of audio chunks to yield. Defaults to 1024.
|
|
143
|
-
verbose (bool, optional): Whether to print debug information. Defaults to False.
|
|
144
|
-
|
|
145
|
-
Yields:
|
|
146
|
-
AsyncGenerator[bytes, None]: Audio data chunks
|
|
147
|
-
"""
|
|
148
|
-
try:
|
|
149
|
-
import aiofiles
|
|
150
|
-
except ImportError:
|
|
151
|
-
raise ImportError("The 'aiofiles' package is required for async streaming. Install it with 'pip install aiofiles'.")
|
|
152
|
-
|
|
153
|
-
# Generate the audio file
|
|
154
|
-
audio_file = await self.tts(text, voice=voice, verbose=verbose)
|
|
155
|
-
|
|
156
|
-
# Stream the file in chunks
|
|
157
|
-
async with aiofiles.open(audio_file, 'rb') as f:
|
|
158
|
-
while chunk := await f.read(chunk_size):
|
|
159
|
-
yield chunk
|
|
1
|
+
"""
|
|
2
|
+
Base class for TTS providers with common functionality.
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import tempfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Generator, Optional
|
|
8
|
+
from webscout.AIbase import TTSProvider
|
|
9
|
+
|
|
10
|
+
class BaseTTSProvider(TTSProvider):
|
|
11
|
+
"""
|
|
12
|
+
Base class for TTS providers with common functionality.
|
|
13
|
+
|
|
14
|
+
This class implements common methods like save_audio and stream_audio
|
|
15
|
+
that can be used by all TTS providers.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
"""Initialize the base TTS provider."""
|
|
20
|
+
self.temp_dir = tempfile.mkdtemp(prefix="webscout_tts_")
|
|
21
|
+
|
|
22
|
+
def save_audio(self, audio_file: str, destination: str = None, verbose: bool = False) -> str:
|
|
23
|
+
"""
|
|
24
|
+
Save audio to a specific destination.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
audio_file (str): Path to the source audio file
|
|
28
|
+
destination (str, optional): Destination path. Defaults to current directory with timestamp.
|
|
29
|
+
verbose (bool, optional): Whether to print debug information. Defaults to False.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
str: Path to the saved audio file
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
FileNotFoundError: If the audio file doesn't exist
|
|
36
|
+
"""
|
|
37
|
+
import shutil
|
|
38
|
+
import time
|
|
39
|
+
|
|
40
|
+
source_path = Path(audio_file)
|
|
41
|
+
|
|
42
|
+
if not source_path.exists():
|
|
43
|
+
raise FileNotFoundError(f"Audio file not found: {audio_file}")
|
|
44
|
+
|
|
45
|
+
if destination is None:
|
|
46
|
+
# Create a default destination with timestamp in current directory
|
|
47
|
+
timestamp = int(time.time())
|
|
48
|
+
destination = os.path.join(os.getcwd(), f"tts_audio_{timestamp}{source_path.suffix}")
|
|
49
|
+
|
|
50
|
+
# Ensure the destination directory exists
|
|
51
|
+
os.makedirs(os.path.dirname(os.path.abspath(destination)), exist_ok=True)
|
|
52
|
+
|
|
53
|
+
# Copy the file
|
|
54
|
+
shutil.copy2(source_path, destination)
|
|
55
|
+
|
|
56
|
+
if verbose:
|
|
57
|
+
print(f"[debug] Audio saved to {destination}")
|
|
58
|
+
|
|
59
|
+
return destination
|
|
60
|
+
|
|
61
|
+
def stream_audio(self, text: str, voice: str = None, chunk_size: int = 1024, verbose: bool = False) -> Generator[bytes, None, None]:
|
|
62
|
+
"""
|
|
63
|
+
Stream audio in chunks.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
text (str): The text to convert to speech
|
|
67
|
+
voice (str, optional): The voice to use. Defaults to provider's default voice.
|
|
68
|
+
chunk_size (int, optional): Size of audio chunks to yield. Defaults to 1024.
|
|
69
|
+
verbose (bool, optional): Whether to print debug information. Defaults to False.
|
|
70
|
+
|
|
71
|
+
Yields:
|
|
72
|
+
Generator[bytes, None, None]: Audio data chunks
|
|
73
|
+
"""
|
|
74
|
+
# Generate the audio file
|
|
75
|
+
audio_file = self.tts(text, voice=voice, verbose=verbose)
|
|
76
|
+
|
|
77
|
+
# Stream the file in chunks
|
|
78
|
+
with open(audio_file, 'rb') as f:
|
|
79
|
+
while chunk := f.read(chunk_size):
|
|
80
|
+
yield chunk
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class AsyncBaseTTSProvider:
|
|
84
|
+
"""
|
|
85
|
+
Base class for async TTS providers with common functionality.
|
|
86
|
+
|
|
87
|
+
This class implements common async methods like save_audio and stream_audio
|
|
88
|
+
that can be used by all async TTS providers.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(self):
|
|
92
|
+
"""Initialize the async base TTS provider."""
|
|
93
|
+
self.temp_dir = tempfile.mkdtemp(prefix="webscout_tts_")
|
|
94
|
+
|
|
95
|
+
async def save_audio(self, audio_file: str, destination: str = None, verbose: bool = False) -> str:
|
|
96
|
+
"""
|
|
97
|
+
Save audio to a specific destination asynchronously.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
audio_file (str): Path to the source audio file
|
|
101
|
+
destination (str, optional): Destination path. Defaults to current directory with timestamp.
|
|
102
|
+
verbose (bool, optional): Whether to print debug information. Defaults to False.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
str: Path to the saved audio file
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
FileNotFoundError: If the audio file doesn't exist
|
|
109
|
+
"""
|
|
110
|
+
import shutil
|
|
111
|
+
import time
|
|
112
|
+
import asyncio
|
|
113
|
+
|
|
114
|
+
source_path = Path(audio_file)
|
|
115
|
+
|
|
116
|
+
if not source_path.exists():
|
|
117
|
+
raise FileNotFoundError(f"Audio file not found: {audio_file}")
|
|
118
|
+
|
|
119
|
+
if destination is None:
|
|
120
|
+
# Create a default destination with timestamp in current directory
|
|
121
|
+
timestamp = int(time.time())
|
|
122
|
+
destination = os.path.join(os.getcwd(), f"tts_audio_{timestamp}{source_path.suffix}")
|
|
123
|
+
|
|
124
|
+
# Ensure the destination directory exists
|
|
125
|
+
os.makedirs(os.path.dirname(os.path.abspath(destination)), exist_ok=True)
|
|
126
|
+
|
|
127
|
+
# Copy the file using asyncio to avoid blocking
|
|
128
|
+
await asyncio.to_thread(shutil.copy2, source_path, destination)
|
|
129
|
+
|
|
130
|
+
if verbose:
|
|
131
|
+
print(f"[debug] Audio saved to {destination}")
|
|
132
|
+
|
|
133
|
+
return destination
|
|
134
|
+
|
|
135
|
+
async def stream_audio(self, text: str, voice: str = None, chunk_size: int = 1024, verbose: bool = False):
|
|
136
|
+
"""
|
|
137
|
+
Stream audio in chunks asynchronously.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
text (str): The text to convert to speech
|
|
141
|
+
voice (str, optional): The voice to use. Defaults to provider's default voice.
|
|
142
|
+
chunk_size (int, optional): Size of audio chunks to yield. Defaults to 1024.
|
|
143
|
+
verbose (bool, optional): Whether to print debug information. Defaults to False.
|
|
144
|
+
|
|
145
|
+
Yields:
|
|
146
|
+
AsyncGenerator[bytes, None]: Audio data chunks
|
|
147
|
+
"""
|
|
148
|
+
try:
|
|
149
|
+
import aiofiles
|
|
150
|
+
except ImportError:
|
|
151
|
+
raise ImportError("The 'aiofiles' package is required for async streaming. Install it with 'pip install aiofiles'.")
|
|
152
|
+
|
|
153
|
+
# Generate the audio file
|
|
154
|
+
audio_file = await self.tts(text, voice=voice, verbose=verbose)
|
|
155
|
+
|
|
156
|
+
# Stream the file in chunks
|
|
157
|
+
async with aiofiles.open(audio_file, 'rb') as f:
|
|
158
|
+
while chunk := await f.read(chunk_size):
|
|
159
|
+
yield chunk
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
##################################################################################
|
|
2
|
+
## OpenAI.fm TTS Provider ##
|
|
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 concurrent.futures import ThreadPoolExecutor, as_completed
|
|
12
|
+
from webscout.Provider.TTS import utils
|
|
13
|
+
from webscout.Provider.TTS.base import BaseTTSProvider
|
|
14
|
+
|
|
15
|
+
class OpenAIFMTTS(BaseTTSProvider):
|
|
16
|
+
"""
|
|
17
|
+
Text-to-speech provider using the OpenAI.fm API.
|
|
18
|
+
"""
|
|
19
|
+
# Request headers
|
|
20
|
+
headers = {
|
|
21
|
+
"accept": "*/*",
|
|
22
|
+
"accept-language": "en-US,en;q=0.9",
|
|
23
|
+
"cache-control": "no-cache",
|
|
24
|
+
"pragma": "no-cache",
|
|
25
|
+
"sec-fetch-dest": "audio",
|
|
26
|
+
"sec-fetch-mode": "no-cors",
|
|
27
|
+
"sec-fetch-site": "same-origin",
|
|
28
|
+
"user-agent": LitAgent().random(),
|
|
29
|
+
"referer": "https://www.openai.fm"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Available voices with their IDs
|
|
33
|
+
all_voices = {
|
|
34
|
+
# OpenAI.fm voices
|
|
35
|
+
"Alloy": "alloy", # Neutral voice with balanced tone
|
|
36
|
+
"Ash": "ash", # Calm and thoughtful male voice
|
|
37
|
+
"Ballad": "ballad", # Soft and melodic voice
|
|
38
|
+
"Coral": "coral", # Warm and inviting female voice
|
|
39
|
+
"Echo": "echo", # Clear and precise voice
|
|
40
|
+
"Fable": "fable", # Authoritative and narrative voice
|
|
41
|
+
"Onyx": "onyx", # Deep and resonant male voice
|
|
42
|
+
"Nova": "nova", # Energetic and bright female voice
|
|
43
|
+
"Sage": "sage", # Measured and contemplative voice
|
|
44
|
+
"Shimmer": "shimmer", # Bright and optimistic voice
|
|
45
|
+
"Verse": "verse" # Melodic and rhythmic voice
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
def __init__(self, timeout: int = 20, proxies: dict = None):
|
|
49
|
+
"""Initializes the OpenAI.fm TTS client."""
|
|
50
|
+
super().__init__()
|
|
51
|
+
self.api_url = "https://www.openai.fm/api/generate"
|
|
52
|
+
self.session = requests.Session()
|
|
53
|
+
self.session.headers.update(self.headers)
|
|
54
|
+
if proxies:
|
|
55
|
+
self.session.proxies.update(proxies)
|
|
56
|
+
self.timeout = timeout
|
|
57
|
+
|
|
58
|
+
def tts(self, text: str, voice: str = "Coral", instructions: str = None, verbose: bool = True) -> str:
|
|
59
|
+
"""
|
|
60
|
+
Converts text to speech using the OpenAI.fm API and saves it to a file.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
text (str): The text to convert to speech
|
|
64
|
+
voice (str): The voice to use for TTS (default: "Coral")
|
|
65
|
+
instructions (str): Voice instructions/prompt (default: "A cheerful guide. Friendly, clear, and reassuring.")
|
|
66
|
+
verbose (bool): Whether to print debug information (default: True)
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
str: Path to the generated audio file
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
exceptions.FailedToGenerateResponseError: If there is an error generating or saving the audio.
|
|
73
|
+
"""
|
|
74
|
+
# Validate input parameters
|
|
75
|
+
if not text or not isinstance(text, str):
|
|
76
|
+
raise ValueError("Text input must be a non-empty string")
|
|
77
|
+
if len(text) > 10000: # Add reasonable length limit
|
|
78
|
+
raise ValueError("Text input exceeds maximum allowed length")
|
|
79
|
+
|
|
80
|
+
assert (
|
|
81
|
+
voice in self.all_voices
|
|
82
|
+
), f"Voice '{voice}' not one of [{', '.join(self.all_voices.keys())}]"
|
|
83
|
+
|
|
84
|
+
with tempfile.NamedTemporaryFile(suffix=".mp3", dir=self.temp_dir, delete=False) as temp_file:
|
|
85
|
+
filename = pathlib.Path(temp_file.name)
|
|
86
|
+
voice_id = self.all_voices[voice]
|
|
87
|
+
|
|
88
|
+
if instructions is None:
|
|
89
|
+
instructions = "A cheerful guide. Friendly, clear, and reassuring."
|
|
90
|
+
|
|
91
|
+
# Prepare parameters for the API request
|
|
92
|
+
params = {
|
|
93
|
+
"input": text,
|
|
94
|
+
"prompt": instructions,
|
|
95
|
+
"voice": voice_id
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
# Make the API request
|
|
100
|
+
response = self.session.get(
|
|
101
|
+
self.api_url,
|
|
102
|
+
params=params,
|
|
103
|
+
timeout=self.timeout
|
|
104
|
+
)
|
|
105
|
+
response.raise_for_status()
|
|
106
|
+
|
|
107
|
+
# Save the audio file
|
|
108
|
+
with open(filename, "wb") as f:
|
|
109
|
+
f.write(response.content)
|
|
110
|
+
|
|
111
|
+
if verbose:
|
|
112
|
+
print(f"[debug] Audio saved to {filename}")
|
|
113
|
+
|
|
114
|
+
return filename.as_posix()
|
|
115
|
+
|
|
116
|
+
except requests.exceptions.RequestException as e:
|
|
117
|
+
if verbose:
|
|
118
|
+
print(f"[debug] Failed to perform the operation: {e}")
|
|
119
|
+
raise exceptions.FailedToGenerateResponseError(
|
|
120
|
+
f"Failed to perform the operation: {e}"
|
|
121
|
+
)
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
# Example usage
|
|
124
|
+
tts_provider = OpenAIFMTTS()
|
|
125
|
+
try:
|
|
126
|
+
audio_file = tts_provider.tts("Hello, this is a test.", instructions="A cheerful guide. Friendly, clear, and reassuring.", voice="Coral")
|
|
127
|
+
print(f"Audio file generated: {audio_file}")
|
|
128
|
+
except exceptions.FailedToGenerateResponseError as e:
|
|
129
|
+
print(f"Error: {e}")
|