webscout 8.3__py3-none-any.whl → 8.3.2__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.

Files changed (120) hide show
  1. webscout/AIauto.py +4 -4
  2. webscout/AIbase.py +61 -1
  3. webscout/AIutel.py +46 -53
  4. webscout/Bing_search.py +418 -0
  5. webscout/Extra/YTToolkit/ytapi/patterns.py +45 -45
  6. webscout/Extra/YTToolkit/ytapi/stream.py +1 -1
  7. webscout/Extra/YTToolkit/ytapi/video.py +10 -10
  8. webscout/Extra/autocoder/autocoder_utiles.py +1 -1
  9. webscout/Extra/gguf.py +706 -177
  10. webscout/Litlogger/formats.py +9 -0
  11. webscout/Litlogger/handlers.py +18 -0
  12. webscout/Litlogger/logger.py +43 -1
  13. webscout/Provider/AISEARCH/genspark_search.py +7 -7
  14. webscout/Provider/AISEARCH/scira_search.py +3 -2
  15. webscout/Provider/GeminiProxy.py +140 -0
  16. webscout/Provider/LambdaChat.py +7 -1
  17. webscout/Provider/MCPCore.py +78 -75
  18. webscout/Provider/OPENAI/BLACKBOXAI.py +1046 -1017
  19. webscout/Provider/OPENAI/GeminiProxy.py +328 -0
  20. webscout/Provider/OPENAI/Qwen3.py +303 -303
  21. webscout/Provider/OPENAI/README.md +5 -0
  22. webscout/Provider/OPENAI/README_AUTOPROXY.md +238 -0
  23. webscout/Provider/OPENAI/TogetherAI.py +355 -0
  24. webscout/Provider/OPENAI/__init__.py +16 -1
  25. webscout/Provider/OPENAI/autoproxy.py +332 -0
  26. webscout/Provider/OPENAI/base.py +101 -14
  27. webscout/Provider/OPENAI/chatgpt.py +15 -2
  28. webscout/Provider/OPENAI/chatgptclone.py +14 -3
  29. webscout/Provider/OPENAI/deepinfra.py +339 -328
  30. webscout/Provider/OPENAI/e2b.py +295 -74
  31. webscout/Provider/OPENAI/mcpcore.py +109 -70
  32. webscout/Provider/OPENAI/opkfc.py +18 -6
  33. webscout/Provider/OPENAI/scirachat.py +59 -50
  34. webscout/Provider/OPENAI/toolbaz.py +2 -10
  35. webscout/Provider/OPENAI/writecream.py +166 -166
  36. webscout/Provider/OPENAI/x0gpt.py +367 -367
  37. webscout/Provider/OPENAI/xenai.py +514 -0
  38. webscout/Provider/OPENAI/yep.py +389 -383
  39. webscout/Provider/STT/__init__.py +3 -0
  40. webscout/Provider/STT/base.py +281 -0
  41. webscout/Provider/STT/elevenlabs.py +265 -0
  42. webscout/Provider/TTI/__init__.py +4 -1
  43. webscout/Provider/TTI/aiarta.py +399 -365
  44. webscout/Provider/TTI/base.py +74 -2
  45. webscout/Provider/TTI/bing.py +231 -0
  46. webscout/Provider/TTI/fastflux.py +63 -30
  47. webscout/Provider/TTI/gpt1image.py +149 -0
  48. webscout/Provider/TTI/imagen.py +196 -0
  49. webscout/Provider/TTI/magicstudio.py +60 -29
  50. webscout/Provider/TTI/piclumen.py +43 -32
  51. webscout/Provider/TTI/pixelmuse.py +232 -225
  52. webscout/Provider/TTI/pollinations.py +43 -32
  53. webscout/Provider/TTI/together.py +287 -0
  54. webscout/Provider/TTI/utils.py +2 -1
  55. webscout/Provider/TTS/README.md +1 -0
  56. webscout/Provider/TTS/__init__.py +2 -1
  57. webscout/Provider/TTS/freetts.py +140 -0
  58. webscout/Provider/TTS/speechma.py +45 -39
  59. webscout/Provider/TogetherAI.py +366 -0
  60. webscout/Provider/UNFINISHED/ChutesAI.py +314 -0
  61. webscout/Provider/UNFINISHED/fetch_together_models.py +95 -0
  62. webscout/Provider/XenAI.py +324 -0
  63. webscout/Provider/__init__.py +8 -0
  64. webscout/Provider/deepseek_assistant.py +378 -0
  65. webscout/Provider/scira_chat.py +3 -2
  66. webscout/Provider/toolbaz.py +0 -1
  67. webscout/auth/__init__.py +44 -0
  68. webscout/auth/api_key_manager.py +189 -0
  69. webscout/auth/auth_system.py +100 -0
  70. webscout/auth/config.py +76 -0
  71. webscout/auth/database.py +400 -0
  72. webscout/auth/exceptions.py +67 -0
  73. webscout/auth/middleware.py +248 -0
  74. webscout/auth/models.py +130 -0
  75. webscout/auth/providers.py +257 -0
  76. webscout/auth/rate_limiter.py +254 -0
  77. webscout/auth/request_models.py +127 -0
  78. webscout/auth/request_processing.py +226 -0
  79. webscout/auth/routes.py +526 -0
  80. webscout/auth/schemas.py +103 -0
  81. webscout/auth/server.py +312 -0
  82. webscout/auth/static/favicon.svg +11 -0
  83. webscout/auth/swagger_ui.py +203 -0
  84. webscout/auth/templates/components/authentication.html +237 -0
  85. webscout/auth/templates/components/base.html +103 -0
  86. webscout/auth/templates/components/endpoints.html +750 -0
  87. webscout/auth/templates/components/examples.html +491 -0
  88. webscout/auth/templates/components/footer.html +75 -0
  89. webscout/auth/templates/components/header.html +27 -0
  90. webscout/auth/templates/components/models.html +286 -0
  91. webscout/auth/templates/components/navigation.html +70 -0
  92. webscout/auth/templates/static/api.js +455 -0
  93. webscout/auth/templates/static/icons.js +168 -0
  94. webscout/auth/templates/static/main.js +784 -0
  95. webscout/auth/templates/static/particles.js +201 -0
  96. webscout/auth/templates/static/styles.css +3353 -0
  97. webscout/auth/templates/static/ui.js +374 -0
  98. webscout/auth/templates/swagger_ui.html +170 -0
  99. webscout/client.py +49 -3
  100. webscout/litagent/Readme.md +12 -3
  101. webscout/litagent/agent.py +99 -62
  102. webscout/scout/core/scout.py +104 -26
  103. webscout/scout/element.py +139 -18
  104. webscout/swiftcli/core/cli.py +14 -3
  105. webscout/swiftcli/decorators/output.py +59 -9
  106. webscout/update_checker.py +31 -49
  107. webscout/version.py +1 -1
  108. webscout/webscout_search.py +4 -12
  109. webscout/webscout_search_async.py +3 -10
  110. webscout/yep_search.py +2 -11
  111. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/METADATA +41 -11
  112. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/RECORD +116 -68
  113. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/entry_points.txt +1 -1
  114. webscout/Provider/HF_space/__init__.py +0 -0
  115. webscout/Provider/HF_space/qwen_qwen2.py +0 -206
  116. webscout/Provider/OPENAI/api.py +0 -1035
  117. webscout/Provider/TTI/artbit.py +0 -0
  118. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/WHEEL +0 -0
  119. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/licenses/LICENSE.md +0 -0
  120. {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/top_level.txt +0 -0
@@ -2,7 +2,10 @@ import requests
2
2
  from typing import Optional, List, Dict, Any, Union
3
3
  from pathlib import Path
4
4
  from requests.exceptions import RequestException
5
- from webscout.Provider.TTI.utils import ImageData, ImageResponse
5
+ from webscout.Provider.TTI.utils import (
6
+ ImageData,
7
+ ImageResponse
8
+ )
6
9
  from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
7
10
  from io import BytesIO
8
11
  import os
@@ -17,6 +20,7 @@ try:
17
20
  except ImportError:
18
21
  Image = None
19
22
 
23
+
20
24
  class Images(BaseImages):
21
25
  def __init__(self, client):
22
26
  self._client = client
@@ -35,7 +39,7 @@ class Images(BaseImages):
35
39
  timeout: int = 60,
36
40
  image_format: str = "png",
37
41
  seed: Optional[int] = None,
38
- **kwargs
42
+ **kwargs,
39
43
  ) -> ImageResponse:
40
44
  """
41
45
  image_format: "png" or "jpeg"
@@ -52,32 +56,35 @@ class Images(BaseImages):
52
56
  for attempt in range(max_retries):
53
57
  tmp_path = None
54
58
  try:
55
- with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
59
+ with tempfile.NamedTemporaryFile(
60
+ suffix=f".{ext}", delete=False
61
+ ) as tmp:
56
62
  tmp.write(img_bytes)
57
63
  tmp.flush()
58
64
  tmp_path = tmp.name
59
- with open(tmp_path, 'rb') as f:
60
- files = {
61
- 'fileToUpload': (f'image.{ext}', f, f'image/{ext}')
62
- }
63
- data = {
64
- 'reqtype': 'fileupload',
65
- 'json': 'true'
66
- }
67
- headers = {'User-Agent': LitAgent().random()}
65
+ with open(tmp_path, "rb") as f:
66
+ files = {"fileToUpload": (f"image.{ext}", f, f"image/{ext}")}
67
+ data = {"reqtype": "fileupload", "json": "true"}
68
+ headers = {"User-Agent": LitAgent().random()}
68
69
  if attempt > 0:
69
- headers['Connection'] = 'close'
70
- resp = requests.post("https://catbox.moe/user/api.php", files=files, data=data, headers=headers, timeout=timeout)
70
+ headers["Connection"] = "close"
71
+ resp = requests.post(
72
+ "https://catbox.moe/user/api.php",
73
+ files=files,
74
+ data=data,
75
+ headers=headers,
76
+ timeout=timeout,
77
+ )
71
78
  if resp.status_code == 200 and resp.text.strip():
72
79
  text = resp.text.strip()
73
- if text.startswith('http'):
80
+ if text.startswith("http"):
74
81
  return text
75
82
  try:
76
83
  result = resp.json()
77
84
  if "url" in result:
78
85
  return result["url"]
79
86
  except json.JSONDecodeError:
80
- if 'http' in text:
87
+ if "http" in text:
81
88
  return text
82
89
  except Exception:
83
90
  if attempt < max_retries - 1:
@@ -100,12 +107,12 @@ class Images(BaseImages):
100
107
  try:
101
108
  if not os.path.isfile(tmp_path):
102
109
  return None
103
- with open(tmp_path, 'rb') as img_file:
104
- files = {'file': img_file}
105
- response = requests.post('https://0x0.st', files=files)
110
+ with open(tmp_path, "rb") as img_file:
111
+ files = {"file": img_file}
112
+ response = requests.post("https://0x0.st", files=files)
106
113
  response.raise_for_status()
107
114
  image_url = response.text.strip()
108
- if not image_url.startswith('http'):
115
+ if not image_url.startswith("http"):
109
116
  return None
110
117
  return image_url
111
118
  except Exception:
@@ -132,7 +139,10 @@ class Images(BaseImages):
132
139
  query = "&".join(f"{k}={v}" for k, v in params.items())
133
140
  url = f"{base_url}?{query}"
134
141
  try:
135
- resp = self._client.session.get(url, timeout=timeout)
142
+ resp = self._client.session.get(
143
+ url,
144
+ timeout=timeout,
145
+ )
136
146
  resp.raise_for_status()
137
147
  img_bytes = resp.content
138
148
  except RequestException as e:
@@ -158,7 +168,9 @@ class Images(BaseImages):
158
168
  if uploaded_url:
159
169
  urls.append(uploaded_url)
160
170
  else:
161
- raise RuntimeError("Failed to upload image to catbox.moe using all available methods")
171
+ raise RuntimeError(
172
+ "Failed to upload image to catbox.moe using all available methods"
173
+ )
162
174
 
163
175
  result_data = []
164
176
  if response_format == "url":
@@ -166,6 +178,7 @@ class Images(BaseImages):
166
178
  result_data.append(ImageData(url=url))
167
179
  elif response_format == "b64_json":
168
180
  import base64
181
+
169
182
  for img in images:
170
183
  b64 = base64.b64encode(img).decode("utf-8")
171
184
  result_data.append(ImageData(b64_json=b64))
@@ -173,17 +186,12 @@ class Images(BaseImages):
173
186
  raise ValueError("response_format must be 'url' or 'b64_json'")
174
187
 
175
188
  from time import time as _time
176
- return ImageResponse(
177
- created=int(_time()),
178
- data=result_data
179
- )
189
+
190
+ return ImageResponse(created=int(_time()), data=result_data)
191
+
180
192
 
181
193
  class PollinationsAI(TTICompatibleProvider):
182
- AVAILABLE_MODELS = [
183
- "flux",
184
- "turbo",
185
- "gptimage"
186
- ]
194
+ AVAILABLE_MODELS = ["flux", "turbo", "gptimage"]
187
195
 
188
196
  def __init__(self):
189
197
  self.api_endpoint = "https://image.pollinations.ai/prompt"
@@ -205,10 +213,13 @@ class PollinationsAI(TTICompatibleProvider):
205
213
  class _ModelList:
206
214
  def list(inner_self):
207
215
  return type(self).AVAILABLE_MODELS
216
+
208
217
  return _ModelList()
209
218
 
219
+
210
220
  if __name__ == "__main__":
211
221
  from rich import print
222
+
212
223
  client = PollinationsAI()
213
224
  response = client.images.create(
214
225
  model="flux",
@@ -216,6 +227,6 @@ if __name__ == "__main__":
216
227
  response_format="url",
217
228
  n=4,
218
229
  timeout=30,
219
- seed=None # You can set a specific seed for reproducibility
230
+ seed=None, # You can set a specific seed for reproducibility
220
231
  )
221
232
  print(response)
@@ -0,0 +1,287 @@
1
+ import requests
2
+ import random
3
+ import string
4
+ import json
5
+ import time
6
+ from typing import Optional, List, Dict, Any
7
+ from webscout.Provider.TTI.utils import (
8
+ ImageData,
9
+ ImageResponse
10
+ )
11
+ from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
12
+ from io import BytesIO
13
+ import os
14
+ import tempfile
15
+ from webscout.litagent import LitAgent
16
+ from requests.adapters import HTTPAdapter
17
+ from urllib3.util.retry import Retry
18
+
19
+
20
+ class Images(BaseImages):
21
+ def __init__(self, client):
22
+ self._client = client
23
+ self.base_url = "https://api.together.xyz/v1"
24
+ # Create a session - it will automatically get proxies from the global monkey patch!
25
+ self.session = requests.Session()
26
+ self._setup_session_with_retries()
27
+
28
+ def _setup_session_with_retries(self):
29
+ """Setup session with retry strategy and timeout configurations"""
30
+ # Configure retry strategy
31
+ retry_strategy = Retry(
32
+ total=3,
33
+ status_forcelist=[429, 500, 502, 503, 504],
34
+ backoff_factor=1,
35
+ allowed_methods=["HEAD", "GET", "OPTIONS", "POST"],
36
+ )
37
+
38
+ adapter = HTTPAdapter(max_retries=retry_strategy)
39
+ self.session.mount("http://", adapter)
40
+ self.session.mount("https://", adapter)
41
+
42
+ def get_api_key(self) -> str:
43
+ """Get API key from activation endpoint or cache"""
44
+ if hasattr(self._client, '_api_key_cache') and self._client._api_key_cache:
45
+ return self._client._api_key_cache
46
+
47
+ try:
48
+ activation_endpoint = "https://www.codegeneration.ai/activate-v2"
49
+ response = requests.get(
50
+ activation_endpoint,
51
+ headers={"Accept": "application/json"},
52
+ timeout=30
53
+ )
54
+ response.raise_for_status()
55
+ activation_data = response.json()
56
+ api_key = activation_data["openAIParams"]["apiKey"]
57
+ self._client._api_key_cache = api_key
58
+ return api_key
59
+ except Exception as e:
60
+ raise Exception(f"Failed to get activation key: {e}")
61
+
62
+ def build_headers(self, extra: Optional[Dict[str, str]] = None) -> Dict[str, str]:
63
+ """Build headers with API authorization"""
64
+ api_key = self.get_api_key()
65
+
66
+ agent = LitAgent()
67
+ fp = agent.generate_fingerprint("chrome")
68
+ headers = {
69
+ "Authorization": f"Bearer {api_key}",
70
+ "Content-Type": "application/json",
71
+ "accept": "application/json",
72
+ "accept-language": fp["accept_language"],
73
+ "user-agent": fp["user_agent"],
74
+ "sec-ch-ua": fp["sec_ch_ua"],
75
+ "sec-ch-ua-mobile": "?0",
76
+ "sec-ch-ua-platform": '"Windows"',
77
+ "sec-fetch-dest": "empty",
78
+ "sec-fetch-mode": "cors",
79
+ "sec-fetch-site": "cross-site",
80
+ }
81
+ if extra:
82
+ headers.update(extra)
83
+ return headers
84
+
85
+ def create(
86
+ self,
87
+ model: str = None,
88
+ prompt: str = None,
89
+ n: int = 1,
90
+ size: str = "1024x1024",
91
+ response_format: str = "url",
92
+ user: Optional[str] = None,
93
+ style: str = None,
94
+ aspect_ratio: str = None,
95
+ timeout: int = 120,
96
+ image_format: str = "png",
97
+ enhance: bool = True,
98
+ steps: int = 20,
99
+ seed: Optional[int] = None,
100
+ **kwargs,
101
+ ) -> ImageResponse:
102
+ """
103
+ Create images using Together.xyz image models
104
+
105
+ Args:
106
+ model: Image model to use (defaults to first available)
107
+ prompt: Text description of the image to generate
108
+ n: Number of images to generate (1-4)
109
+ size: Image size in format "WIDTHxHEIGHT"
110
+ response_format: "url" or "b64_json"
111
+ timeout: Request timeout in seconds
112
+ steps: Number of inference steps (1-50)
113
+ seed: Random seed for reproducible results
114
+ **kwargs: Additional model-specific parameters
115
+ """
116
+ if not prompt:
117
+ raise ValueError(
118
+ "Describe the image you want to create (use the 'prompt' property)."
119
+ )
120
+
121
+ # Use provided model or default to first available
122
+ if not model:
123
+ model = self._client.AVAILABLE_MODELS[0]
124
+ elif model not in self._client.AVAILABLE_MODELS:
125
+ raise ValueError(f"Model '{model}' not available. Choose from: {self._client.AVAILABLE_MODELS}")
126
+
127
+ # Parse size
128
+ if 'x' in size:
129
+ width, height = map(int, size.split('x'))
130
+ else:
131
+ width = height = int(size)
132
+
133
+ # Build request body
134
+ body = {
135
+ "model": model,
136
+ "prompt": prompt,
137
+ "width": width,
138
+ "height": height,
139
+ # Clamp steps to 1-4 as required by Together.xyz API
140
+ "steps": min(max(steps, 1), 4),
141
+ "n": min(max(n, 1), 4), # Clamp between 1-4
142
+ }
143
+
144
+ # Add optional parameters
145
+ if seed is not None:
146
+ body["seed"] = seed
147
+
148
+ # Add any additional kwargs
149
+ body.update(kwargs)
150
+
151
+ try:
152
+ resp = self.session.request(
153
+ "post",
154
+ f"{self.base_url}/images/generations",
155
+ json=body,
156
+ headers=self.build_headers(),
157
+ timeout=timeout,
158
+ )
159
+
160
+ data = resp.json()
161
+
162
+ # Check for errors
163
+ if "error" in data:
164
+ error_msg = data["error"].get("message", str(data["error"]))
165
+ raise RuntimeError(f"Together.xyz API error: {error_msg}")
166
+
167
+ if not data.get("data") or len(data["data"]) == 0:
168
+ raise RuntimeError("Failed to process image. No data found.")
169
+
170
+ result = data["data"]
171
+ result_data = []
172
+
173
+ for i, item in enumerate(result):
174
+ if response_format == "url":
175
+ if "url" in item:
176
+ result_data.append(ImageData(url=item["url"]))
177
+ else: # b64_json
178
+ if "b64_json" in item:
179
+ result_data.append(ImageData(b64_json=item["b64_json"]))
180
+
181
+ if not result_data:
182
+ raise RuntimeError("No valid image data found in response")
183
+
184
+ return ImageResponse(data=result_data)
185
+
186
+ except requests.exceptions.Timeout:
187
+ raise RuntimeError(f"Request timed out after {timeout} seconds. Try reducing image size or steps.")
188
+ except requests.exceptions.RequestException as e:
189
+ # Print the response content for debugging if available
190
+ if hasattr(e, 'response') and e.response is not None:
191
+ try:
192
+ print("[Together.xyz API error details]", e.response.text)
193
+ except Exception:
194
+ pass
195
+ raise RuntimeError(f"Network error: {str(e)}")
196
+ except json.JSONDecodeError:
197
+ raise RuntimeError("Invalid JSON response from Together.xyz API")
198
+ except Exception as e:
199
+ raise RuntimeError(f"An error occurred: {str(e)}")
200
+
201
+
202
+ class TogetherImage(TTICompatibleProvider):
203
+ """
204
+ Together.xyz Text-to-Image provider
205
+ Updated: 2025-06-02 10:42:41 UTC by OEvortex
206
+ Supports FLUX and other image generation models
207
+ """
208
+
209
+ # Image models from Together.xyz API (filtered for image type only)
210
+ AVAILABLE_MODELS = [
211
+ "black-forest-labs/FLUX.1-schnell-Free",
212
+ "black-forest-labs/FLUX.1.1-pro",
213
+ "black-forest-labs/FLUX.1-pro",
214
+ "black-forest-labs/FLUX.1-redux",
215
+ "black-forest-labs/FLUX.1-depth",
216
+ "black-forest-labs/FLUX.1-canny",
217
+ "black-forest-labs/FLUX.1-kontext-max",
218
+ "black-forest-labs/FLUX.1-dev-lora",
219
+ "black-forest-labs/FLUX.1-schnell",
220
+ "black-forest-labs/FLUX.1-dev",
221
+ "black-forest-labs/FLUX.1-kontext-pro",
222
+ ]
223
+
224
+ def __init__(self):
225
+ self.images = Images(self)
226
+ self._api_key_cache = None
227
+
228
+ @property
229
+ def models(self):
230
+ class _ModelList:
231
+ def list(inner_self):
232
+ return TogetherImage.AVAILABLE_MODELS
233
+
234
+ return _ModelList()
235
+
236
+ def convert_model_name(self, model: str) -> str:
237
+ """Convert model alias to full model name"""
238
+ if model in self.AVAILABLE_MODELS:
239
+ return model
240
+
241
+ # Default to first available model
242
+ return self.AVAILABLE_MODELS[0]
243
+
244
+ # def fetch_available_models(self) -> List[str]:
245
+ # """Fetch current image models from Together.xyz API"""
246
+ # try:
247
+ # api_key = self.images.get_api_key()
248
+ # headers = {
249
+ # "Authorization": f"Bearer {api_key}",
250
+ # "Accept": "application/json"
251
+ # }
252
+
253
+ # response = requests.get(
254
+ # "https://api.together.xyz/v1/models",
255
+ # headers=headers,
256
+ # timeout=30
257
+ # )
258
+ # response.raise_for_status()
259
+ # models_data = response.json()
260
+
261
+ # # Filter image models
262
+ # image_models = []
263
+ # for model in models_data:
264
+ # if isinstance(model, dict) and model.get("type", "").lower() == "image":
265
+ # image_models.append(model["id"])
266
+
267
+ # return sorted(image_models)
268
+
269
+ # except Exception as e:
270
+ # return self.AVAILABLE_MODELS
271
+
272
+
273
+ if __name__ == "__main__":
274
+ from rich import print
275
+ client = TogetherImage()
276
+
277
+ # Test with a sample prompt
278
+ response = client.images.create(
279
+ model="black-forest-labs/FLUX.1-schnell-Free", # Free FLUX model
280
+ prompt="A majestic dragon flying over a mystical forest, fantasy art, highly detailed",
281
+ size="1024x1024",
282
+ n=1,
283
+ steps=25,
284
+ response_format="url",
285
+ timeout=120,
286
+ )
287
+ print(response)
@@ -1,11 +1,12 @@
1
+ import time
1
2
  from typing import List, Optional
2
3
  from pydantic import BaseModel, Field
3
- import time
4
4
 
5
5
  class ImageData(BaseModel):
6
6
  url: Optional[str] = None
7
7
  b64_json: Optional[str] = None
8
8
 
9
+
9
10
  class ImageResponse(BaseModel):
10
11
  created: int = Field(default_factory=lambda: int(time.time()))
11
12
  data: List[ImageData]
@@ -25,6 +25,7 @@ Webscout's TTS Providers offer a versatile and powerful text-to-speech conversio
25
25
  6. **StreamElementsTTS**
26
26
  7. **SpeechMaTTS**
27
27
  8. **SthirTTS**
28
+ 9. **FreeTTS**
28
29
  ## 🚀 Installation
29
30
 
30
31
  ```bash
@@ -7,4 +7,5 @@ from .murfai import *
7
7
  from .gesserit import *
8
8
  from .speechma import *
9
9
  from .sthir import *
10
- from .openai_fm import *
10
+ from .openai_fm import *
11
+ from .freetts import *
@@ -0,0 +1,140 @@
1
+ import os
2
+ import requests
3
+ from datetime import datetime
4
+ from datetime import datetime
5
+ from webscout.Provider.TTS import BaseTTSProvider
6
+ from webscout.litagent import LitAgent
7
+
8
+
9
+ class FreeTTS(BaseTTSProvider):
10
+ """
11
+ Text-to-speech provider using the FreeTTS API.
12
+ """
13
+
14
+ headers = {
15
+ "accept": "*/*",
16
+ "accept-language": "ru-RU,ru;q=0.8",
17
+ "cache-control": "no-cache",
18
+ "pragma": "no-cache",
19
+ "sec-ch-ua": '"Brave";v="137", "Chromium";v="137", "Not/A)Brand";v="24"',
20
+ "sec-ch-ua-mobile": "?0",
21
+ "sec-ch-ua-platform": '"Windows"',
22
+ "sec-fetch-dest": "empty",
23
+ "sec-fetch-mode": "cors",
24
+ "sec-fetch-site": "same-origin",
25
+ "sec-gpc": "1",
26
+ "User-Agent": LitAgent().random()
27
+ }
28
+
29
+ def __init__(self, lang="ru-RU", timeout: int = 30, proxies: dict = None):
30
+ """Initializes the FreeTTS TTS client."""
31
+ super().__init__()
32
+ self.lang = lang
33
+ self.url = "https://freetts.ru/api/v1/tts"
34
+ self.select_url = "https://freetts.ru/api/v1/select"
35
+ self.audio_base_url = "https://freetts.ru"
36
+ self.session = requests.Session()
37
+ self.session.headers.update(self.headers)
38
+ if proxies:
39
+ self.session.proxies.update(proxies)
40
+ self.timeout = timeout
41
+ self.voices = {}
42
+ self.load_voices()
43
+
44
+ def load_voices(self):
45
+ """Загружает данные о голосах и приводит их к нужному виду"""
46
+ try:
47
+ response = self.session.get(self.select_url, timeout=self.timeout)
48
+ if response.status_code == 200:
49
+ data = response.json()
50
+ voices_data = data["data"]["voice"]
51
+
52
+ if isinstance(voices_data, list):
53
+ for voice_info in voices_data:
54
+ if isinstance(voice_info, dict):
55
+ voice_id = voice_info.get("code")
56
+ voice_name = voice_info.get("name", voice_id)
57
+ if voice_id and voice_id.startswith(self.lang):
58
+ self.voices[voice_id] = voice_name
59
+ else:
60
+ print("Error")
61
+ print("Done")
62
+ else:
63
+ print(f"Error HTTP: {response.status_code}")
64
+ except Exception as e:
65
+ print(f"Error downloading voice: {e}")
66
+
67
+ def get_available_voices(self):
68
+ """Возвращает все доступные голоса в формате строки"""
69
+ if not self.voices:
70
+ return "Error"
71
+ voices_list = [f"{voice_id}: {name}" for voice_id, name in self.voices.items()]
72
+ return "\n".join(voices_list)
73
+
74
+ def tts(self, text: str, voiceid: str = None) -> str:
75
+ """
76
+ Converts text to speech using the FreeTTS API and saves it to a file.
77
+ Args:
78
+ text (str): The text to convert to speech
79
+ voiceid (str): Voice ID to use for TTS (default: first available)
80
+ Returns:
81
+ str: Path to the generated audio file (MP3)
82
+ Raises:
83
+ AssertionError: If no voices are available
84
+ requests.RequestException: If there's an error communicating with the API
85
+ RuntimeError: If there's an error processing the audio
86
+ """
87
+ try:
88
+ if not self.voices:
89
+ raise RuntimeError(f"No voices available for language '{self.lang}'")
90
+
91
+ available_voices = self.get_available_voices()
92
+ if not available_voices:
93
+ print(f"There are no available voices for the language '{self.lang}'")
94
+ return ""
95
+
96
+ if voiceid is None:
97
+ voiceid = next(iter(available_voices.keys()))
98
+
99
+ payload = {
100
+ "text": text,
101
+ "voiceid": voiceid
102
+ }
103
+
104
+ response = requests.post(self.url, json=payload, headers=self.headers)
105
+
106
+ if response.status_code == 200:
107
+ data = response.json()
108
+ mp3_path = data.get("data", {}).get("src", "")
109
+
110
+ if not mp3_path:
111
+ print("The path to the audio file in the response was not found.")
112
+ return ""
113
+
114
+ mp3_url = self.audio_base_url + mp3_path
115
+
116
+ mp3_filename = datetime.now().strftime("%Y_%m_%d_%H_%M_%S") + ".mp3"
117
+ full_path = os.path.abspath(mp3_filename)
118
+
119
+ with requests.get(mp3_url, stream=True) as r:
120
+ r.raise_for_status()
121
+ with open(mp3_filename, "wb") as f:
122
+ for chunk in r.iter_content(chunk_size=1024):
123
+ f.write(chunk)
124
+
125
+ print(f"File '{mp3_filename}'saved successfully!")
126
+ return full_path
127
+
128
+ except Exception as e:
129
+ print(e)
130
+
131
+ if __name__ == "__main__":
132
+ tts = FreeTTS(lang="ru")
133
+ available_voices = tts.get_available_voices()
134
+ print("Available voices:", available_voices)
135
+
136
+ text_to_speak = input("\nEnter text: ")
137
+ voice_id = "ru-RU001"
138
+ print("[debug] Generating audio...")
139
+ audio_file = tts.tts(text=text_to_speak, voiceid=voice_id)
140
+ print(f"Audio saved to: {audio_file}")