webscout 8.2.8__py3-none-any.whl → 8.2.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- webscout/AIauto.py +32 -14
- 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 +153 -35
- 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 +171 -81
- webscout/Provider/OPENAI/BLACKBOXAI.py +766 -735
- webscout/Provider/OPENAI/Cloudflare.py +7 -7
- webscout/Provider/OPENAI/FreeGemini.py +6 -5
- webscout/Provider/OPENAI/NEMOTRON.py +8 -20
- webscout/Provider/OPENAI/Qwen3.py +283 -0
- webscout/Provider/OPENAI/README.md +952 -1253
- webscout/Provider/OPENAI/TwoAI.py +357 -0
- webscout/Provider/OPENAI/__init__.py +5 -1
- webscout/Provider/OPENAI/ai4chat.py +40 -40
- webscout/Provider/OPENAI/api.py +808 -649
- webscout/Provider/OPENAI/c4ai.py +3 -3
- webscout/Provider/OPENAI/chatgpt.py +555 -555
- webscout/Provider/OPENAI/chatgptclone.py +493 -487
- webscout/Provider/OPENAI/chatsandbox.py +4 -3
- webscout/Provider/OPENAI/copilot.py +242 -0
- webscout/Provider/OPENAI/deepinfra.py +5 -2
- webscout/Provider/OPENAI/e2b.py +63 -5
- webscout/Provider/OPENAI/exaai.py +416 -410
- webscout/Provider/OPENAI/exachat.py +444 -443
- webscout/Provider/OPENAI/freeaichat.py +2 -2
- webscout/Provider/OPENAI/glider.py +5 -2
- webscout/Provider/OPENAI/groq.py +5 -2
- webscout/Provider/OPENAI/heckai.py +308 -307
- webscout/Provider/OPENAI/mcpcore.py +8 -2
- webscout/Provider/OPENAI/multichat.py +4 -4
- webscout/Provider/OPENAI/netwrck.py +6 -5
- webscout/Provider/OPENAI/oivscode.py +287 -0
- webscout/Provider/OPENAI/opkfc.py +496 -496
- webscout/Provider/OPENAI/pydantic_imports.py +172 -0
- webscout/Provider/OPENAI/scirachat.py +15 -9
- webscout/Provider/OPENAI/sonus.py +304 -303
- webscout/Provider/OPENAI/standardinput.py +433 -433
- webscout/Provider/OPENAI/textpollinations.py +4 -4
- webscout/Provider/OPENAI/toolbaz.py +413 -413
- webscout/Provider/OPENAI/typefully.py +3 -3
- webscout/Provider/OPENAI/typegpt.py +11 -5
- webscout/Provider/OPENAI/uncovrAI.py +463 -462
- webscout/Provider/OPENAI/utils.py +90 -79
- webscout/Provider/OPENAI/venice.py +431 -425
- webscout/Provider/OPENAI/wisecat.py +387 -381
- webscout/Provider/OPENAI/writecream.py +3 -3
- webscout/Provider/OPENAI/x0gpt.py +365 -378
- webscout/Provider/OPENAI/yep.py +39 -13
- 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 -0
- webscout/Provider/ai4chat.py +33 -8
- webscout/Provider/koala.py +169 -169
- webscout/Provider/oivscode.py +309 -0
- webscout/Provider/samurai.py +3 -2
- 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.2.9.dist-info}/METADATA +159 -35
- {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/RECORD +116 -161
- {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/WHEEL +1 -1
- {webscout-8.2.8.dist-info → webscout-8.2.9.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/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 → webscout-8.2.9.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.2.8.dist-info → webscout-8.2.9.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import List, Dict, Optional, Any, Union, Generator
|
|
3
|
+
from .utils import ImageResponse
|
|
4
|
+
import random
|
|
5
|
+
|
|
6
|
+
class BaseImages(ABC):
|
|
7
|
+
@abstractmethod
|
|
8
|
+
def create(
|
|
9
|
+
self,
|
|
10
|
+
*,
|
|
11
|
+
model: str,
|
|
12
|
+
prompt: str,
|
|
13
|
+
n: int = 1,
|
|
14
|
+
size: str = "1024x1024",
|
|
15
|
+
response_format: str = "url",
|
|
16
|
+
user: Optional[str] = None,
|
|
17
|
+
style: str = "none",
|
|
18
|
+
aspect_ratio: str = "1:1",
|
|
19
|
+
timeout: int = None,
|
|
20
|
+
image_format: str = "png",
|
|
21
|
+
seed: Optional[int] = None,
|
|
22
|
+
**kwargs
|
|
23
|
+
) -> ImageResponse:
|
|
24
|
+
"""
|
|
25
|
+
Abstract method to create images from a prompt.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
model: The model to use for image generation.
|
|
29
|
+
prompt: The prompt for the image.
|
|
30
|
+
n: Number of images to generate.
|
|
31
|
+
size: Image size.
|
|
32
|
+
response_format: "url" or "b64_json".
|
|
33
|
+
user: Optional user identifier.
|
|
34
|
+
style: Optional style.
|
|
35
|
+
aspect_ratio: Optional aspect ratio.
|
|
36
|
+
timeout: Request timeout in seconds.
|
|
37
|
+
image_format: "png" or "jpeg" for output format.
|
|
38
|
+
seed: Optional random seed for reproducibility.
|
|
39
|
+
**kwargs: Additional provider-specific parameters.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
ImageResponse: The generated images.
|
|
43
|
+
"""
|
|
44
|
+
raise NotImplementedError
|
|
45
|
+
|
|
46
|
+
class TTICompatibleProvider(ABC):
|
|
47
|
+
"""
|
|
48
|
+
Abstract Base Class for TTI providers mimicking the OpenAI Python client structure.
|
|
49
|
+
Requires a nested 'images.create' structure.
|
|
50
|
+
"""
|
|
51
|
+
images: BaseImages
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def __init__(self, **kwargs: Any):
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def models(self):
|
|
60
|
+
"""
|
|
61
|
+
Property that returns an object with a .list() method returning available models.
|
|
62
|
+
Subclasses must implement this property.
|
|
63
|
+
"""
|
|
64
|
+
pass
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import os
|
|
3
|
+
import tempfile
|
|
4
|
+
import time
|
|
5
|
+
from typing import Optional, List, Dict, Any
|
|
6
|
+
from webscout.Provider.TTI.utils import ImageData, ImageResponse
|
|
7
|
+
from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
|
|
8
|
+
from io import BytesIO
|
|
9
|
+
from webscout.litagent import LitAgent
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from PIL import Image
|
|
13
|
+
except ImportError:
|
|
14
|
+
Image = None
|
|
15
|
+
|
|
16
|
+
class Images(BaseImages):
|
|
17
|
+
def __init__(self, client):
|
|
18
|
+
self._client = client
|
|
19
|
+
|
|
20
|
+
def create(self,
|
|
21
|
+
model: str = "flux_1_schnell",
|
|
22
|
+
prompt: str = None,
|
|
23
|
+
n: int = 1,
|
|
24
|
+
size: str = "1_1",
|
|
25
|
+
response_format: str = "url",
|
|
26
|
+
user: Optional[str] = None,
|
|
27
|
+
style: str = "none",
|
|
28
|
+
aspect_ratio: str = "1:1",
|
|
29
|
+
timeout: int = 60,
|
|
30
|
+
image_format: str = "png",
|
|
31
|
+
is_public: bool = False,
|
|
32
|
+
**kwargs
|
|
33
|
+
) -> ImageResponse:
|
|
34
|
+
if not prompt:
|
|
35
|
+
raise ValueError("Prompt is required!")
|
|
36
|
+
agent = LitAgent()
|
|
37
|
+
images = []
|
|
38
|
+
urls = []
|
|
39
|
+
api_url = self._client.api_endpoint
|
|
40
|
+
payload = {
|
|
41
|
+
"prompt": prompt,
|
|
42
|
+
"model": model,
|
|
43
|
+
"size": size,
|
|
44
|
+
"isPublic": is_public
|
|
45
|
+
}
|
|
46
|
+
for _ in range(n):
|
|
47
|
+
resp = self._client.session.post(api_url, json=payload, timeout=timeout)
|
|
48
|
+
resp.raise_for_status()
|
|
49
|
+
result = resp.json()
|
|
50
|
+
if result and 'result' in result:
|
|
51
|
+
image_data = result['result']
|
|
52
|
+
base64_data = image_data.split(',')[1]
|
|
53
|
+
img_bytes = base64.b64decode(base64_data)
|
|
54
|
+
# Convert to png or jpeg in memory if needed
|
|
55
|
+
if Image is not None:
|
|
56
|
+
with BytesIO(img_bytes) as input_io:
|
|
57
|
+
with Image.open(input_io) as im:
|
|
58
|
+
out_io = BytesIO()
|
|
59
|
+
if image_format.lower() == "jpeg":
|
|
60
|
+
im = im.convert("RGB")
|
|
61
|
+
im.save(out_io, format="JPEG")
|
|
62
|
+
else:
|
|
63
|
+
im.save(out_io, format="PNG")
|
|
64
|
+
img_bytes = out_io.getvalue()
|
|
65
|
+
images.append(img_bytes)
|
|
66
|
+
if response_format == "url":
|
|
67
|
+
def upload_file_with_retry(img_bytes, image_format, max_retries=3):
|
|
68
|
+
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
69
|
+
for attempt in range(max_retries):
|
|
70
|
+
tmp_path = None
|
|
71
|
+
try:
|
|
72
|
+
with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
|
|
73
|
+
tmp.write(img_bytes)
|
|
74
|
+
tmp.flush()
|
|
75
|
+
tmp_path = tmp.name
|
|
76
|
+
with open(tmp_path, 'rb') as f:
|
|
77
|
+
files = {
|
|
78
|
+
'fileToUpload': (f'image.{ext}', f, f'image/{ext}')
|
|
79
|
+
}
|
|
80
|
+
data = {
|
|
81
|
+
'reqtype': 'fileupload',
|
|
82
|
+
'json': 'true'
|
|
83
|
+
}
|
|
84
|
+
headers = {'User-Agent': agent.random()}
|
|
85
|
+
if attempt > 0:
|
|
86
|
+
headers['Connection'] = 'close'
|
|
87
|
+
resp = requests.post("https://catbox.moe/user/api.php", files=files, data=data, headers=headers, timeout=timeout)
|
|
88
|
+
if resp.status_code == 200 and resp.text.strip():
|
|
89
|
+
text = resp.text.strip()
|
|
90
|
+
if text.startswith('http'):
|
|
91
|
+
return text
|
|
92
|
+
try:
|
|
93
|
+
result = resp.json()
|
|
94
|
+
if "url" in result:
|
|
95
|
+
return result["url"]
|
|
96
|
+
except Exception:
|
|
97
|
+
if 'http' in text:
|
|
98
|
+
return text
|
|
99
|
+
except Exception:
|
|
100
|
+
if attempt < max_retries - 1:
|
|
101
|
+
time.sleep(1 * (attempt + 1))
|
|
102
|
+
finally:
|
|
103
|
+
if tmp_path and os.path.isfile(tmp_path):
|
|
104
|
+
try:
|
|
105
|
+
os.remove(tmp_path)
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
return None
|
|
109
|
+
def upload_file_alternative(img_bytes, image_format):
|
|
110
|
+
try:
|
|
111
|
+
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
112
|
+
with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
|
|
113
|
+
tmp.write(img_bytes)
|
|
114
|
+
tmp.flush()
|
|
115
|
+
tmp_path = tmp.name
|
|
116
|
+
try:
|
|
117
|
+
if not os.path.isfile(tmp_path):
|
|
118
|
+
return None
|
|
119
|
+
with open(tmp_path, 'rb') as img_file:
|
|
120
|
+
files = {'file': img_file}
|
|
121
|
+
response = requests.post('https://0x0.st', files=files)
|
|
122
|
+
response.raise_for_status()
|
|
123
|
+
image_url = response.text.strip()
|
|
124
|
+
if not image_url.startswith('http'):
|
|
125
|
+
return None
|
|
126
|
+
return image_url
|
|
127
|
+
except Exception:
|
|
128
|
+
return None
|
|
129
|
+
finally:
|
|
130
|
+
try:
|
|
131
|
+
os.remove(tmp_path)
|
|
132
|
+
except Exception:
|
|
133
|
+
pass
|
|
134
|
+
except Exception:
|
|
135
|
+
return None
|
|
136
|
+
uploaded_url = upload_file_with_retry(img_bytes, image_format)
|
|
137
|
+
if not uploaded_url:
|
|
138
|
+
uploaded_url = upload_file_alternative(img_bytes, image_format)
|
|
139
|
+
if uploaded_url:
|
|
140
|
+
urls.append(uploaded_url)
|
|
141
|
+
else:
|
|
142
|
+
raise RuntimeError("Failed to upload image to catbox.moe using all available methods")
|
|
143
|
+
else:
|
|
144
|
+
raise RuntimeError("No image data received from FastFlux API")
|
|
145
|
+
result_data = []
|
|
146
|
+
if response_format == "url":
|
|
147
|
+
for url in urls:
|
|
148
|
+
result_data.append(ImageData(url=url))
|
|
149
|
+
elif response_format == "b64_json":
|
|
150
|
+
import base64
|
|
151
|
+
for img in images:
|
|
152
|
+
b64 = base64.b64encode(img).decode("utf-8")
|
|
153
|
+
result_data.append(ImageData(b64_json=b64))
|
|
154
|
+
else:
|
|
155
|
+
raise ValueError("response_format must be 'url' or 'b64_json'")
|
|
156
|
+
from time import time as _time
|
|
157
|
+
return ImageResponse(
|
|
158
|
+
created=int(_time()),
|
|
159
|
+
data=result_data
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
class FastFluxAI(TTICompatibleProvider):
|
|
163
|
+
AVAILABLE_MODELS = [
|
|
164
|
+
"flux_1_schnell",
|
|
165
|
+
]
|
|
166
|
+
def __init__(self, api_key: str = None):
|
|
167
|
+
self.api_endpoint = "https://api.freeflux.ai/v1/images/generate"
|
|
168
|
+
self.session = requests.Session()
|
|
169
|
+
self.user_agent = LitAgent().random()
|
|
170
|
+
self.api_key = api_key
|
|
171
|
+
self.headers = {
|
|
172
|
+
"accept": "application/json, text/plain, */*",
|
|
173
|
+
"accept-language": "en-US,en;q=0.9",
|
|
174
|
+
"content-type": "application/json",
|
|
175
|
+
"origin": "https://freeflux.ai",
|
|
176
|
+
"referer": "https://freeflux.ai/",
|
|
177
|
+
"user-agent": self.user_agent,
|
|
178
|
+
}
|
|
179
|
+
if self.api_key:
|
|
180
|
+
self.headers["authorization"] = f"Bearer {self.api_key}"
|
|
181
|
+
self.session.headers.update(self.headers)
|
|
182
|
+
self.images = Images(self)
|
|
183
|
+
@property
|
|
184
|
+
def models(self):
|
|
185
|
+
class _ModelList:
|
|
186
|
+
def list(inner_self):
|
|
187
|
+
return type(self).AVAILABLE_MODELS
|
|
188
|
+
return _ModelList()
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
from rich import print
|
|
192
|
+
client = FastFluxAI()
|
|
193
|
+
response = client.images.create(
|
|
194
|
+
model="flux_1_schnell",
|
|
195
|
+
prompt="A cool cyberpunk city at night",
|
|
196
|
+
response_format="url",
|
|
197
|
+
n=2,
|
|
198
|
+
timeout=30,
|
|
199
|
+
)
|
|
200
|
+
print(response)
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import os
|
|
3
|
+
import uuid
|
|
4
|
+
import time
|
|
5
|
+
import tempfile
|
|
6
|
+
from typing import Optional, List
|
|
7
|
+
from webscout.Provider.TTI.utils import ImageData, ImageResponse
|
|
8
|
+
from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
|
|
9
|
+
from io import BytesIO
|
|
10
|
+
from webscout.litagent import LitAgent
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from PIL import Image
|
|
14
|
+
except ImportError:
|
|
15
|
+
Image = None
|
|
16
|
+
|
|
17
|
+
class Images(BaseImages):
|
|
18
|
+
def __init__(self, client):
|
|
19
|
+
self._client = client
|
|
20
|
+
|
|
21
|
+
def create(self,
|
|
22
|
+
model: str = "magicstudio",
|
|
23
|
+
prompt: str = None,
|
|
24
|
+
n: int = 1,
|
|
25
|
+
size: str = None,
|
|
26
|
+
response_format: str = "url",
|
|
27
|
+
user: Optional[str] = None,
|
|
28
|
+
style: str = None,
|
|
29
|
+
aspect_ratio: str = None,
|
|
30
|
+
timeout: int = 60,
|
|
31
|
+
image_format: str = "jpg",
|
|
32
|
+
**kwargs
|
|
33
|
+
) -> ImageResponse:
|
|
34
|
+
if not prompt:
|
|
35
|
+
raise ValueError("Prompt is required!")
|
|
36
|
+
agent = LitAgent()
|
|
37
|
+
images = []
|
|
38
|
+
urls = []
|
|
39
|
+
api_url = "https://ai-api.magicstudio.com/api/ai-art-generator"
|
|
40
|
+
headers = {
|
|
41
|
+
"Accept": "application/json, text/plain, */*",
|
|
42
|
+
"User-Agent": agent.random(),
|
|
43
|
+
"Origin": "https://magicstudio.com",
|
|
44
|
+
"Referer": "https://magicstudio.com/ai-art-generator/",
|
|
45
|
+
"DNT": "1",
|
|
46
|
+
"Sec-GPC": "1"
|
|
47
|
+
}
|
|
48
|
+
session = requests.Session()
|
|
49
|
+
session.headers.update(headers)
|
|
50
|
+
for _ in range(n):
|
|
51
|
+
form_data = {
|
|
52
|
+
"prompt": prompt,
|
|
53
|
+
"output_format": "bytes",
|
|
54
|
+
"user_profile_id": "null",
|
|
55
|
+
"anonymous_user_id": str(uuid.uuid4()),
|
|
56
|
+
"request_timestamp": time.time(),
|
|
57
|
+
"user_is_subscribed": "false",
|
|
58
|
+
"client_id": uuid.uuid4().hex,
|
|
59
|
+
}
|
|
60
|
+
resp = session.post(api_url, data=form_data, timeout=timeout)
|
|
61
|
+
resp.raise_for_status()
|
|
62
|
+
img_bytes = resp.content
|
|
63
|
+
# Convert to png or jpeg in memory if needed
|
|
64
|
+
if Image is not None:
|
|
65
|
+
with BytesIO(img_bytes) as input_io:
|
|
66
|
+
with Image.open(input_io) as im:
|
|
67
|
+
out_io = BytesIO()
|
|
68
|
+
if image_format.lower() == "jpeg" or image_format.lower() == "jpg":
|
|
69
|
+
im = im.convert("RGB")
|
|
70
|
+
im.save(out_io, format="JPEG")
|
|
71
|
+
else:
|
|
72
|
+
im.save(out_io, format="PNG")
|
|
73
|
+
img_bytes = out_io.getvalue()
|
|
74
|
+
images.append(img_bytes)
|
|
75
|
+
if response_format == "url":
|
|
76
|
+
def upload_file_with_retry(img_bytes, image_format, max_retries=3):
|
|
77
|
+
ext = "jpg" if image_format.lower() in ("jpeg", "jpg") else "png"
|
|
78
|
+
for attempt in range(max_retries):
|
|
79
|
+
tmp_path = None
|
|
80
|
+
try:
|
|
81
|
+
with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
|
|
82
|
+
tmp.write(img_bytes)
|
|
83
|
+
tmp.flush()
|
|
84
|
+
tmp_path = tmp.name
|
|
85
|
+
with open(tmp_path, 'rb') as f:
|
|
86
|
+
files = {
|
|
87
|
+
'fileToUpload': (f'image.{ext}', f, f'image/{ext}')
|
|
88
|
+
}
|
|
89
|
+
data = {
|
|
90
|
+
'reqtype': 'fileupload',
|
|
91
|
+
'json': 'true'
|
|
92
|
+
}
|
|
93
|
+
headers = {'User-Agent': agent.random()}
|
|
94
|
+
if attempt > 0:
|
|
95
|
+
headers['Connection'] = 'close'
|
|
96
|
+
resp = requests.post("https://catbox.moe/user/api.php", files=files, data=data, headers=headers, timeout=timeout)
|
|
97
|
+
if resp.status_code == 200 and resp.text.strip():
|
|
98
|
+
text = resp.text.strip()
|
|
99
|
+
if text.startswith('http'):
|
|
100
|
+
return text
|
|
101
|
+
try:
|
|
102
|
+
result = resp.json()
|
|
103
|
+
if "url" in result:
|
|
104
|
+
return result["url"]
|
|
105
|
+
except Exception:
|
|
106
|
+
if 'http' in text:
|
|
107
|
+
return text
|
|
108
|
+
except Exception:
|
|
109
|
+
if attempt < max_retries - 1:
|
|
110
|
+
time.sleep(1 * (attempt + 1))
|
|
111
|
+
finally:
|
|
112
|
+
if tmp_path and os.path.isfile(tmp_path):
|
|
113
|
+
try:
|
|
114
|
+
os.remove(tmp_path)
|
|
115
|
+
except Exception:
|
|
116
|
+
pass
|
|
117
|
+
return None
|
|
118
|
+
def upload_file_alternative(img_bytes, image_format):
|
|
119
|
+
try:
|
|
120
|
+
ext = "jpg" if image_format.lower() in ("jpeg", "jpg") else "png"
|
|
121
|
+
with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
|
|
122
|
+
tmp.write(img_bytes)
|
|
123
|
+
tmp.flush()
|
|
124
|
+
tmp_path = tmp.name
|
|
125
|
+
try:
|
|
126
|
+
if not os.path.isfile(tmp_path):
|
|
127
|
+
return None
|
|
128
|
+
with open(tmp_path, 'rb') as img_file:
|
|
129
|
+
files = {'file': img_file}
|
|
130
|
+
alt_resp = requests.post('https://0x0.st', files=files)
|
|
131
|
+
alt_resp.raise_for_status()
|
|
132
|
+
image_url = alt_resp.text.strip()
|
|
133
|
+
if not image_url.startswith('http'):
|
|
134
|
+
return None
|
|
135
|
+
return image_url
|
|
136
|
+
except Exception:
|
|
137
|
+
return None
|
|
138
|
+
finally:
|
|
139
|
+
try:
|
|
140
|
+
os.remove(tmp_path)
|
|
141
|
+
except Exception:
|
|
142
|
+
pass
|
|
143
|
+
except Exception:
|
|
144
|
+
return None
|
|
145
|
+
uploaded_url = upload_file_with_retry(img_bytes, image_format)
|
|
146
|
+
if not uploaded_url:
|
|
147
|
+
uploaded_url = upload_file_alternative(img_bytes, image_format)
|
|
148
|
+
if uploaded_url:
|
|
149
|
+
urls.append(uploaded_url)
|
|
150
|
+
else:
|
|
151
|
+
raise RuntimeError("Failed to upload image to catbox.moe using all available methods")
|
|
152
|
+
result_data = []
|
|
153
|
+
if response_format == "url":
|
|
154
|
+
for url in urls:
|
|
155
|
+
result_data.append(ImageData(url=url))
|
|
156
|
+
elif response_format == "b64_json":
|
|
157
|
+
import base64
|
|
158
|
+
for img in images:
|
|
159
|
+
b64 = base64.b64encode(img).decode("utf-8")
|
|
160
|
+
result_data.append(ImageData(b64_json=b64))
|
|
161
|
+
else:
|
|
162
|
+
raise ValueError("response_format must be 'url' or 'b64_json'")
|
|
163
|
+
from time import time as _time
|
|
164
|
+
return ImageResponse(
|
|
165
|
+
created=int(_time()),
|
|
166
|
+
data=result_data
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
class MagicStudioAI(TTICompatibleProvider):
|
|
170
|
+
AVAILABLE_MODELS = ["magicstudio"]
|
|
171
|
+
def __init__(self):
|
|
172
|
+
self.api_endpoint = "https://ai-api.magicstudio.com/api/ai-art-generator"
|
|
173
|
+
self.session = requests.Session()
|
|
174
|
+
self.user_agent = LitAgent().random()
|
|
175
|
+
self.headers = {
|
|
176
|
+
"accept": "*/*",
|
|
177
|
+
"accept-language": "en-US,en;q=0.9",
|
|
178
|
+
"content-type": "application/json",
|
|
179
|
+
"origin": "https://magicstudio.com",
|
|
180
|
+
"referer": "https://magicstudio.com/ai-art-generator/",
|
|
181
|
+
"user-agent": self.user_agent,
|
|
182
|
+
}
|
|
183
|
+
self.session.headers.update(self.headers)
|
|
184
|
+
self.images = Images(self)
|
|
185
|
+
@property
|
|
186
|
+
def models(self):
|
|
187
|
+
class _ModelList:
|
|
188
|
+
def list(inner_self):
|
|
189
|
+
return type(self).AVAILABLE_MODELS
|
|
190
|
+
return _ModelList()
|
|
191
|
+
|
|
192
|
+
if __name__ == "__main__":
|
|
193
|
+
from rich import print
|
|
194
|
+
client = MagicStudioAI()
|
|
195
|
+
response = client.images.create(
|
|
196
|
+
prompt="A cool cyberpunk city at night",
|
|
197
|
+
response_format="url",
|
|
198
|
+
n=2,
|
|
199
|
+
timeout=30,
|
|
200
|
+
)
|
|
201
|
+
print(response)
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from typing import Optional, List, Dict, Any
|
|
3
|
+
from webscout.Provider.TTI.utils import ImageData, ImageResponse
|
|
4
|
+
from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
|
|
5
|
+
from io import BytesIO
|
|
6
|
+
import os
|
|
7
|
+
import tempfile
|
|
8
|
+
from webscout.litagent import LitAgent
|
|
9
|
+
import time
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from PIL import Image
|
|
14
|
+
except ImportError:
|
|
15
|
+
Image = None
|
|
16
|
+
|
|
17
|
+
class Images(BaseImages):
|
|
18
|
+
def __init__(self, client):
|
|
19
|
+
self._client = client
|
|
20
|
+
|
|
21
|
+
def create(self,
|
|
22
|
+
model: str,
|
|
23
|
+
prompt: str,
|
|
24
|
+
n: int = 1,
|
|
25
|
+
size: str = "1024x1024",
|
|
26
|
+
response_format: str = "url",
|
|
27
|
+
user: Optional[str] = None,
|
|
28
|
+
style: str = "none",
|
|
29
|
+
aspect_ratio: str = "1:1",
|
|
30
|
+
timeout: int = 60,
|
|
31
|
+
image_format: str = "jpeg",
|
|
32
|
+
**kwargs
|
|
33
|
+
) -> ImageResponse:
|
|
34
|
+
"""
|
|
35
|
+
image_format: "png" or "jpeg"
|
|
36
|
+
"""
|
|
37
|
+
if Image is None:
|
|
38
|
+
raise ImportError("Pillow (PIL) is required for image format conversion.")
|
|
39
|
+
|
|
40
|
+
images = []
|
|
41
|
+
urls = []
|
|
42
|
+
agent = LitAgent()
|
|
43
|
+
|
|
44
|
+
def upload_file_with_retry(img_bytes, image_format, max_retries=3):
|
|
45
|
+
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
46
|
+
for attempt in range(max_retries):
|
|
47
|
+
tmp_path = None
|
|
48
|
+
try:
|
|
49
|
+
with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
|
|
50
|
+
tmp.write(img_bytes)
|
|
51
|
+
tmp.flush()
|
|
52
|
+
tmp_path = tmp.name
|
|
53
|
+
with open(tmp_path, 'rb') as f:
|
|
54
|
+
files = {
|
|
55
|
+
'fileToUpload': (f'image.{ext}', f, f'image/{ext}')
|
|
56
|
+
}
|
|
57
|
+
data = {
|
|
58
|
+
'reqtype': 'fileupload',
|
|
59
|
+
'json': 'true'
|
|
60
|
+
}
|
|
61
|
+
headers = {'User-Agent': agent.random()}
|
|
62
|
+
if attempt > 0:
|
|
63
|
+
headers['Connection'] = 'close'
|
|
64
|
+
resp = requests.post("https://catbox.moe/user/api.php", files=files, data=data, headers=headers, timeout=timeout)
|
|
65
|
+
if resp.status_code == 200 and resp.text.strip():
|
|
66
|
+
text = resp.text.strip()
|
|
67
|
+
if text.startswith('http'):
|
|
68
|
+
return text
|
|
69
|
+
try:
|
|
70
|
+
result = resp.json()
|
|
71
|
+
if "url" in result:
|
|
72
|
+
return result["url"]
|
|
73
|
+
except json.JSONDecodeError:
|
|
74
|
+
if 'http' in text:
|
|
75
|
+
return text
|
|
76
|
+
except Exception:
|
|
77
|
+
if attempt < max_retries - 1:
|
|
78
|
+
time.sleep(1 * (attempt + 1))
|
|
79
|
+
finally:
|
|
80
|
+
if tmp_path and os.path.isfile(tmp_path):
|
|
81
|
+
try:
|
|
82
|
+
os.remove(tmp_path)
|
|
83
|
+
except Exception:
|
|
84
|
+
pass
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
def upload_file_alternative(img_bytes, image_format):
|
|
88
|
+
try:
|
|
89
|
+
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
90
|
+
with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
|
|
91
|
+
tmp.write(img_bytes)
|
|
92
|
+
tmp.flush()
|
|
93
|
+
tmp_path = tmp.name
|
|
94
|
+
try:
|
|
95
|
+
if not os.path.isfile(tmp_path):
|
|
96
|
+
return None
|
|
97
|
+
with open(tmp_path, 'rb') as img_file:
|
|
98
|
+
files = {'file': img_file}
|
|
99
|
+
response = requests.post('https://0x0.st', files=files)
|
|
100
|
+
response.raise_for_status()
|
|
101
|
+
image_url = response.text.strip()
|
|
102
|
+
if not image_url.startswith('http'):
|
|
103
|
+
return None
|
|
104
|
+
return image_url
|
|
105
|
+
except Exception:
|
|
106
|
+
return None
|
|
107
|
+
finally:
|
|
108
|
+
try:
|
|
109
|
+
os.remove(tmp_path)
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
except Exception:
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
for _ in range(n):
|
|
116
|
+
payload = {"prompt": prompt}
|
|
117
|
+
resp = self._client.session.post(
|
|
118
|
+
self._client.api_endpoint,
|
|
119
|
+
json=payload,
|
|
120
|
+
timeout=timeout
|
|
121
|
+
)
|
|
122
|
+
resp.raise_for_status()
|
|
123
|
+
# Piclumen returns image/jpeg directly
|
|
124
|
+
if resp.headers.get('content-type') == 'image/jpeg':
|
|
125
|
+
img_bytes = resp.content
|
|
126
|
+
# Convert to png or jpeg in memory
|
|
127
|
+
with BytesIO(img_bytes) as input_io:
|
|
128
|
+
with Image.open(input_io) as im:
|
|
129
|
+
out_io = BytesIO()
|
|
130
|
+
if image_format.lower() == "jpeg":
|
|
131
|
+
im = im.convert("RGB")
|
|
132
|
+
im.save(out_io, format="JPEG")
|
|
133
|
+
else:
|
|
134
|
+
im.save(out_io, format="PNG")
|
|
135
|
+
img_bytes = out_io.getvalue()
|
|
136
|
+
images.append(img_bytes)
|
|
137
|
+
if response_format == "url":
|
|
138
|
+
uploaded_url = upload_file_with_retry(img_bytes, image_format)
|
|
139
|
+
if not uploaded_url:
|
|
140
|
+
uploaded_url = upload_file_alternative(img_bytes, image_format)
|
|
141
|
+
if uploaded_url:
|
|
142
|
+
urls.append(uploaded_url)
|
|
143
|
+
else:
|
|
144
|
+
raise RuntimeError("Failed to upload image to catbox.moe using all available methods")
|
|
145
|
+
else:
|
|
146
|
+
raise RuntimeError("No image data received from Piclumen")
|
|
147
|
+
|
|
148
|
+
result_data = []
|
|
149
|
+
if response_format == "url":
|
|
150
|
+
for url in urls:
|
|
151
|
+
result_data.append(ImageData(url=url))
|
|
152
|
+
elif response_format == "b64_json":
|
|
153
|
+
import base64
|
|
154
|
+
for img in images:
|
|
155
|
+
b64 = base64.b64encode(img).decode("utf-8")
|
|
156
|
+
result_data.append(ImageData(b64_json=b64))
|
|
157
|
+
else:
|
|
158
|
+
raise ValueError("response_format must be 'url' or 'b64_json'")
|
|
159
|
+
|
|
160
|
+
from time import time as _time
|
|
161
|
+
return ImageResponse(
|
|
162
|
+
created=int(_time()),
|
|
163
|
+
data=result_data
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
class PiclumenAI(TTICompatibleProvider):
|
|
167
|
+
AVAILABLE_MODELS = [
|
|
168
|
+
"piclumen-v1"
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
def __init__(self):
|
|
172
|
+
self.api_endpoint = "https://s9.piclumen.art/comfy/api/generate-image"
|
|
173
|
+
self.session = requests.Session()
|
|
174
|
+
self.user_agent = LitAgent().random()
|
|
175
|
+
self.headers = {
|
|
176
|
+
"accept": "*/*",
|
|
177
|
+
"accept-language": "en-US,en;q=0.9",
|
|
178
|
+
"content-type": "application/json",
|
|
179
|
+
"origin": "https://www.piclumen.com",
|
|
180
|
+
"referer": "https://s9.piclumen.art/",
|
|
181
|
+
"user-agent": self.user_agent,
|
|
182
|
+
}
|
|
183
|
+
self.session.headers.update(self.headers)
|
|
184
|
+
self.images = Images(self)
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def models(self):
|
|
188
|
+
class _ModelList:
|
|
189
|
+
def list(inner_self):
|
|
190
|
+
return type(self).AVAILABLE_MODELS
|
|
191
|
+
return _ModelList()
|
|
192
|
+
|
|
193
|
+
if __name__ == "__main__":
|
|
194
|
+
from rich import print
|
|
195
|
+
client = PiclumenAI()
|
|
196
|
+
response = client.images.create(
|
|
197
|
+
model="piclumen-v1",
|
|
198
|
+
prompt="a futuristic city skyline at sunset",
|
|
199
|
+
response_format="url",
|
|
200
|
+
n=2,
|
|
201
|
+
timeout=30,
|
|
202
|
+
)
|
|
203
|
+
print(response)
|