webscout 8.2.1__py3-none-any.whl → 8.2.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/AIbase.py +144 -7
- webscout/Bard.py +5 -0
- webscout/Extra/tempmail/__init__.py +2 -0
- webscout/Extra/tempmail/base.py +6 -1
- webscout/Extra/tempmail/emailnator.py +84 -0
- webscout/Local/__init__.py +8 -2
- webscout/Local/cli.py +178 -0
- webscout/Local/llm.py +104 -5
- webscout/Local/model_manager.py +48 -0
- webscout/Local/server.py +547 -13
- webscout/Provider/Cloudflare.py +5 -0
- webscout/Provider/Gemini.py +2 -0
- webscout/Provider/OPENAI/e2b.py +159 -1
- webscout/Provider/OPENAI/textpollinations.py +90 -44
- webscout/Provider/OPENAI/toolbaz.py +4 -4
- webscout/Provider/TTS/__init__.py +1 -0
- webscout/Provider/TTS/base.py +159 -0
- webscout/Provider/TTS/deepgram.py +16 -16
- webscout/Provider/TTS/elevenlabs.py +5 -5
- webscout/Provider/TTS/gesserit.py +6 -5
- webscout/Provider/TTS/murfai.py +7 -7
- webscout/Provider/TTS/parler.py +6 -6
- webscout/Provider/TTS/speechma.py +22 -22
- webscout/Provider/TTS/streamElements.py +7 -7
- webscout/Provider/TextPollinationsAI.py +56 -41
- webscout/Provider/toolbaz.py +4 -4
- webscout/version.py +1 -1
- {webscout-8.2.1.dist-info → webscout-8.2.3.dist-info}/METADATA +1 -1
- {webscout-8.2.1.dist-info → webscout-8.2.3.dist-info}/RECORD +33 -31
- {webscout-8.2.1.dist-info → webscout-8.2.3.dist-info}/LICENSE.md +0 -0
- {webscout-8.2.1.dist-info → webscout-8.2.3.dist-info}/WHEEL +0 -0
- {webscout-8.2.1.dist-info → webscout-8.2.3.dist-info}/entry_points.txt +0 -0
- {webscout-8.2.1.dist-info → webscout-8.2.3.dist-info}/top_level.txt +0 -0
|
@@ -5,12 +5,12 @@ import base64
|
|
|
5
5
|
import tempfile
|
|
6
6
|
from io import BytesIO
|
|
7
7
|
from webscout import exceptions
|
|
8
|
-
from webscout.AIbase import TTSProvider
|
|
9
8
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
10
9
|
from webscout.litagent import LitAgent
|
|
11
|
-
from . import utils
|
|
10
|
+
from . import utils
|
|
11
|
+
from .base import BaseTTSProvider
|
|
12
12
|
|
|
13
|
-
class DeepgramTTS(
|
|
13
|
+
class DeepgramTTS(BaseTTSProvider):
|
|
14
14
|
"""
|
|
15
15
|
Text-to-speech provider using the DeepgramTTS API.
|
|
16
16
|
"""
|
|
@@ -27,12 +27,12 @@ class DeepgramTTS(TTSProvider):
|
|
|
27
27
|
|
|
28
28
|
def __init__(self, timeout: int = 20, proxies: dict = None):
|
|
29
29
|
"""Initializes the DeepgramTTS TTS client."""
|
|
30
|
+
super().__init__()
|
|
30
31
|
self.session = requests.Session()
|
|
31
32
|
self.session.headers.update(self.headers)
|
|
32
33
|
if proxies:
|
|
33
34
|
self.session.proxies.update(proxies)
|
|
34
35
|
self.timeout = timeout
|
|
35
|
-
self.temp_dir = tempfile.mkdtemp(prefix="webscout_tts_")
|
|
36
36
|
|
|
37
37
|
def tts(self, text: str, voice: str = "Brian", verbose: bool = True) -> str:
|
|
38
38
|
"""
|
|
@@ -80,15 +80,15 @@ class DeepgramTTS(TTSProvider):
|
|
|
80
80
|
"""
|
|
81
81
|
max_retries = 3
|
|
82
82
|
retry_count = 0
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
while retry_count < max_retries:
|
|
85
85
|
try:
|
|
86
86
|
payload = {"text": part_text, "model": self.all_voices[voice]}
|
|
87
87
|
response = self.session.post(
|
|
88
|
-
url=url,
|
|
89
|
-
headers=self.headers,
|
|
90
|
-
json=payload,
|
|
91
|
-
stream=True,
|
|
88
|
+
url=url,
|
|
89
|
+
headers=self.headers,
|
|
90
|
+
json=payload,
|
|
91
|
+
stream=True,
|
|
92
92
|
timeout=self.timeout
|
|
93
93
|
)
|
|
94
94
|
response.raise_for_status()
|
|
@@ -99,29 +99,29 @@ class DeepgramTTS(TTSProvider):
|
|
|
99
99
|
if verbose:
|
|
100
100
|
print(f"[debug] Chunk {part_number} processed successfully")
|
|
101
101
|
return part_number, audio_data
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
if verbose:
|
|
104
104
|
print(f"[debug] No data received for chunk {part_number}. Attempt {retry_count + 1}/{max_retries}")
|
|
105
|
-
|
|
105
|
+
|
|
106
106
|
except requests.RequestException as e:
|
|
107
107
|
if verbose:
|
|
108
108
|
print(f"[debug] Error processing chunk {part_number}: {str(e)}. Attempt {retry_count + 1}/{max_retries}")
|
|
109
109
|
if retry_count == max_retries - 1:
|
|
110
110
|
raise
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
retry_count += 1
|
|
113
113
|
time.sleep(1)
|
|
114
|
-
|
|
114
|
+
|
|
115
115
|
raise RuntimeError(f"Failed to generate audio for chunk {part_number} after {max_retries} attempts")
|
|
116
116
|
|
|
117
117
|
try:
|
|
118
118
|
# Using ThreadPoolExecutor to handle requests concurrently
|
|
119
119
|
with ThreadPoolExecutor() as executor:
|
|
120
120
|
futures = {
|
|
121
|
-
executor.submit(generate_audio_for_chunk, sentence.strip(), chunk_num): chunk_num
|
|
121
|
+
executor.submit(generate_audio_for_chunk, sentence.strip(), chunk_num): chunk_num
|
|
122
122
|
for chunk_num, sentence in enumerate(sentences, start=1)
|
|
123
123
|
}
|
|
124
|
-
|
|
124
|
+
|
|
125
125
|
# Dictionary to store results with order preserved
|
|
126
126
|
audio_chunks = {}
|
|
127
127
|
|
|
@@ -152,5 +152,5 @@ if __name__ == "__main__":
|
|
|
152
152
|
text = "This is a test of the DeepgramTTS text-to-speech API. It supports multiple sentences. Let's see how it works!"
|
|
153
153
|
|
|
154
154
|
print("[debug] Generating audio...")
|
|
155
|
-
audio_file = deepgram.tts(text, voice="Asteria")
|
|
155
|
+
audio_file = deepgram.tts(text, voice="Asteria")
|
|
156
156
|
print(f"Audio saved to: {audio_file}")
|
|
@@ -4,12 +4,12 @@ import pathlib
|
|
|
4
4
|
import tempfile
|
|
5
5
|
from io import BytesIO
|
|
6
6
|
from webscout import exceptions
|
|
7
|
-
from webscout.AIbase import TTSProvider
|
|
8
7
|
from webscout.litagent import LitAgent
|
|
9
8
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
10
9
|
from . import utils
|
|
10
|
+
from .base import BaseTTSProvider
|
|
11
11
|
|
|
12
|
-
class ElevenlabsTTS(
|
|
12
|
+
class ElevenlabsTTS(BaseTTSProvider):
|
|
13
13
|
"""
|
|
14
14
|
Text-to-speech provider using the ElevenlabsTTS API.
|
|
15
15
|
"""
|
|
@@ -21,13 +21,13 @@ class ElevenlabsTTS(TTSProvider):
|
|
|
21
21
|
|
|
22
22
|
def __init__(self, timeout: int = 20, proxies: dict = None):
|
|
23
23
|
"""Initializes the ElevenlabsTTS TTS client."""
|
|
24
|
+
super().__init__()
|
|
24
25
|
self.session = requests.Session()
|
|
25
26
|
self.session.headers.update(self.headers)
|
|
26
27
|
if proxies:
|
|
27
28
|
self.session.proxies.update(proxies)
|
|
28
29
|
self.timeout = timeout
|
|
29
30
|
self.params = {'allow_unauthenticated': '1'}
|
|
30
|
-
self.temp_dir = tempfile.mkdtemp(prefix="webscout_tts_")
|
|
31
31
|
|
|
32
32
|
def tts(self, text: str, voice: str = "Brian", verbose:bool = True) -> str:
|
|
33
33
|
"""
|
|
@@ -65,9 +65,9 @@ class ElevenlabsTTS(TTSProvider):
|
|
|
65
65
|
try:
|
|
66
66
|
# Using ThreadPoolExecutor to handle requests concurrently
|
|
67
67
|
with ThreadPoolExecutor() as executor:
|
|
68
|
-
futures = {executor.submit(generate_audio_for_chunk, sentence.strip(), chunk_num): chunk_num
|
|
68
|
+
futures = {executor.submit(generate_audio_for_chunk, sentence.strip(), chunk_num): chunk_num
|
|
69
69
|
for chunk_num, sentence in enumerate(sentences, start=1)}
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
# Dictionary to store results with order preserved
|
|
72
72
|
audio_chunks = {}
|
|
73
73
|
|
|
@@ -4,12 +4,12 @@ import pathlib
|
|
|
4
4
|
import base64
|
|
5
5
|
from io import BytesIO
|
|
6
6
|
from webscout import exceptions
|
|
7
|
-
from webscout.AIbase import TTSProvider
|
|
8
7
|
from webscout.litagent import LitAgent
|
|
9
8
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
10
9
|
from . import utils
|
|
10
|
+
from .base import BaseTTSProvider
|
|
11
11
|
|
|
12
|
-
class GesseritTTS(
|
|
12
|
+
class GesseritTTS(BaseTTSProvider):
|
|
13
13
|
"""Text-to-speech provider using the GesseritTTS API."""
|
|
14
14
|
# Request headers
|
|
15
15
|
headers: dict[str, str] = {
|
|
@@ -30,6 +30,7 @@ class GesseritTTS(TTSProvider):
|
|
|
30
30
|
|
|
31
31
|
def __init__(self, timeout: int = 20, proxies: dict = None):
|
|
32
32
|
"""Initializes the GesseritTTS TTS client."""
|
|
33
|
+
super().__init__()
|
|
33
34
|
self.session = requests.Session()
|
|
34
35
|
self.session.headers.update(self.headers)
|
|
35
36
|
if proxies:
|
|
@@ -61,7 +62,7 @@ class GesseritTTS(TTSProvider):
|
|
|
61
62
|
response.raise_for_status()
|
|
62
63
|
|
|
63
64
|
# Create the audio_cache directory if it doesn't exist
|
|
64
|
-
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
65
66
|
|
|
66
67
|
# Check if the request was successful
|
|
67
68
|
if response.ok and response.status_code == 200:
|
|
@@ -81,9 +82,9 @@ class GesseritTTS(TTSProvider):
|
|
|
81
82
|
try:
|
|
82
83
|
# Using ThreadPoolExecutor to handle requests concurrently
|
|
83
84
|
with ThreadPoolExecutor() as executor:
|
|
84
|
-
futures = {executor.submit(generate_audio_for_chunk, sentence.strip(), chunk_num): chunk_num
|
|
85
|
+
futures = {executor.submit(generate_audio_for_chunk, sentence.strip(), chunk_num): chunk_num
|
|
85
86
|
for chunk_num, sentence in enumerate(sentences, start=1)}
|
|
86
|
-
|
|
87
|
+
|
|
87
88
|
# Dictionary to store results with order preserved
|
|
88
89
|
audio_chunks = {}
|
|
89
90
|
|
webscout/Provider/TTS/murfai.py
CHANGED
|
@@ -5,12 +5,12 @@ import tempfile
|
|
|
5
5
|
from io import BytesIO
|
|
6
6
|
from urllib.parse import urlencode
|
|
7
7
|
from webscout import exceptions
|
|
8
|
-
from webscout.AIbase import TTSProvider
|
|
9
8
|
from webscout.litagent import LitAgent
|
|
10
9
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
11
10
|
from . import utils
|
|
11
|
+
from .base import BaseTTSProvider
|
|
12
12
|
|
|
13
|
-
class MurfAITTS(
|
|
13
|
+
class MurfAITTS(BaseTTSProvider):
|
|
14
14
|
"""Text-to-speech provider using the MurfAITTS API."""
|
|
15
15
|
# Request headers
|
|
16
16
|
headers: dict[str, str] = {
|
|
@@ -20,12 +20,12 @@ class MurfAITTS(TTSProvider):
|
|
|
20
20
|
|
|
21
21
|
def __init__(self, timeout: int = 20, proxies: dict = None):
|
|
22
22
|
"""Initializes the MurfAITTS TTS client."""
|
|
23
|
+
super().__init__()
|
|
23
24
|
self.session = requests.Session()
|
|
24
25
|
self.session.headers.update(self.headers)
|
|
25
26
|
if proxies:
|
|
26
27
|
self.session.proxies.update(proxies)
|
|
27
28
|
self.timeout = timeout
|
|
28
|
-
self.temp_dir = tempfile.mkdtemp(prefix="webscout_tts_")
|
|
29
29
|
|
|
30
30
|
def tts(self, text: str, voice: str = "Hazel", verbose:bool = True) -> str:
|
|
31
31
|
"""Converts text to speech using the MurfAITTS API and saves it to a file."""
|
|
@@ -45,7 +45,7 @@ class MurfAITTS(TTSProvider):
|
|
|
45
45
|
while True:
|
|
46
46
|
try:
|
|
47
47
|
params: dict[str, str] = {
|
|
48
|
-
"name": voice_id,
|
|
48
|
+
"name": voice_id,
|
|
49
49
|
"text": part_text
|
|
50
50
|
}
|
|
51
51
|
encode_param: str = urlencode(params)
|
|
@@ -67,9 +67,9 @@ class MurfAITTS(TTSProvider):
|
|
|
67
67
|
try:
|
|
68
68
|
# Using ThreadPoolExecutor to handle requests concurrently
|
|
69
69
|
with ThreadPoolExecutor() as executor:
|
|
70
|
-
futures = {executor.submit(generate_audio_for_chunk, sentence.strip(), chunk_num): chunk_num
|
|
70
|
+
futures = {executor.submit(generate_audio_for_chunk, sentence.strip(), chunk_num): chunk_num
|
|
71
71
|
for chunk_num, sentence in enumerate(sentences, start=1)}
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
# Dictionary to store results with order preserved
|
|
74
74
|
audio_chunks = {}
|
|
75
75
|
|
|
@@ -109,5 +109,5 @@ if __name__ == "__main__":
|
|
|
109
109
|
text = "This is a test of the MurfAITTS text-to-speech API. It supports multiple sentences and advanced logging."
|
|
110
110
|
|
|
111
111
|
print("[debug] Generating audio...")
|
|
112
|
-
audio_file = murfai.tts(text, voice="Hazel")
|
|
112
|
+
audio_file = murfai.tts(text, voice="Hazel")
|
|
113
113
|
print(f"Audio saved to: {audio_file}")
|
webscout/Provider/TTS/parler.py
CHANGED
|
@@ -2,22 +2,22 @@ import time
|
|
|
2
2
|
import tempfile
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from webscout import exceptions
|
|
5
|
-
from webscout.AIbase import TTSProvider
|
|
6
5
|
from gradio_client import Client
|
|
7
6
|
import os
|
|
7
|
+
from .base import BaseTTSProvider
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class ParlerTTS(
|
|
10
|
+
class ParlerTTS(BaseTTSProvider):
|
|
11
11
|
"""
|
|
12
12
|
A class to interact with the Parler TTS API through Gradio Client.
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
def __init__(self, timeout: int = 20, proxies: dict = None):
|
|
16
16
|
"""Initializes the Parler TTS client."""
|
|
17
|
+
super().__init__()
|
|
17
18
|
self.api_endpoint = "/gen_tts"
|
|
18
19
|
self.client = Client("parler-tts/parler_tts") # Initialize the Gradio client
|
|
19
20
|
self.timeout = timeout
|
|
20
|
-
self.temp_dir = tempfile.mkdtemp(prefix="webscout_tts_")
|
|
21
21
|
|
|
22
22
|
def tts(self, text: str, description: str = "", use_large: bool = False, verbose: bool = True) -> str:
|
|
23
23
|
"""
|
|
@@ -40,7 +40,7 @@ class ParlerTTS(TTSProvider):
|
|
|
40
40
|
try:
|
|
41
41
|
if verbose:
|
|
42
42
|
print(f"[debug] Generating TTS with description: {description}")
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
result = self.client.predict(
|
|
45
45
|
text=text,
|
|
46
46
|
description=description,
|
|
@@ -57,10 +57,10 @@ class ParlerTTS(TTSProvider):
|
|
|
57
57
|
raise ValueError(f"Unexpected response from API: {result}")
|
|
58
58
|
|
|
59
59
|
self._save_audio(audio_bytes, filename, verbose)
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
if verbose:
|
|
62
62
|
print(f"[debug] Audio generated successfully: {filename}")
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
return filename.as_posix()
|
|
65
65
|
|
|
66
66
|
except Exception as e:
|
|
@@ -7,12 +7,12 @@ import pathlib
|
|
|
7
7
|
import tempfile
|
|
8
8
|
from io import BytesIO
|
|
9
9
|
from webscout import exceptions
|
|
10
|
-
from webscout.AIbase import TTSProvider
|
|
11
10
|
from webscout.litagent import LitAgent
|
|
12
11
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
13
12
|
from . import utils
|
|
13
|
+
from .base import BaseTTSProvider
|
|
14
14
|
|
|
15
|
-
class SpeechMaTTS(
|
|
15
|
+
class SpeechMaTTS(BaseTTSProvider):
|
|
16
16
|
"""
|
|
17
17
|
Text-to-speech provider using the SpeechMa API.
|
|
18
18
|
"""
|
|
@@ -25,7 +25,7 @@ class SpeechMaTTS(TTSProvider):
|
|
|
25
25
|
"priority": "u=1, i",
|
|
26
26
|
"User-Agent": LitAgent().random()
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
# Available voices with their IDs
|
|
30
30
|
all_voices = {
|
|
31
31
|
"Ava": "voice-110", # Multilingual female voice
|
|
@@ -36,13 +36,13 @@ class SpeechMaTTS(TTSProvider):
|
|
|
36
36
|
|
|
37
37
|
def __init__(self, timeout: int = 20, proxies: dict = None):
|
|
38
38
|
"""Initializes the SpeechMa TTS client."""
|
|
39
|
+
super().__init__()
|
|
39
40
|
self.api_url = "https://speechma.com/com.api/tts-api.php"
|
|
40
41
|
self.session = requests.Session()
|
|
41
42
|
self.session.headers.update(self.headers)
|
|
42
43
|
if proxies:
|
|
43
44
|
self.session.proxies.update(proxies)
|
|
44
45
|
self.timeout = timeout
|
|
45
|
-
self.temp_dir = tempfile.mkdtemp(prefix="webscout_tts_")
|
|
46
46
|
|
|
47
47
|
def tts(self, text: str, voice: str = "Emma", pitch: int = 0, rate: int = 0, verbose: bool = False) -> str:
|
|
48
48
|
"""
|
|
@@ -57,7 +57,7 @@ class SpeechMaTTS(TTSProvider):
|
|
|
57
57
|
|
|
58
58
|
Returns:
|
|
59
59
|
str: Path to the generated audio file
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
Raises:
|
|
62
62
|
exceptions.FailedToGenerateResponseError: If there is an error generating or saving the audio.
|
|
63
63
|
"""
|
|
@@ -66,26 +66,26 @@ class SpeechMaTTS(TTSProvider):
|
|
|
66
66
|
), f"Voice '{voice}' not one of [{', '.join(self.all_voices.keys())}]"
|
|
67
67
|
|
|
68
68
|
filename = pathlib.Path(tempfile.mktemp(suffix=".mp3", dir=self.temp_dir))
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
# Get the voice ID
|
|
71
71
|
voice_id = self.all_voices[voice]
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
if verbose:
|
|
74
74
|
print(f"[debug] Using voice: {voice} (ID: {voice_id})")
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
# Split text into sentences for better processing
|
|
77
77
|
sentences = utils.split_sentences(text)
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
if verbose:
|
|
80
80
|
print(f"[debug] Text split into {len(sentences)} sentences")
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
# Function to request audio for each chunk
|
|
83
83
|
def generate_audio_for_chunk(part_text: str, part_number: int):
|
|
84
84
|
while True:
|
|
85
85
|
try:
|
|
86
86
|
if verbose:
|
|
87
87
|
print(f"[debug] Processing chunk {part_number}: '{part_text[:30]}...'")
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
# Prepare payload for this sentence
|
|
90
90
|
payload = {
|
|
91
91
|
"text": part_text,
|
|
@@ -93,11 +93,11 @@ class SpeechMaTTS(TTSProvider):
|
|
|
93
93
|
"pitch": pitch,
|
|
94
94
|
"rate": rate
|
|
95
95
|
}
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
response = self.session.post(
|
|
98
|
-
self.api_url,
|
|
99
|
-
headers=self.headers,
|
|
100
|
-
json=payload,
|
|
98
|
+
self.api_url,
|
|
99
|
+
headers=self.headers,
|
|
100
|
+
json=payload,
|
|
101
101
|
timeout=self.timeout
|
|
102
102
|
)
|
|
103
103
|
response.raise_for_status()
|
|
@@ -115,23 +115,23 @@ class SpeechMaTTS(TTSProvider):
|
|
|
115
115
|
else:
|
|
116
116
|
if verbose:
|
|
117
117
|
print(f"[debug] Failed request for chunk {part_number} (status code: {response.status_code}). Retrying...")
|
|
118
|
-
|
|
118
|
+
|
|
119
119
|
# If we get here, something went wrong with the request
|
|
120
120
|
time.sleep(1)
|
|
121
121
|
except requests.RequestException as e:
|
|
122
122
|
if verbose:
|
|
123
123
|
print(f"[debug] Error for chunk {part_number}: {e}. Retrying...")
|
|
124
124
|
time.sleep(1)
|
|
125
|
-
|
|
125
|
+
|
|
126
126
|
try:
|
|
127
127
|
if verbose:
|
|
128
128
|
print(f"[debug] Starting concurrent audio generation for {len(sentences)} chunks")
|
|
129
|
-
|
|
129
|
+
|
|
130
130
|
# Using ThreadPoolExecutor to handle requests concurrently
|
|
131
131
|
with ThreadPoolExecutor() as executor:
|
|
132
|
-
futures = {executor.submit(generate_audio_for_chunk, sentence.strip(), chunk_num): chunk_num
|
|
132
|
+
futures = {executor.submit(generate_audio_for_chunk, sentence.strip(), chunk_num): chunk_num
|
|
133
133
|
for chunk_num, sentence in enumerate(sentences, start=1)}
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
# Dictionary to store results with order preserved
|
|
136
136
|
audio_chunks = {}
|
|
137
137
|
|
|
@@ -159,10 +159,10 @@ class SpeechMaTTS(TTSProvider):
|
|
|
159
159
|
# Save the combined audio data to a single file
|
|
160
160
|
with open(filename, 'wb') as f:
|
|
161
161
|
f.write(combined_audio.getvalue())
|
|
162
|
-
|
|
162
|
+
|
|
163
163
|
if verbose:
|
|
164
164
|
print(f"[debug] Final audio saved as {filename}")
|
|
165
|
-
|
|
165
|
+
|
|
166
166
|
return filename.as_posix()
|
|
167
167
|
|
|
168
168
|
except requests.exceptions.RequestException as e:
|
|
@@ -6,12 +6,12 @@ import tempfile
|
|
|
6
6
|
from typing import Union
|
|
7
7
|
from io import BytesIO
|
|
8
8
|
from webscout import exceptions
|
|
9
|
-
from webscout.AIbase import TTSProvider
|
|
10
9
|
from webscout.litagent import LitAgent
|
|
11
10
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
12
11
|
from . import utils
|
|
12
|
+
from .base import BaseTTSProvider
|
|
13
13
|
|
|
14
|
-
class StreamElements(
|
|
14
|
+
class StreamElements(BaseTTSProvider):
|
|
15
15
|
"""
|
|
16
16
|
Text-to-speech provider using the StreamElements API.
|
|
17
17
|
"""
|
|
@@ -231,12 +231,12 @@ class StreamElements(TTSProvider):
|
|
|
231
231
|
|
|
232
232
|
def __init__(self, timeout: int = 20, proxies: dict = None):
|
|
233
233
|
"""Initializes the StreamElements TTS client."""
|
|
234
|
+
super().__init__()
|
|
234
235
|
self.session = requests.Session()
|
|
235
236
|
self.session.headers.update(self.headers)
|
|
236
237
|
if proxies:
|
|
237
238
|
self.session.proxies.update(proxies)
|
|
238
239
|
self.timeout = timeout
|
|
239
|
-
self.temp_dir = tempfile.mkdtemp(prefix="webscout_tts_")
|
|
240
240
|
|
|
241
241
|
def tts(self, text: str, voice: str = "Mathieu", verbose: bool = True) -> str:
|
|
242
242
|
"""
|
|
@@ -266,9 +266,9 @@ class StreamElements(TTSProvider):
|
|
|
266
266
|
# URL encode the text and voice
|
|
267
267
|
encoded_text = urllib.parse.quote(part_text)
|
|
268
268
|
encoded_voice = urllib.parse.quote(voice)
|
|
269
|
-
|
|
269
|
+
|
|
270
270
|
url = f"https://streamelements.com/tts/{encoded_voice}/{encoded_text}"
|
|
271
|
-
|
|
271
|
+
|
|
272
272
|
response = self.session.get(url, headers=self.headers, timeout=self.timeout)
|
|
273
273
|
response.raise_for_status()
|
|
274
274
|
|
|
@@ -287,9 +287,9 @@ class StreamElements(TTSProvider):
|
|
|
287
287
|
try:
|
|
288
288
|
# Using ThreadPoolExecutor to handle requests concurrently
|
|
289
289
|
with ThreadPoolExecutor() as executor:
|
|
290
|
-
futures = {executor.submit(generate_audio_for_chunk, sentence.strip(), chunk_num): chunk_num
|
|
290
|
+
futures = {executor.submit(generate_audio_for_chunk, sentence.strip(), chunk_num): chunk_num
|
|
291
291
|
for chunk_num, sentence in enumerate(sentences, start=1)}
|
|
292
|
-
|
|
292
|
+
|
|
293
293
|
# Dictionary to store results with order preserved
|
|
294
294
|
audio_chunks = {}
|
|
295
295
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import requests
|
|
2
2
|
import json
|
|
3
|
-
from typing import Union, Any, Dict, Generator
|
|
3
|
+
from typing import Union, Any, Dict, Generator, Optional, List
|
|
4
4
|
from webscout.AIutel import Optimizers, Conversation, AwesomePrompts
|
|
5
5
|
from webscout.AIbase import Provider
|
|
6
6
|
from webscout import exceptions
|
|
@@ -12,33 +12,28 @@ class TextPollinationsAI(Provider):
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
AVAILABLE_MODELS = [
|
|
15
|
-
"openai",
|
|
16
|
-
"openai-large",
|
|
17
|
-
"openai-reasoning",
|
|
18
|
-
"qwen-coder",
|
|
19
|
-
"llama",
|
|
20
|
-
"llamascout",
|
|
21
|
-
"mistral",
|
|
22
|
-
"unity",
|
|
23
|
-
"midijourney",
|
|
24
|
-
"rtist",
|
|
25
|
-
"searchgpt",
|
|
26
|
-
"evil",
|
|
27
|
-
"deepseek-reasoning",
|
|
28
|
-
"deepseek-reasoning-large",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"roblox-rp",
|
|
38
|
-
"deepseek",
|
|
39
|
-
"sur",
|
|
40
|
-
"llama-scaleway",
|
|
41
|
-
"openai-audio",
|
|
15
|
+
"openai", # OpenAI GPT-4.1-nano (Azure) - vision capable
|
|
16
|
+
"openai-large", # OpenAI GPT-4.1 mini (Azure) - vision capable
|
|
17
|
+
"openai-reasoning", # OpenAI o4-mini (Azure) - vision capable, reasoning
|
|
18
|
+
"qwen-coder", # Qwen 2.5 Coder 32B (Scaleway)
|
|
19
|
+
"llama", # Llama 3.3 70B (Cloudflare)
|
|
20
|
+
"llamascout", # Llama 4 Scout 17B (Cloudflare)
|
|
21
|
+
"mistral", # Mistral Small 3 (Scaleway) - vision capable
|
|
22
|
+
"unity", # Unity Mistral Large (Scaleway) - vision capable, uncensored
|
|
23
|
+
"midijourney", # Midijourney (Azure)
|
|
24
|
+
"rtist", # Rtist (Azure)
|
|
25
|
+
"searchgpt", # SearchGPT (Azure) - vision capable
|
|
26
|
+
"evil", # Evil (Scaleway) - vision capable, uncensored
|
|
27
|
+
"deepseek-reasoning", # DeepSeek-R1 Distill Qwen 32B (Cloudflare) - reasoning
|
|
28
|
+
"deepseek-reasoning-large", # DeepSeek R1 - Llama 70B (Scaleway) - reasoning
|
|
29
|
+
"phi", # Phi-4 Instruct (Cloudflare) - vision and audio capable
|
|
30
|
+
"llama-vision", # Llama 3.2 11B Vision (Cloudflare) - vision capable
|
|
31
|
+
"gemini", # gemini-2.5-flash-preview-04-17 (Azure) - vision and audio capable
|
|
32
|
+
"hormoz", # Hormoz 8b (Modal)
|
|
33
|
+
"hypnosis-tracy", # Hypnosis Tracy 7B (Azure) - audio capable
|
|
34
|
+
"deepseek", # DeepSeek-V3 (DeepSeek)
|
|
35
|
+
"sur", # Sur AI Assistant (Mistral) (Scaleway) - vision capable
|
|
36
|
+
"openai-audio", # OpenAI GPT-4o-audio-preview (Azure) - vision and audio capable
|
|
42
37
|
]
|
|
43
38
|
|
|
44
39
|
def __init__(
|
|
@@ -68,14 +63,14 @@ class TextPollinationsAI(Provider):
|
|
|
68
63
|
self.last_response = {}
|
|
69
64
|
self.model = model
|
|
70
65
|
self.system_prompt = system_prompt
|
|
71
|
-
|
|
66
|
+
|
|
72
67
|
self.headers = {
|
|
73
68
|
'Accept': '*/*',
|
|
74
69
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
75
70
|
'User-Agent': Lit().random(),
|
|
76
71
|
'Content-Type': 'application/json',
|
|
77
72
|
}
|
|
78
|
-
|
|
73
|
+
|
|
79
74
|
self.session.headers.update(self.headers)
|
|
80
75
|
self.session.proxies = proxies
|
|
81
76
|
|
|
@@ -104,6 +99,8 @@ class TextPollinationsAI(Provider):
|
|
|
104
99
|
raw: bool = False,
|
|
105
100
|
optimizer: str = None,
|
|
106
101
|
conversationally: bool = False,
|
|
102
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
103
|
+
tool_choice: Optional[Dict[str, Any]] = None,
|
|
107
104
|
) -> Union[Dict[str, Any], Generator[Any, None, None]]:
|
|
108
105
|
"""Chat with AI"""
|
|
109
106
|
conversation_prompt = self.conversation.gen_complete_prompt(prompt)
|
|
@@ -124,6 +121,12 @@ class TextPollinationsAI(Provider):
|
|
|
124
121
|
"stream": stream,
|
|
125
122
|
}
|
|
126
123
|
|
|
124
|
+
# Add function calling parameters if provided
|
|
125
|
+
if tools:
|
|
126
|
+
payload["tools"] = tools
|
|
127
|
+
if tool_choice:
|
|
128
|
+
payload["tool_choice"] = tool_choice
|
|
129
|
+
|
|
127
130
|
def for_stream():
|
|
128
131
|
response = self.session.post(
|
|
129
132
|
self.api_endpoint,
|
|
@@ -149,12 +152,15 @@ class TextPollinationsAI(Provider):
|
|
|
149
152
|
json_data = json.loads(line[6:])
|
|
150
153
|
if 'choices' in json_data and len(json_data['choices']) > 0:
|
|
151
154
|
choice = json_data['choices'][0]
|
|
152
|
-
if 'delta' in choice
|
|
153
|
-
content
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
if 'delta' in choice:
|
|
156
|
+
if 'content' in choice['delta']:
|
|
157
|
+
content = choice['delta']['content']
|
|
158
|
+
full_response += content
|
|
159
|
+
yield content if raw else dict(text=content)
|
|
160
|
+
elif 'tool_calls' in choice['delta']:
|
|
161
|
+
# Handle tool calls in streaming response
|
|
162
|
+
tool_calls = choice['delta']['tool_calls']
|
|
163
|
+
yield tool_calls if raw else dict(tool_calls=tool_calls)
|
|
158
164
|
except json.JSONDecodeError:
|
|
159
165
|
continue
|
|
160
166
|
|
|
@@ -176,11 +182,14 @@ class TextPollinationsAI(Provider):
|
|
|
176
182
|
stream: bool = False,
|
|
177
183
|
optimizer: str = None,
|
|
178
184
|
conversationally: bool = False,
|
|
185
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
186
|
+
tool_choice: Optional[Dict[str, Any]] = None,
|
|
179
187
|
) -> Union[str, Generator[str, None, None]]:
|
|
180
188
|
"""Generate response as a string"""
|
|
181
189
|
def for_stream():
|
|
182
190
|
for response in self.ask(
|
|
183
|
-
prompt, True, optimizer=optimizer, conversationally=conversationally
|
|
191
|
+
prompt, True, optimizer=optimizer, conversationally=conversationally,
|
|
192
|
+
tools=tools, tool_choice=tool_choice
|
|
184
193
|
):
|
|
185
194
|
yield self.get_message(response)
|
|
186
195
|
|
|
@@ -191,6 +200,8 @@ class TextPollinationsAI(Provider):
|
|
|
191
200
|
False,
|
|
192
201
|
optimizer=optimizer,
|
|
193
202
|
conversationally=conversationally,
|
|
203
|
+
tools=tools,
|
|
204
|
+
tool_choice=tool_choice,
|
|
194
205
|
)
|
|
195
206
|
)
|
|
196
207
|
|
|
@@ -199,17 +210,21 @@ class TextPollinationsAI(Provider):
|
|
|
199
210
|
def get_message(self, response: dict) -> str:
|
|
200
211
|
"""Retrieves message only from response"""
|
|
201
212
|
assert isinstance(response, dict), "Response should be of dict data-type only"
|
|
202
|
-
|
|
213
|
+
if "text" in response:
|
|
214
|
+
return response["text"]
|
|
215
|
+
elif "tool_calls" in response:
|
|
216
|
+
# For tool calls, return a string representation
|
|
217
|
+
return json.dumps(response["tool_calls"])
|
|
203
218
|
|
|
204
219
|
if __name__ == "__main__":
|
|
205
220
|
print("-" * 80)
|
|
206
221
|
print(f"{'Model':<50} {'Status':<10} {'Response'}")
|
|
207
222
|
print("-" * 80)
|
|
208
|
-
|
|
223
|
+
|
|
209
224
|
# Test all available models
|
|
210
225
|
working = 0
|
|
211
226
|
total = len(TextPollinationsAI.AVAILABLE_MODELS)
|
|
212
|
-
|
|
227
|
+
|
|
213
228
|
for model in TextPollinationsAI.AVAILABLE_MODELS:
|
|
214
229
|
try:
|
|
215
230
|
test_ai = TextPollinationsAI(model=model, timeout=60)
|
|
@@ -218,7 +233,7 @@ if __name__ == "__main__":
|
|
|
218
233
|
for chunk in response:
|
|
219
234
|
response_text += chunk
|
|
220
235
|
print(f"\r{model:<50} {'Testing...':<10}", end="", flush=True)
|
|
221
|
-
|
|
236
|
+
|
|
222
237
|
if response_text and len(response_text.strip()) > 0:
|
|
223
238
|
status = "✓"
|
|
224
239
|
# Truncate response if too long
|