返回提交历史
Modified
g4f/Provider/PollinationsAI.py
+1
-1
Modified
g4f/Provider/hf/HuggingFaceInference.py
+10
-7
Modified
g4f/Provider/hf/HuggingFaceMedia.py
+1
-1
Modified
g4f/gui/client/background.html
+21
-2
Modified
g4f/gui/server/api.py
+1
-1
Modified
g4f/gui/server/backend_api.py
+4
-6
Modified
g4f/image/copy_images.py
+22
-20
XFEstudio/gpt4free
Add video feed to background site
d17305ab
代码差异
7 个文件
+60
-38
@@ -308,7 +308,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
308
308
})
309
309
async with session.post(url, json=data) as response:
310
310
await raise_for_status(response)
311
async for chunk in save_response_media(response, messages[-1]["content"]):
311
async for chunk in save_response_media(response, messages[-1]["content"], [model]):
312
312
yield chunk
313
313
return
314
314
if response.headers["content-type"].startswith("text/plain"):
@@ -94,6 +94,11 @@ class HuggingFaceInference(AsyncGeneratorProvider, ProviderModelMixin):
94
94
}
95
95
if api_key is not None:
96
96
headers["Authorization"] = f"Bearer {api_key}"
97
image_extra_data = use_aspect_ratio({
98
"width": width,
99
"height": height,
100
**extra_data
101
}, aspect_ratio)
97
102
async with StreamSession(
98
103
headers=headers,
99
104
proxy=proxy,
@@ -101,14 +106,12 @@ class HuggingFaceInference(AsyncGeneratorProvider, ProviderModelMixin):
101
106
) as session:
102
107
try:
103
108
if model in provider_together_urls:
104
data = use_aspect_ratio({
109
data = {
105
110
"response_format": "url",
106
111
"prompt": format_image_prompt(messages, prompt),
107
112
"model": model,
108
"width": width,
109
"height": height,
110
**extra_data
111
}, aspect_ratio)
113
**image_extra_data
114
}
112
115
async with session.post(provider_together_urls[model], json=data) as response:
113
116
if response.status == 404:
114
117
raise ModelNotSupportedError(f"Model is not supported: {model}")
@@ -132,7 +135,7 @@ class HuggingFaceInference(AsyncGeneratorProvider, ProviderModelMixin):
132
135
if pipeline_tag == "text-to-image":
133
136
stream = False
134
137
inputs = format_image_prompt(messages, prompt)
135
payload = {"inputs": inputs, "parameters": {"seed": random.randint(0, 2**32) if seed is None else seed, **extra_data}}
138
payload = {"inputs": inputs, "parameters": {"seed": random.randint(0, 2**32) if seed is None else seed, **image_extra_data}}
136
139
elif pipeline_tag in ("text-generation", "image-text-to-text"):
137
140
model_type = None
138
141
if "config" in model_data and "model_type" in model_data["config"]:
@@ -179,7 +182,7 @@ class HuggingFaceInference(AsyncGeneratorProvider, ProviderModelMixin):
179
182
debug.log(f"Special token: {is_special}")
180
183
yield FinishReason("stop" if is_special else "length")
181
184
else:
182
async for chunk in save_response_media(response, prompt):
185
async for chunk in save_response_media(response, inputs, [aspect_ratio, model]):
183
186
yield chunk
184
187
return
185
188
yield (await response.json())[0]["generated_text"].strip()
@@ -189,7 +189,7 @@ class HuggingFaceMedia(AsyncGeneratorProvider, ProviderModelMixin):
189
189
if response.status == 404:
190
190
raise ModelNotSupportedError(f"Model is not supported: {model}")
191
191
await raise_for_status(response)
192
async for chunk in save_response_media(response, prompt):
192
async for chunk in save_response_media(response, prompt, [aspect_ratio, model]):
193
193
return provider_info, chunk
194
194
result = await response.json()
195
195
if "video" in result:
@@ -83,7 +83,7 @@
83
83
display: none;
84
84
}
85
85
86
#background, #image-feed {
86
#background, #image-feed, #video-feed {
87
87
height: 100%;
88
88
position: absolute;
89
89
z-index: -1;
@@ -97,6 +97,7 @@
97
97
</head>
98
98
<body>
99
99
<img id="image-feed" class="hidden" alt="Image Feed">
100
<video id="video-feed" class="hidden" alt="Video Feed" src="/search/video" autoplay></video>
100
101
101
102
<!-- Gradient Background Circle -->
102
103
<div class="gradient"></div></div>
@@ -104,9 +105,24 @@
104
105
(async () => {
105
106
const url = "https://image.pollinations.ai/feed";
106
107
const imageFeed = document.getElementById("image-feed");
108
const videoFeed = document.getElementById("video-feed");
107
109
const gradient = document.querySelector(".gradient");
108
110
const images = []
109
111
let es = null;
112
let skipVideo = 1;
113
let errorVideo = false;
114
videoFeed.onloadeddata = () => {
115
videoFeed.classList.remove("hidden");
116
gradient.classList.add("hidden");
117
};
118
videoFeed.onerror = () => {
119
videoFeed.classList.add("hidden");
120
errorVideo = true;
121
};
122
videoFeed.onended = () => {
123
videoFeed.src = "/search/video?skip=" + skipVideo;
124
skipVideo++;
125
};
110
126
function initES() {
111
127
if (es == null || es.readyState == EventSource.CLOSED) {
112
128
const eventSource = new EventSource(url);
@@ -116,7 +132,7 @@
116
132
return;
117
133
}
118
134
const lower = data.prompt.toLowerCase();
119
const tags = ["nsfw", "timeline", "feet", "blood", "soap", "orally", "heel", "latex", "bathroom", "boobs", "charts", "gel", "logo", "infographic", "warts", " bra ", "prostitute", "curvy", "breasts", "written", "bodies", "naked", "classroom", "malone", "dirty", "shoes", "shower", "banner", "fat", "nipples", "couple", "sexual", "sandal", "supplier", "overlord", "succubus", "platinum", "cracy", "crazy", "hemale", "oprah", "lamic", "ropes", "cables", "wires", "dirty", "messy", "cluttered", "chaotic", "disorganized", "disorderly", "untidy", "unorganized", "unorderly", "unsystematic", "disarranged", "disarrayed", "disheveled", "disordered", "jumbled", "muddled", "scattered", "shambolic", "sloppy", "unkept", "unruly"];
135
const tags = ["nsfw", "timeline", "feet", "blood", "soap", "orally", "heel", "latex", "bathroom", "boobs", "charts", "gel", "logo", "infographic", "warts", " bra ", "prostitute", "curvy", "breasts", "written", "bodies", "naked", "classroom", "malone", "dirty", "shoes", "shower", "banner", "fat", "nipples", "couple", "sexual", "sandal", "supplier", "overlord", "succubus", "platinum", "cracy", "crazy", "hemale", "oprah", "lamic", "ropes", "cables", "wires", "dirty", "messy", "cluttered", "chaotic", "disorganized", "disorderly", "untidy", "unorganized", "unorderly", "unsystematic", "disarranged", "disarrayed", "disheveled", "disordered", "jumbled", "muddled", "scattered", "shambolic", "sloppy", "unkept", "unruly", "bottomless", "18 year"];
120
136
for (i in tags) {
121
137
if (lower.indexOf(tags[i]) != -1) {
122
138
console.log("Skipping image with tag: " + tags[i]);
@@ -138,6 +154,9 @@
138
154
}
139
155
initES();
140
156
setInterval(() => {
157
if (!errorVideo) {
158
return;
159
}
141
160
if (images.length > 0) {
142
161
imageFeed.classList.remove("hidden");
143
162
imageFeed.src = images.shift();
@@ -187,7 +187,7 @@ class Api:
187
187
media = chunk
188
188
if download_media or chunk.get("cookies"):
189
189
chunk.alt = format_image_prompt(kwargs.get("messages"), chunk.alt)
190
tags = [tag for tag in [model, kwargs.get("aspect_ratio")] if tag]
190
tags = [model, kwargs.get("aspect_ratio")]
191
191
media = asyncio.run(copy_media(chunk.get_list(), chunk.get("cookies"), chunk.get("headers"), proxy=proxy, alt=chunk.alt, tags=tags))
192
192
media = ImageResponse(media, chunk.alt) if isinstance(chunk, ImageResponse) else VideoResponse(media, chunk.alt)
193
193
yield self._format_json("content", str(media), images=chunk.get_list(), alt=chunk.alt)
@@ -351,10 +351,8 @@ class Backend_Api(Api):
351
351
raise
352
352
353
353
@app.route('/search/<search>', methods=['GET'])
354
def find_media(search: str, min: int = None):
354
def find_media(search: str):
355
355
search = [secure_filename(chunk.lower()) for chunk in search.split("+")]
356
if min is None:
357
min = len(search)
358
356
if not os.access(images_dir, os.R_OK):
359
357
return jsonify({"error": {"message": "Not found"}}), 404
360
358
match_files = {}
@@ -370,10 +368,10 @@ class Backend_Api(Api):
370
368
for tag in search:
371
369
if tag in file.lower():
372
370
match_files[file] = match_files.get(file, 0) + 1
373
match_files = [file for file, count in match_files.items() if count >= min]
374
if not match_files:
371
match_files = [file for file, count in match_files.items() if count >= request.args.get("min", len(search))]
372
if int(request.args.get("skip")) >= len(match_files):
375
373
return jsonify({"error": {"message": "Not found"}}), 404
376
return redirect(f"/media/{random.choice(match_files)}")
374
return redirect(f"/media/{match_files[int(request.args.get("skip", 0))]}"), 302
377
375
378
376
@app.route('/backend-api/v2/upload_cookies', methods=['POST'])
379
377
def upload_cookies():
@@ -6,13 +6,13 @@ import uuid
6
6
import asyncio
7
7
import hashlib
8
8
import re
9
from typing import AsyncIterator
9
10
from urllib.parse import quote, unquote
10
11
from aiohttp import ClientSession, ClientError
11
12
12
13
from ..typing import Optional, Cookies
13
14
from ..requests.aiohttp import get_connector, StreamResponse
14
15
from ..image import MEDIA_TYPE_MAP, EXTENSIONS_MAP
15
from ..tools.files import get_bucket_dir
16
16
from ..providers.response import ImageResponse, AudioResponse, VideoResponse
17
17
from ..Provider.template import BackendApi
18
18
from . import is_accepted_format, extract_data_uri
@@ -23,8 +23,11 @@ images_dir = "./generated_images"
23
23
24
24
def get_media_extension(media: str) -> str:
25
25
"""Extract media file extension from URL or filename"""
26
match = re.search(r"\.(jpe?g|png|gif|svg|webp|webm|mp4|mp3|wav|flac|opus|ogg|mkv)(?:\?|$)", media, re.IGNORECASE)
27
return f".{match.group(1).lower()}" if match else ""
26
match = re.search(r"\.(j?[a-z]{3})(?:\?|$)", media, re.IGNORECASE)
27
extension = match.group(1).lower() if match else ""
28
if extension not in EXTENSIONS_MAP:
29
raise ValueError(f"Unsupported media extension: {extension}")
30
return f".{extension}"
28
31
29
32
def ensure_images_dir():
30
33
"""Create images directory if it doesn't exist"""
@@ -54,23 +57,19 @@ def secure_filename(filename: str) -> str:
54
57
def is_valid_media_type(content_type: str) -> bool:
55
58
return content_type in MEDIA_TYPE_MAP or content_type.startswith("audio/") or content_type.startswith("video/")
56
59
57
async def save_response_media(response: StreamResponse, prompt: str):
60
async def save_response_media(response: StreamResponse, prompt: str, tags: list[str]) -> AsyncIterator:
61
"""Save media from response to local file and return URL"""
58
62
content_type = response.headers["content-type"]
59
63
if is_valid_media_type(content_type):
60
64
extension = MEDIA_TYPE_MAP[content_type] if content_type in MEDIA_TYPE_MAP else content_type[6:].replace("mpeg", "mp3")
61
65
if extension not in EXTENSIONS_MAP:
62
66
raise ValueError(f"Unsupported media type: {content_type}")
63
bucket_id = str(uuid.uuid4())
64
dirname = str(int(time.time()))
65
bucket_dir = get_bucket_dir(bucket_id, dirname)
66
media_dir = os.path.join(bucket_dir, "media")
67
os.makedirs(media_dir, exist_ok=True)
68
filename = secure_filename(f"{content_type[0:5] if prompt is None else prompt}.{extension}")
69
newfile = os.path.join(media_dir, filename)
70
with open(newfile, 'wb') as f:
67
filename = get_filename(tags, prompt, f".{extension}", prompt)
68
target_path = os.path.join(images_dir, filename)
69
with open(target_path, 'wb') as f:
71
70
async for chunk in response.iter_content() if hasattr(response, "iter_content") else response.content.iter_any():
72
71
f.write(chunk)
73
media_url = f"/files/{dirname}/{bucket_id}/media/{filename}"
72
media_url = f"/media/{filename}"
74
73
if response.method == "GET":
75
74
media_url = f"{media_url}?url={str(response.url)}"
76
75
if content_type.startswith("audio/"):
@@ -79,6 +78,15 @@ async def save_response_media(response: StreamResponse, prompt: str):
79
78
yield VideoResponse(media_url, prompt)
80
79
else:
81
80
yield ImageResponse(media_url, prompt)
81
82
def get_filename(tags: list[str], alt: str, extension: str, image: str) -> str:
83
return secure_filename("".join((
84
f"{int(time.time())}_",
85
(f"{'_'.join([tag for tag in tags if tag])}_" if tags else ""),
86
(f"{alt}_" if alt else ""),
87
f"{hashlib.sha256(image.encode()).hexdigest()[:16]}",
88
f"{extension}"
89
)))
82
90
83
91
async def copy_media(
84
92
images: list[str],
@@ -112,13 +120,7 @@ async def copy_media(
112
120
target_path = target
113
121
if target_path is None:
114
122
# Build safe filename with full Unicode support
115
filename = secure_filename("".join((
116
f"{int(time.time())}_",
117
(f"{''.join(tags, '_')}_" if tags else ""),
118
(f"{alt}_" if alt else ""),
119
f"{hashlib.sha256(image.encode()).hexdigest()[:16]}",
120
f"{get_media_extension(image)}"
121
)))
123
filename = get_filename(tags, alt, get_media_extension(image), image)
122
124
target_path = os.path.join(images_dir, filename)
123
125
try:
124
126
# Handle different image types