XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

XFEstudio/gpt4free

Add video feed to background site

d17305ab
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

7 个文件 +60 -38
Modified g4f/Provider/PollinationsAI.py +1 -1
@@ -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"):
Modified g4f/Provider/hf/HuggingFaceInference.py +10 -7
@@ -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()
Modified g4f/Provider/hf/HuggingFaceMedia.py +1 -1
@@ -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:
Modified g4f/gui/client/background.html +21 -2
@@ -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();
Modified g4f/gui/server/api.py +1 -1
@@ -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)
Modified g4f/gui/server/backend_api.py +4 -6
@@ -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():
Modified g4f/image/copy_images.py +22 -20
@@ -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