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.
- webscout/AIauto.py +4 -4
- webscout/AIbase.py +61 -1
- webscout/AIutel.py +46 -53
- webscout/Bing_search.py +418 -0
- webscout/Extra/YTToolkit/ytapi/patterns.py +45 -45
- webscout/Extra/YTToolkit/ytapi/stream.py +1 -1
- webscout/Extra/YTToolkit/ytapi/video.py +10 -10
- webscout/Extra/autocoder/autocoder_utiles.py +1 -1
- webscout/Extra/gguf.py +706 -177
- webscout/Litlogger/formats.py +9 -0
- webscout/Litlogger/handlers.py +18 -0
- webscout/Litlogger/logger.py +43 -1
- webscout/Provider/AISEARCH/genspark_search.py +7 -7
- webscout/Provider/AISEARCH/scira_search.py +3 -2
- webscout/Provider/GeminiProxy.py +140 -0
- webscout/Provider/LambdaChat.py +7 -1
- webscout/Provider/MCPCore.py +78 -75
- webscout/Provider/OPENAI/BLACKBOXAI.py +1046 -1017
- webscout/Provider/OPENAI/GeminiProxy.py +328 -0
- webscout/Provider/OPENAI/Qwen3.py +303 -303
- webscout/Provider/OPENAI/README.md +5 -0
- webscout/Provider/OPENAI/README_AUTOPROXY.md +238 -0
- webscout/Provider/OPENAI/TogetherAI.py +355 -0
- webscout/Provider/OPENAI/__init__.py +16 -1
- webscout/Provider/OPENAI/autoproxy.py +332 -0
- webscout/Provider/OPENAI/base.py +101 -14
- webscout/Provider/OPENAI/chatgpt.py +15 -2
- webscout/Provider/OPENAI/chatgptclone.py +14 -3
- webscout/Provider/OPENAI/deepinfra.py +339 -328
- webscout/Provider/OPENAI/e2b.py +295 -74
- webscout/Provider/OPENAI/mcpcore.py +109 -70
- webscout/Provider/OPENAI/opkfc.py +18 -6
- webscout/Provider/OPENAI/scirachat.py +59 -50
- webscout/Provider/OPENAI/toolbaz.py +2 -10
- webscout/Provider/OPENAI/writecream.py +166 -166
- webscout/Provider/OPENAI/x0gpt.py +367 -367
- webscout/Provider/OPENAI/xenai.py +514 -0
- webscout/Provider/OPENAI/yep.py +389 -383
- webscout/Provider/STT/__init__.py +3 -0
- webscout/Provider/STT/base.py +281 -0
- webscout/Provider/STT/elevenlabs.py +265 -0
- webscout/Provider/TTI/__init__.py +4 -1
- webscout/Provider/TTI/aiarta.py +399 -365
- webscout/Provider/TTI/base.py +74 -2
- webscout/Provider/TTI/bing.py +231 -0
- webscout/Provider/TTI/fastflux.py +63 -30
- webscout/Provider/TTI/gpt1image.py +149 -0
- webscout/Provider/TTI/imagen.py +196 -0
- webscout/Provider/TTI/magicstudio.py +60 -29
- webscout/Provider/TTI/piclumen.py +43 -32
- webscout/Provider/TTI/pixelmuse.py +232 -225
- webscout/Provider/TTI/pollinations.py +43 -32
- webscout/Provider/TTI/together.py +287 -0
- webscout/Provider/TTI/utils.py +2 -1
- webscout/Provider/TTS/README.md +1 -0
- webscout/Provider/TTS/__init__.py +2 -1
- webscout/Provider/TTS/freetts.py +140 -0
- webscout/Provider/TTS/speechma.py +45 -39
- webscout/Provider/TogetherAI.py +366 -0
- webscout/Provider/UNFINISHED/ChutesAI.py +314 -0
- webscout/Provider/UNFINISHED/fetch_together_models.py +95 -0
- webscout/Provider/XenAI.py +324 -0
- webscout/Provider/__init__.py +8 -0
- webscout/Provider/deepseek_assistant.py +378 -0
- webscout/Provider/scira_chat.py +3 -2
- webscout/Provider/toolbaz.py +0 -1
- webscout/auth/__init__.py +44 -0
- webscout/auth/api_key_manager.py +189 -0
- webscout/auth/auth_system.py +100 -0
- webscout/auth/config.py +76 -0
- webscout/auth/database.py +400 -0
- webscout/auth/exceptions.py +67 -0
- webscout/auth/middleware.py +248 -0
- webscout/auth/models.py +130 -0
- webscout/auth/providers.py +257 -0
- webscout/auth/rate_limiter.py +254 -0
- webscout/auth/request_models.py +127 -0
- webscout/auth/request_processing.py +226 -0
- webscout/auth/routes.py +526 -0
- webscout/auth/schemas.py +103 -0
- webscout/auth/server.py +312 -0
- webscout/auth/static/favicon.svg +11 -0
- webscout/auth/swagger_ui.py +203 -0
- webscout/auth/templates/components/authentication.html +237 -0
- webscout/auth/templates/components/base.html +103 -0
- webscout/auth/templates/components/endpoints.html +750 -0
- webscout/auth/templates/components/examples.html +491 -0
- webscout/auth/templates/components/footer.html +75 -0
- webscout/auth/templates/components/header.html +27 -0
- webscout/auth/templates/components/models.html +286 -0
- webscout/auth/templates/components/navigation.html +70 -0
- webscout/auth/templates/static/api.js +455 -0
- webscout/auth/templates/static/icons.js +168 -0
- webscout/auth/templates/static/main.js +784 -0
- webscout/auth/templates/static/particles.js +201 -0
- webscout/auth/templates/static/styles.css +3353 -0
- webscout/auth/templates/static/ui.js +374 -0
- webscout/auth/templates/swagger_ui.html +170 -0
- webscout/client.py +49 -3
- webscout/litagent/Readme.md +12 -3
- webscout/litagent/agent.py +99 -62
- webscout/scout/core/scout.py +104 -26
- webscout/scout/element.py +139 -18
- webscout/swiftcli/core/cli.py +14 -3
- webscout/swiftcli/decorators/output.py +59 -9
- webscout/update_checker.py +31 -49
- webscout/version.py +1 -1
- webscout/webscout_search.py +4 -12
- webscout/webscout_search_async.py +3 -10
- webscout/yep_search.py +2 -11
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/METADATA +41 -11
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/RECORD +116 -68
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/entry_points.txt +1 -1
- webscout/Provider/HF_space/__init__.py +0 -0
- webscout/Provider/HF_space/qwen_qwen2.py +0 -206
- webscout/Provider/OPENAI/api.py +0 -1035
- webscout/Provider/TTI/artbit.py +0 -0
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/WHEEL +0 -0
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.3.dist-info → webscout-8.3.2.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import requests
|
|
2
2
|
from typing import Optional, List, Dict, Any
|
|
3
|
-
from webscout.Provider.TTI.utils import
|
|
3
|
+
from webscout.Provider.TTI.utils import (
|
|
4
|
+
ImageData,
|
|
5
|
+
ImageResponse
|
|
6
|
+
)
|
|
4
7
|
from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
|
|
5
8
|
from io import BytesIO
|
|
6
9
|
import os
|
|
@@ -14,11 +17,13 @@ try:
|
|
|
14
17
|
except ImportError:
|
|
15
18
|
Image = None
|
|
16
19
|
|
|
20
|
+
|
|
17
21
|
class Images(BaseImages):
|
|
18
22
|
def __init__(self, client):
|
|
19
23
|
self._client = client
|
|
20
24
|
|
|
21
|
-
def create(
|
|
25
|
+
def create(
|
|
26
|
+
self,
|
|
22
27
|
model: str,
|
|
23
28
|
prompt: str,
|
|
24
29
|
n: int = 1,
|
|
@@ -29,7 +34,7 @@ class Images(BaseImages):
|
|
|
29
34
|
aspect_ratio: str = "1:1",
|
|
30
35
|
timeout: int = 60,
|
|
31
36
|
image_format: str = "jpeg",
|
|
32
|
-
**kwargs
|
|
37
|
+
**kwargs,
|
|
33
38
|
) -> ImageResponse:
|
|
34
39
|
"""
|
|
35
40
|
image_format: "png" or "jpeg"
|
|
@@ -46,32 +51,35 @@ class Images(BaseImages):
|
|
|
46
51
|
for attempt in range(max_retries):
|
|
47
52
|
tmp_path = None
|
|
48
53
|
try:
|
|
49
|
-
with tempfile.NamedTemporaryFile(
|
|
54
|
+
with tempfile.NamedTemporaryFile(
|
|
55
|
+
suffix=f".{ext}", delete=False
|
|
56
|
+
) as tmp:
|
|
50
57
|
tmp.write(img_bytes)
|
|
51
58
|
tmp.flush()
|
|
52
59
|
tmp_path = tmp.name
|
|
53
|
-
with open(tmp_path,
|
|
54
|
-
files = {
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
data = {
|
|
58
|
-
'reqtype': 'fileupload',
|
|
59
|
-
'json': 'true'
|
|
60
|
-
}
|
|
61
|
-
headers = {'User-Agent': agent.random()}
|
|
60
|
+
with open(tmp_path, "rb") as f:
|
|
61
|
+
files = {"fileToUpload": (f"image.{ext}", f, f"image/{ext}")}
|
|
62
|
+
data = {"reqtype": "fileupload", "json": "true"}
|
|
63
|
+
headers = {"User-Agent": agent.random()}
|
|
62
64
|
if attempt > 0:
|
|
63
|
-
headers[
|
|
64
|
-
resp = requests.post(
|
|
65
|
+
headers["Connection"] = "close"
|
|
66
|
+
resp = requests.post(
|
|
67
|
+
"https://catbox.moe/user/api.php",
|
|
68
|
+
files=files,
|
|
69
|
+
data=data,
|
|
70
|
+
headers=headers,
|
|
71
|
+
timeout=timeout,
|
|
72
|
+
)
|
|
65
73
|
if resp.status_code == 200 and resp.text.strip():
|
|
66
74
|
text = resp.text.strip()
|
|
67
|
-
if text.startswith(
|
|
75
|
+
if text.startswith("http"):
|
|
68
76
|
return text
|
|
69
77
|
try:
|
|
70
78
|
result = resp.json()
|
|
71
79
|
if "url" in result:
|
|
72
80
|
return result["url"]
|
|
73
81
|
except json.JSONDecodeError:
|
|
74
|
-
if
|
|
82
|
+
if "http" in text:
|
|
75
83
|
return text
|
|
76
84
|
except Exception:
|
|
77
85
|
if attempt < max_retries - 1:
|
|
@@ -94,12 +102,12 @@ class Images(BaseImages):
|
|
|
94
102
|
try:
|
|
95
103
|
if not os.path.isfile(tmp_path):
|
|
96
104
|
return None
|
|
97
|
-
with open(tmp_path,
|
|
98
|
-
files = {
|
|
99
|
-
response = requests.post(
|
|
105
|
+
with open(tmp_path, "rb") as img_file:
|
|
106
|
+
files = {"file": img_file}
|
|
107
|
+
response = requests.post("https://0x0.st", files=files)
|
|
100
108
|
response.raise_for_status()
|
|
101
109
|
image_url = response.text.strip()
|
|
102
|
-
if not image_url.startswith(
|
|
110
|
+
if not image_url.startswith("http"):
|
|
103
111
|
return None
|
|
104
112
|
return image_url
|
|
105
113
|
except Exception:
|
|
@@ -117,11 +125,11 @@ class Images(BaseImages):
|
|
|
117
125
|
resp = self._client.session.post(
|
|
118
126
|
self._client.api_endpoint,
|
|
119
127
|
json=payload,
|
|
120
|
-
timeout=timeout
|
|
128
|
+
timeout=timeout,
|
|
121
129
|
)
|
|
122
130
|
resp.raise_for_status()
|
|
123
131
|
# Piclumen returns image/jpeg directly
|
|
124
|
-
if resp.headers.get(
|
|
132
|
+
if resp.headers.get("content-type") == "image/jpeg":
|
|
125
133
|
img_bytes = resp.content
|
|
126
134
|
# Convert to png or jpeg in memory
|
|
127
135
|
with BytesIO(img_bytes) as input_io:
|
|
@@ -141,7 +149,9 @@ class Images(BaseImages):
|
|
|
141
149
|
if uploaded_url:
|
|
142
150
|
urls.append(uploaded_url)
|
|
143
151
|
else:
|
|
144
|
-
raise RuntimeError(
|
|
152
|
+
raise RuntimeError(
|
|
153
|
+
"Failed to upload image to catbox.moe using all available methods"
|
|
154
|
+
)
|
|
145
155
|
else:
|
|
146
156
|
raise RuntimeError("No image data received from Piclumen")
|
|
147
157
|
|
|
@@ -151,6 +161,7 @@ class Images(BaseImages):
|
|
|
151
161
|
result_data.append(ImageData(url=url))
|
|
152
162
|
elif response_format == "b64_json":
|
|
153
163
|
import base64
|
|
164
|
+
|
|
154
165
|
for img in images:
|
|
155
166
|
b64 = base64.b64encode(img).decode("utf-8")
|
|
156
167
|
result_data.append(ImageData(b64_json=b64))
|
|
@@ -158,15 +169,12 @@ class Images(BaseImages):
|
|
|
158
169
|
raise ValueError("response_format must be 'url' or 'b64_json'")
|
|
159
170
|
|
|
160
171
|
from time import time as _time
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
)
|
|
172
|
+
|
|
173
|
+
return ImageResponse(created=int(_time()), data=result_data)
|
|
174
|
+
|
|
165
175
|
|
|
166
176
|
class PiclumenAI(TTICompatibleProvider):
|
|
167
|
-
AVAILABLE_MODELS = [
|
|
168
|
-
"piclumen-v1"
|
|
169
|
-
]
|
|
177
|
+
AVAILABLE_MODELS = ["piclumen-v1"]
|
|
170
178
|
|
|
171
179
|
def __init__(self):
|
|
172
180
|
self.api_endpoint = "https://s9.piclumen.art/comfy/api/generate-image"
|
|
@@ -188,10 +196,13 @@ class PiclumenAI(TTICompatibleProvider):
|
|
|
188
196
|
class _ModelList:
|
|
189
197
|
def list(inner_self):
|
|
190
198
|
return type(self).AVAILABLE_MODELS
|
|
199
|
+
|
|
191
200
|
return _ModelList()
|
|
192
201
|
|
|
202
|
+
|
|
193
203
|
if __name__ == "__main__":
|
|
194
204
|
from rich import print
|
|
205
|
+
|
|
195
206
|
client = PiclumenAI()
|
|
196
207
|
response = client.images.create(
|
|
197
208
|
model="piclumen-v1",
|
|
@@ -200,4 +211,4 @@ if __name__ == "__main__":
|
|
|
200
211
|
n=2,
|
|
201
212
|
timeout=30,
|
|
202
213
|
)
|
|
203
|
-
print(response)
|
|
214
|
+
print(response)
|
|
@@ -1,225 +1,232 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
from typing import Optional, List, Dict, Any
|
|
3
|
-
from webscout.Provider.TTI.utils import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
from
|
|
9
|
-
import
|
|
10
|
-
import
|
|
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
|
-
image_format:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
with
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
"
|
|
202
|
-
"
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
)
|
|
225
|
-
|
|
1
|
+
import requests
|
|
2
|
+
from typing import Optional, List, Dict, Any
|
|
3
|
+
from webscout.Provider.TTI.utils import (
|
|
4
|
+
ImageData,
|
|
5
|
+
ImageResponse,
|
|
6
|
+
)
|
|
7
|
+
from webscout.Provider.TTI.base import TTICompatibleProvider, BaseImages
|
|
8
|
+
from io import BytesIO
|
|
9
|
+
import os
|
|
10
|
+
import tempfile
|
|
11
|
+
from webscout.litagent import LitAgent
|
|
12
|
+
import time
|
|
13
|
+
import json
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from PIL import Image
|
|
17
|
+
except ImportError:
|
|
18
|
+
Image = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Images(BaseImages):
|
|
22
|
+
def __init__(self, client: "PixelMuse"):
|
|
23
|
+
self._client = client
|
|
24
|
+
|
|
25
|
+
def create(
|
|
26
|
+
self,
|
|
27
|
+
*,
|
|
28
|
+
model: str,
|
|
29
|
+
prompt: str,
|
|
30
|
+
n: int = 1,
|
|
31
|
+
size: str = "1024x1024",
|
|
32
|
+
response_format: str = "url",
|
|
33
|
+
user: Optional[str] = None,
|
|
34
|
+
style: str = "none",
|
|
35
|
+
aspect_ratio: str = "1:1",
|
|
36
|
+
timeout: int = 60,
|
|
37
|
+
image_format: str = "png",
|
|
38
|
+
**kwargs,
|
|
39
|
+
) -> ImageResponse:
|
|
40
|
+
"""
|
|
41
|
+
image_format: "png" or "jpeg"
|
|
42
|
+
"""
|
|
43
|
+
if Image is None:
|
|
44
|
+
raise ImportError("Pillow (PIL) is required for image format conversion.")
|
|
45
|
+
|
|
46
|
+
images = []
|
|
47
|
+
urls = []
|
|
48
|
+
|
|
49
|
+
def upload_file_with_retry(img_bytes, image_format, max_retries=3):
|
|
50
|
+
"""Upload file with retry logic using requests and tempfile"""
|
|
51
|
+
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
52
|
+
for attempt in range(max_retries):
|
|
53
|
+
tmp_path = None
|
|
54
|
+
try:
|
|
55
|
+
with tempfile.NamedTemporaryFile(
|
|
56
|
+
suffix=f".{ext}", delete=False
|
|
57
|
+
) as tmp:
|
|
58
|
+
tmp.write(img_bytes)
|
|
59
|
+
tmp.flush()
|
|
60
|
+
tmp_path = tmp.name
|
|
61
|
+
with open(tmp_path, "rb") as f:
|
|
62
|
+
files = {"fileToUpload": (f"image.{ext}", f, f"image/{ext}")}
|
|
63
|
+
data = {"reqtype": "fileupload", "json": "true"}
|
|
64
|
+
headers = {"User-Agent": LitAgent().random()}
|
|
65
|
+
if attempt > 0:
|
|
66
|
+
headers["Connection"] = "close"
|
|
67
|
+
resp = requests.post(
|
|
68
|
+
"https://catbox.moe/user/api.php",
|
|
69
|
+
files=files,
|
|
70
|
+
data=data,
|
|
71
|
+
headers=headers,
|
|
72
|
+
timeout=timeout,
|
|
73
|
+
)
|
|
74
|
+
if resp.status_code == 200 and resp.text.strip():
|
|
75
|
+
text = resp.text.strip()
|
|
76
|
+
if text.startswith("http"):
|
|
77
|
+
return text
|
|
78
|
+
try:
|
|
79
|
+
result = resp.json()
|
|
80
|
+
if "url" in result:
|
|
81
|
+
return result["url"]
|
|
82
|
+
except json.JSONDecodeError:
|
|
83
|
+
if "http" in text:
|
|
84
|
+
return text
|
|
85
|
+
except Exception:
|
|
86
|
+
if attempt < max_retries - 1:
|
|
87
|
+
time.sleep(1 * (attempt + 1))
|
|
88
|
+
finally:
|
|
89
|
+
if tmp_path and os.path.isfile(tmp_path):
|
|
90
|
+
try:
|
|
91
|
+
os.remove(tmp_path)
|
|
92
|
+
except Exception:
|
|
93
|
+
pass
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
def upload_file_alternative(img_bytes, image_format):
|
|
97
|
+
"""Alternative upload method: save to temp file and upload to 0x0.st"""
|
|
98
|
+
try:
|
|
99
|
+
ext = "jpg" if image_format.lower() == "jpeg" else "png"
|
|
100
|
+
with tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) as tmp:
|
|
101
|
+
tmp.write(img_bytes)
|
|
102
|
+
tmp.flush()
|
|
103
|
+
tmp_path = tmp.name
|
|
104
|
+
try:
|
|
105
|
+
if not os.path.isfile(tmp_path):
|
|
106
|
+
return None
|
|
107
|
+
with open(tmp_path, "rb") as img_file:
|
|
108
|
+
files = {"file": img_file}
|
|
109
|
+
response = requests.post("https://0x0.st", files=files)
|
|
110
|
+
response.raise_for_status()
|
|
111
|
+
image_url = response.text.strip()
|
|
112
|
+
if not image_url.startswith("http"):
|
|
113
|
+
return None
|
|
114
|
+
return image_url
|
|
115
|
+
except Exception:
|
|
116
|
+
return None
|
|
117
|
+
finally:
|
|
118
|
+
try:
|
|
119
|
+
os.remove(tmp_path)
|
|
120
|
+
except Exception:
|
|
121
|
+
pass
|
|
122
|
+
except Exception:
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
for _ in range(n):
|
|
126
|
+
resp = self._client.session.post(
|
|
127
|
+
self._client.api_endpoint,
|
|
128
|
+
json={
|
|
129
|
+
"prompt": prompt,
|
|
130
|
+
"model": model,
|
|
131
|
+
"style": style,
|
|
132
|
+
"aspect_ratio": aspect_ratio,
|
|
133
|
+
},
|
|
134
|
+
timeout=timeout,
|
|
135
|
+
)
|
|
136
|
+
resp.raise_for_status()
|
|
137
|
+
data = resp.json()
|
|
138
|
+
|
|
139
|
+
if "output" in data and len(data["output"]) > 0:
|
|
140
|
+
image_url = data["output"][0]
|
|
141
|
+
img_resp = self._client.session.get(image_url, timeout=timeout)
|
|
142
|
+
img_resp.raise_for_status()
|
|
143
|
+
webp_bytes = img_resp.content
|
|
144
|
+
|
|
145
|
+
# Convert webp to png or jpeg in memory
|
|
146
|
+
with BytesIO(webp_bytes) as input_io:
|
|
147
|
+
with Image.open(input_io) as im:
|
|
148
|
+
out_io = BytesIO()
|
|
149
|
+
if image_format.lower() == "jpeg":
|
|
150
|
+
im = im.convert("RGB")
|
|
151
|
+
im.save(out_io, format="JPEG")
|
|
152
|
+
else:
|
|
153
|
+
im.save(out_io, format="PNG")
|
|
154
|
+
img_bytes = out_io.getvalue()
|
|
155
|
+
|
|
156
|
+
images.append(img_bytes)
|
|
157
|
+
|
|
158
|
+
if response_format == "url":
|
|
159
|
+
# Try primary upload method with retries
|
|
160
|
+
uploaded_url = upload_file_with_retry(img_bytes, image_format)
|
|
161
|
+
|
|
162
|
+
# If primary method fails, try alternative
|
|
163
|
+
if not uploaded_url:
|
|
164
|
+
uploaded_url = upload_file_alternative(img_bytes, image_format)
|
|
165
|
+
|
|
166
|
+
if uploaded_url:
|
|
167
|
+
urls.append(uploaded_url)
|
|
168
|
+
else:
|
|
169
|
+
raise RuntimeError(
|
|
170
|
+
"Failed to upload image to catbox.moe using all available methods"
|
|
171
|
+
)
|
|
172
|
+
else:
|
|
173
|
+
raise RuntimeError("No image data received from PixelMuse")
|
|
174
|
+
|
|
175
|
+
result_data = []
|
|
176
|
+
if response_format == "url":
|
|
177
|
+
for url in urls:
|
|
178
|
+
result_data.append(ImageData(url=url))
|
|
179
|
+
elif response_format == "b64_json":
|
|
180
|
+
import base64
|
|
181
|
+
|
|
182
|
+
for img in images:
|
|
183
|
+
b64 = base64.b64encode(img).decode("utf-8")
|
|
184
|
+
result_data.append(ImageData(b64_json=b64))
|
|
185
|
+
else:
|
|
186
|
+
raise ValueError("response_format must be 'url' or 'b64_json'")
|
|
187
|
+
|
|
188
|
+
from time import time as _time
|
|
189
|
+
|
|
190
|
+
return ImageResponse(created=int(_time()), data=result_data)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class PixelMuse(TTICompatibleProvider):
|
|
194
|
+
AVAILABLE_MODELS = ["flux-schnell", "imagen-3-fast", "imagen-3", "recraft-v3"]
|
|
195
|
+
|
|
196
|
+
def __init__(self):
|
|
197
|
+
self.api_endpoint = "https://www.pixelmuse.studio/api/predictions"
|
|
198
|
+
self.session = requests.Session()
|
|
199
|
+
self.user_agent = LitAgent().random()
|
|
200
|
+
self.headers = {
|
|
201
|
+
"accept": "*/*",
|
|
202
|
+
"accept-language": "en-US,en;q=0.9",
|
|
203
|
+
"content-type": "application/json",
|
|
204
|
+
"origin": "https://www.pixelmuse.studio",
|
|
205
|
+
"referer": "https://www.pixelmuse.studio/",
|
|
206
|
+
"user-agent": self.user_agent,
|
|
207
|
+
}
|
|
208
|
+
self.session.headers.update(self.headers)
|
|
209
|
+
self.images = Images(self)
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def models(self):
|
|
213
|
+
class _ModelList:
|
|
214
|
+
def list(inner_self):
|
|
215
|
+
return type(self).AVAILABLE_MODELS
|
|
216
|
+
|
|
217
|
+
return _ModelList()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# Example usage:
|
|
221
|
+
if __name__ == "__main__":
|
|
222
|
+
from rich import print
|
|
223
|
+
|
|
224
|
+
client = PixelMuse()
|
|
225
|
+
response = client.images.create(
|
|
226
|
+
model="flux-schnell",
|
|
227
|
+
prompt="a white siamese cat",
|
|
228
|
+
response_format="url",
|
|
229
|
+
n=4,
|
|
230
|
+
timeout=30,
|
|
231
|
+
)
|
|
232
|
+
print(response)
|