webscout 8.3.5__py3-none-any.whl → 8.3.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of webscout might be problematic. Click here for more details.
- webscout/Bard.py +12 -6
- webscout/DWEBS.py +66 -57
- webscout/Provider/{UNFINISHED → AISEARCH}/PERPLEXED_search.py +34 -74
- webscout/Provider/AISEARCH/__init__.py +1 -1
- webscout/Provider/Deepinfra.py +6 -0
- 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 +50 -55
- webscout/Provider/OPENAI/__init__.py +4 -2
- webscout/Provider/OPENAI/copilot.py +20 -4
- webscout/Provider/OPENAI/deepinfra.py +6 -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/textpollinations.py +3 -6
- webscout/Provider/OPENAI/toolbaz.py +1 -0
- webscout/Provider/TTI/bing.py +14 -2
- webscout/Provider/TTI/together.py +10 -9
- 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 +3 -6
- webscout/Provider/TogetherAI.py +50 -55
- webscout/Provider/UNFINISHED/VercelAIGateway.py +339 -0
- webscout/Provider/__init__.py +2 -90
- webscout/Provider/cerebras.py +83 -33
- webscout/Provider/copilot.py +42 -23
- webscout/Provider/toolbaz.py +1 -0
- webscout/conversation.py +22 -20
- webscout/sanitize.py +14 -10
- 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.5.dist-info → webscout-8.3.6.dist-info}/METADATA +2 -1
- {webscout-8.3.5.dist-info → webscout-8.3.6.dist-info}/RECORD +59 -56
- webscout/Provider/Glider.py +0 -225
- webscout/Provider/OPENAI/c4ai.py +0 -394
- webscout/Provider/OPENAI/glider.py +0 -330
- webscout/Provider/TTS/sthir.py +0 -94
- /webscout/Provider/{samurai.py → UNFINISHED/samurai.py} +0 -0
- {webscout-8.3.5.dist-info → webscout-8.3.6.dist-info}/WHEEL +0 -0
- {webscout-8.3.5.dist-info → webscout-8.3.6.dist-info}/entry_points.txt +0 -0
- {webscout-8.3.5.dist-info → webscout-8.3.6.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.3.5.dist-info → webscout-8.3.6.dist-info}/top_level.txt +0 -0
|
@@ -1,111 +1,425 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
from .
|
|
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
|
+
## ElevenLabs 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
|
+
|
|
13
|
+
try:
|
|
14
|
+
from . import utils
|
|
15
|
+
from .base import BaseTTSProvider
|
|
16
|
+
except ImportError:
|
|
17
|
+
# Handle direct execution
|
|
18
|
+
import sys
|
|
19
|
+
import os
|
|
20
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
|
|
21
|
+
from webscout.Provider.TTS import utils
|
|
22
|
+
from webscout.Provider.TTS.base import BaseTTSProvider
|
|
23
|
+
|
|
24
|
+
class ElevenlabsTTS(BaseTTSProvider):
|
|
25
|
+
"""
|
|
26
|
+
Text-to-speech provider using the ElevenLabs API with OpenAI-compatible interface.
|
|
27
|
+
|
|
28
|
+
This provider follows the OpenAI TTS API structure with support for:
|
|
29
|
+
- Multiple TTS models (gpt-4o-mini-tts, tts-1, tts-1-hd)
|
|
30
|
+
- ElevenLabs' multilingual voices
|
|
31
|
+
- Voice instructions for controlling speech aspects
|
|
32
|
+
- Multiple output formats
|
|
33
|
+
- Streaming support
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# Request headers
|
|
37
|
+
headers: dict[str, str] = {
|
|
38
|
+
"User-Agent": LitAgent().random()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Override supported models for ElevenLabs
|
|
42
|
+
SUPPORTED_MODELS = None
|
|
43
|
+
|
|
44
|
+
# ElevenLabs voices (mapped to OpenAI-compatible names)
|
|
45
|
+
SUPPORTED_VOICES = [
|
|
46
|
+
"brian", # nPczCjzI2devNBz1zQrb - Deep male voice
|
|
47
|
+
"alice", # Xb7hH8MSUJpSbSDYk0k2 - Natural female voice
|
|
48
|
+
"bill", # pqHfZKP75CvOlQylNhV4 - Warm male voice
|
|
49
|
+
"callum", # N2lVS1w4EtoT3dr4eOWO - Scottish male voice
|
|
50
|
+
"charlie", # IKne3meq5aSn9XLyUdCD - American male voice
|
|
51
|
+
"charlotte", # XB0fDUnXU5powFXDhCwa - British female voice
|
|
52
|
+
"chris", # iP95p4xoKVk53GoZ742B - American male voice
|
|
53
|
+
"daniel", # onwK4e9ZLuTAKqWW03F9 - British male voice
|
|
54
|
+
"eric", # cjVigY5qzO86Huf0OWal - American male voice
|
|
55
|
+
"george", # JBFqnCBsd6RMkjVDRZzb - Raspy male voice
|
|
56
|
+
"jessica", # cgSgspJ2msm6clMCkdW9 - Warm female voice
|
|
57
|
+
"laura", # FGY2WhTYpPnrIDTdsKH5 - Soft female voice
|
|
58
|
+
"liam", # TX3LPaxmHKxFdv7VOQHJ - Irish male voice
|
|
59
|
+
"lily", # pFZP5JQG7iQjIQuC4Bku - British female voice
|
|
60
|
+
"matilda", # XrExE9yKIg1WjnnlVkGX - Warm female voice
|
|
61
|
+
"sarah", # EXAVITQu4vr4xnSDxMaL - Soft female voice
|
|
62
|
+
"will" # bIHbv24MWmeRgasZH58o - American male voice
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
# Voice mapping for ElevenLabs API compatibility
|
|
66
|
+
voice_mapping = {
|
|
67
|
+
"brian": "nPczCjzI2devNBz1zQrb",
|
|
68
|
+
"alice": "Xb7hH8MSUJpSbSDYk0k2",
|
|
69
|
+
"bill": "pqHfZKP75CvOlQylNhV4",
|
|
70
|
+
"callum": "N2lVS1w4EtoT3dr4eOWO",
|
|
71
|
+
"charlie": "IKne3meq5aSn9XLyUdCD",
|
|
72
|
+
"charlotte": "XB0fDUnXU5powFXDhCwa",
|
|
73
|
+
"chris": "iP95p4xoKVk53GoZ742B",
|
|
74
|
+
"daniel": "onwK4e9ZLuTAKqWW03F9",
|
|
75
|
+
"eric": "cjVigY5qzO86Huf0OWal",
|
|
76
|
+
"george": "JBFqnCBsd6RMkjVDRZzb",
|
|
77
|
+
"jessica": "cgSgspJ2msm6clMCkdW9",
|
|
78
|
+
"laura": "FGY2WhTYpPnrIDTdsKH5",
|
|
79
|
+
"liam": "TX3LPaxmHKxFdv7VOQHJ",
|
|
80
|
+
"lily": "pFZP5JQG7iQjIQuC4Bku",
|
|
81
|
+
"matilda": "XrExE9yKIg1WjnnlVkGX",
|
|
82
|
+
"sarah": "EXAVITQu4vr4xnSDxMaL",
|
|
83
|
+
"will": "bIHbv24MWmeRgasZH58o"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Legacy voice mapping for backward compatibility
|
|
87
|
+
all_voices: dict[str, str] = {
|
|
88
|
+
"Brian": "nPczCjzI2devNBz1zQrb", "Alice": "Xb7hH8MSUJpSbSDYk0k2", "Bill": "pqHfZKP75CvOlQylNhV4",
|
|
89
|
+
"Callum": "N2lVS1w4EtoT3dr4eOWO", "Charlie": "IKne3meq5aSn9XLyUdCD", "Charlotte": "XB0fDUnXU5powFXDhCwa",
|
|
90
|
+
"Chris": "iP95p4xoKVk53GoZ742B", "Daniel": "onwK4e9ZLuTAKqWW03F9", "Eric": "cjVigY5qzO86Huf0OWal",
|
|
91
|
+
"George": "JBFqnCBsd6RMkjVDRZzb", "Jessica": "cgSgspJ2msm6clMCkdW9", "Laura": "FGY2WhTYpPnrIDTdsKH5",
|
|
92
|
+
"Liam": "TX3LPaxmHKxFdv7VOQHJ", "Lily": "pFZP5JQG7iQjIQuC4Bku", "Matilda": "XrExE9yKIg1WjnnlVkGX",
|
|
93
|
+
"Sarah": "EXAVITQu4vr4xnSDxMaL", "Will": "bIHbv24MWmeRgasZH58o"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
def __init__(self, timeout: int = 20, proxies: dict = None):
|
|
97
|
+
"""
|
|
98
|
+
Initialize the ElevenLabs TTS client.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
timeout (int): Request timeout in seconds
|
|
102
|
+
proxies (dict): Proxy configuration
|
|
103
|
+
"""
|
|
104
|
+
super().__init__()
|
|
105
|
+
self.api_url = "https://api.elevenlabs.io/v1/text-to-speech"
|
|
106
|
+
self.session = requests.Session()
|
|
107
|
+
self.session.headers.update(self.headers)
|
|
108
|
+
if proxies:
|
|
109
|
+
self.session.proxies.update(proxies)
|
|
110
|
+
self.timeout = timeout
|
|
111
|
+
self.params = {'allow_unauthenticated': '1'}
|
|
112
|
+
self.default_voice = "brian"
|
|
113
|
+
|
|
114
|
+
def tts(
|
|
115
|
+
self,
|
|
116
|
+
text: str,
|
|
117
|
+
model: str = "gpt-4o-mini-tts",
|
|
118
|
+
voice: str = "brian",
|
|
119
|
+
response_format: str = "mp3",
|
|
120
|
+
instructions: str = None,
|
|
121
|
+
verbose: bool = True
|
|
122
|
+
) -> str:
|
|
123
|
+
"""
|
|
124
|
+
Convert text to speech using ElevenLabs API with OpenAI-compatible parameters.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
text (str): The text to convert to speech (max 10,000 characters)
|
|
128
|
+
model (str): The TTS model to use (gpt-4o-mini-tts, tts-1, tts-1-hd)
|
|
129
|
+
voice (str): The voice to use for TTS (brian, alice, bill, etc.)
|
|
130
|
+
response_format (str): Audio format (mp3, opus, aac, flac, wav, pcm)
|
|
131
|
+
instructions (str): Voice instructions (not used by ElevenLabs but kept for compatibility)
|
|
132
|
+
verbose (bool): Whether to print debug information
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
str: Path to the generated audio file
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
ValueError: If input parameters are invalid
|
|
139
|
+
exceptions.FailedToGenerateResponseError: If there is an error generating or saving the audio
|
|
140
|
+
"""
|
|
141
|
+
# Validate input parameters
|
|
142
|
+
if not text or not isinstance(text, str):
|
|
143
|
+
raise ValueError("Input text must be a non-empty string")
|
|
144
|
+
if len(text) > 10000:
|
|
145
|
+
raise ValueError("Input text exceeds maximum allowed length of 10,000 characters")
|
|
146
|
+
|
|
147
|
+
# Validate model, voice, and format using base class methods
|
|
148
|
+
model = self.validate_model(model)
|
|
149
|
+
voice = self.validate_voice(voice)
|
|
150
|
+
response_format = self.validate_format(response_format)
|
|
151
|
+
|
|
152
|
+
# Map voice to ElevenLabs API format
|
|
153
|
+
elevenlabs_voice = self.voice_mapping.get(voice, voice)
|
|
154
|
+
|
|
155
|
+
# Create temporary file with appropriate extension
|
|
156
|
+
file_extension = f".{response_format}" if response_format != "pcm" else ".wav"
|
|
157
|
+
filename = pathlib.Path(tempfile.mktemp(suffix=file_extension, dir=self.temp_dir))
|
|
158
|
+
|
|
159
|
+
# Split text into sentences using the utils module for better processing
|
|
160
|
+
sentences = utils.split_sentences(text)
|
|
161
|
+
if verbose:
|
|
162
|
+
print(f"[debug] Processing {len(sentences)} sentences")
|
|
163
|
+
print(f"[debug] Model: {model}")
|
|
164
|
+
print(f"[debug] Voice: {voice} -> {elevenlabs_voice}")
|
|
165
|
+
print(f"[debug] Format: {response_format}")
|
|
166
|
+
|
|
167
|
+
def generate_audio_for_chunk(part_text: str, part_number: int):
|
|
168
|
+
"""
|
|
169
|
+
Generate audio for a single chunk of text.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
part_text (str): The text chunk to convert
|
|
173
|
+
part_number (int): The chunk number for ordering
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
tuple: (part_number, audio_data)
|
|
177
|
+
|
|
178
|
+
Raises:
|
|
179
|
+
requests.RequestException: If there's an API error
|
|
180
|
+
"""
|
|
181
|
+
max_retries = 3
|
|
182
|
+
retry_count = 0
|
|
183
|
+
|
|
184
|
+
while retry_count < max_retries:
|
|
185
|
+
try:
|
|
186
|
+
json_data = {
|
|
187
|
+
'text': part_text,
|
|
188
|
+
'model_id': 'eleven_multilingual_v2',
|
|
189
|
+
# Add model parameter for future ElevenLabs API compatibility
|
|
190
|
+
'tts_model': model
|
|
191
|
+
}
|
|
192
|
+
response = self.session.post(
|
|
193
|
+
url=f'{self.api_url}/{elevenlabs_voice}',
|
|
194
|
+
params=self.params,
|
|
195
|
+
headers=self.headers,
|
|
196
|
+
json=json_data,
|
|
197
|
+
timeout=self.timeout
|
|
198
|
+
)
|
|
199
|
+
response.raise_for_status()
|
|
200
|
+
|
|
201
|
+
# Check if the request was successful
|
|
202
|
+
if response.ok and response.status_code == 200:
|
|
203
|
+
if verbose:
|
|
204
|
+
print(f"[debug] Chunk {part_number} processed successfully")
|
|
205
|
+
return part_number, response.content
|
|
206
|
+
else:
|
|
207
|
+
if verbose:
|
|
208
|
+
print(f"[debug] No data received for chunk {part_number}. Retrying...")
|
|
209
|
+
|
|
210
|
+
except requests.RequestException as e:
|
|
211
|
+
if verbose:
|
|
212
|
+
print(f"[debug] Error for chunk {part_number}: {e}. Retrying...")
|
|
213
|
+
retry_count += 1
|
|
214
|
+
time.sleep(1)
|
|
215
|
+
|
|
216
|
+
raise exceptions.FailedToGenerateResponseError(f"Failed to generate audio for chunk {part_number} after {max_retries} attempts")
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
# Using ThreadPoolExecutor to handle requests concurrently
|
|
220
|
+
with ThreadPoolExecutor() as executor:
|
|
221
|
+
futures = {
|
|
222
|
+
executor.submit(generate_audio_for_chunk, sentence.strip(), chunk_num): chunk_num
|
|
223
|
+
for chunk_num, sentence in enumerate(sentences, start=1)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# Dictionary to store results with order preserved
|
|
227
|
+
audio_chunks = {}
|
|
228
|
+
|
|
229
|
+
for future in as_completed(futures):
|
|
230
|
+
chunk_num = futures[future]
|
|
231
|
+
try:
|
|
232
|
+
part_number, audio_data = future.result()
|
|
233
|
+
audio_chunks[part_number] = audio_data # Store the audio data in correct sequence
|
|
234
|
+
except Exception as e:
|
|
235
|
+
raise exceptions.FailedToGenerateResponseError(f"Failed to generate audio for chunk {chunk_num}: {str(e)}")
|
|
236
|
+
|
|
237
|
+
# Combine all audio chunks in order
|
|
238
|
+
combined_audio = BytesIO()
|
|
239
|
+
for part_number in sorted(audio_chunks.keys()):
|
|
240
|
+
combined_audio.write(audio_chunks[part_number])
|
|
241
|
+
if verbose:
|
|
242
|
+
print(f"[debug] Added chunk {part_number} to the combined file.")
|
|
243
|
+
|
|
244
|
+
# Save the combined audio data to a single file
|
|
245
|
+
with open(filename, 'wb') as f:
|
|
246
|
+
f.write(combined_audio.getvalue())
|
|
247
|
+
|
|
248
|
+
if verbose:
|
|
249
|
+
print(f"[debug] Speech generated successfully")
|
|
250
|
+
print(f"[debug] Audio saved to {filename}")
|
|
251
|
+
|
|
252
|
+
return filename.as_posix()
|
|
253
|
+
|
|
254
|
+
except requests.exceptions.RequestException as e:
|
|
255
|
+
if verbose:
|
|
256
|
+
print(f"[debug] Failed to perform the operation: {e}")
|
|
257
|
+
raise exceptions.FailedToGenerateResponseError(
|
|
258
|
+
f"Failed to perform the operation: {e}"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def create_speech(
|
|
262
|
+
self,
|
|
263
|
+
input: str,
|
|
264
|
+
model: str = "gpt-4o-mini-tts",
|
|
265
|
+
voice: str = "brian",
|
|
266
|
+
response_format: str = "mp3",
|
|
267
|
+
instructions: str = None,
|
|
268
|
+
verbose: bool = False
|
|
269
|
+
) -> str:
|
|
270
|
+
"""
|
|
271
|
+
OpenAI-compatible speech creation interface.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
input (str): The text to convert to speech
|
|
275
|
+
model (str): The TTS model to use
|
|
276
|
+
voice (str): The voice to use
|
|
277
|
+
response_format (str): Audio format
|
|
278
|
+
instructions (str): Voice instructions (not used by ElevenLabs)
|
|
279
|
+
verbose (bool): Whether to print debug information
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
str: Path to the generated audio file
|
|
283
|
+
"""
|
|
284
|
+
return self.tts(
|
|
285
|
+
text=input,
|
|
286
|
+
model=model,
|
|
287
|
+
voice=voice,
|
|
288
|
+
response_format=response_format,
|
|
289
|
+
instructions=instructions,
|
|
290
|
+
verbose=verbose
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
def with_streaming_response(self):
|
|
294
|
+
"""
|
|
295
|
+
Return a streaming response context manager (OpenAI-compatible).
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
StreamingResponseContextManager: Context manager for streaming responses
|
|
299
|
+
"""
|
|
300
|
+
return StreamingResponseContextManager(self)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class StreamingResponseContextManager:
|
|
304
|
+
"""
|
|
305
|
+
Context manager for streaming TTS responses (OpenAI-compatible).
|
|
306
|
+
"""
|
|
307
|
+
|
|
308
|
+
def __init__(self, tts_provider: ElevenlabsTTS):
|
|
309
|
+
self.tts_provider = tts_provider
|
|
310
|
+
self.audio_file = None
|
|
311
|
+
|
|
312
|
+
def create(
|
|
313
|
+
self,
|
|
314
|
+
input: str,
|
|
315
|
+
model: str = "gpt-4o-mini-tts",
|
|
316
|
+
voice: str = "brian",
|
|
317
|
+
response_format: str = "mp3",
|
|
318
|
+
instructions: str = None
|
|
319
|
+
):
|
|
320
|
+
"""
|
|
321
|
+
Create speech with streaming capability.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
input (str): The text to convert to speech
|
|
325
|
+
model (str): The TTS model to use
|
|
326
|
+
voice (str): The voice to use
|
|
327
|
+
response_format (str): Audio format
|
|
328
|
+
instructions (str): Voice instructions
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
StreamingResponse: Streaming response object
|
|
332
|
+
"""
|
|
333
|
+
self.audio_file = self.tts_provider.create_speech(
|
|
334
|
+
input=input,
|
|
335
|
+
model=model,
|
|
336
|
+
voice=voice,
|
|
337
|
+
response_format=response_format,
|
|
338
|
+
instructions=instructions
|
|
339
|
+
)
|
|
340
|
+
return StreamingResponse(self.audio_file)
|
|
341
|
+
|
|
342
|
+
def __enter__(self):
|
|
343
|
+
return self
|
|
344
|
+
|
|
345
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
346
|
+
pass
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
class StreamingResponse:
|
|
350
|
+
"""
|
|
351
|
+
Streaming response object for TTS audio (OpenAI-compatible).
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
def __init__(self, audio_file: str):
|
|
355
|
+
self.audio_file = audio_file
|
|
356
|
+
|
|
357
|
+
def __enter__(self):
|
|
358
|
+
"""Enter the context manager."""
|
|
359
|
+
return self
|
|
360
|
+
|
|
361
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
362
|
+
"""Exit the context manager."""
|
|
363
|
+
pass
|
|
364
|
+
|
|
365
|
+
def stream_to_file(self, file_path: str, chunk_size: int = 1024):
|
|
366
|
+
"""
|
|
367
|
+
Stream audio content to a file.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
file_path (str): Destination file path
|
|
371
|
+
chunk_size (int): Size of chunks to read/write
|
|
372
|
+
"""
|
|
373
|
+
import shutil
|
|
374
|
+
shutil.copy2(self.audio_file, file_path)
|
|
375
|
+
|
|
376
|
+
def iter_bytes(self, chunk_size: int = 1024):
|
|
377
|
+
"""
|
|
378
|
+
Iterate over audio bytes in chunks.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
chunk_size (int): Size of chunks to yield
|
|
382
|
+
|
|
383
|
+
Yields:
|
|
384
|
+
bytes: Audio data chunks
|
|
385
|
+
"""
|
|
386
|
+
with open(self.audio_file, 'rb') as f:
|
|
387
|
+
while chunk := f.read(chunk_size):
|
|
388
|
+
yield chunk
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
# Example usage
|
|
392
|
+
if __name__ == "__main__":
|
|
393
|
+
# Example usage demonstrating OpenAI-compatible interface
|
|
394
|
+
elevenlabs = ElevenlabsTTS()
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
# Basic usage
|
|
398
|
+
print("Testing basic speech generation...")
|
|
399
|
+
audio_file = elevenlabs.create_speech(
|
|
400
|
+
input="Today is a wonderful day to build something people love!",
|
|
401
|
+
model="gpt-4o-mini-tts",
|
|
402
|
+
voice="brian",
|
|
403
|
+
instructions="Speak in a cheerful and positive tone."
|
|
404
|
+
)
|
|
405
|
+
print(f"Audio file generated: {audio_file}")
|
|
406
|
+
|
|
407
|
+
# Streaming usage
|
|
408
|
+
print("\nTesting streaming response...")
|
|
409
|
+
with elevenlabs.with_streaming_response().create(
|
|
410
|
+
input="This is a streaming test with ElevenLabs voices.",
|
|
411
|
+
voice="alice",
|
|
412
|
+
response_format="wav"
|
|
413
|
+
) as response:
|
|
414
|
+
response.stream_to_file("elevenlabs_streaming_test.wav")
|
|
415
|
+
print("Streaming audio saved to elevenlabs_streaming_test.wav")
|
|
416
|
+
|
|
417
|
+
# Legacy compatibility test
|
|
418
|
+
print("\nTesting legacy voice compatibility...")
|
|
419
|
+
legacy_audio = elevenlabs.tts("Testing legacy voice format.", voice="brian")
|
|
420
|
+
print(f"Legacy audio file generated: {legacy_audio}")
|
|
421
|
+
|
|
422
|
+
except exceptions.FailedToGenerateResponseError as e:
|
|
423
|
+
print(f"Error: {e}")
|
|
424
|
+
except Exception as e:
|
|
425
|
+
print(f"Unexpected error: {e}")
|