lllyasviel / lllyasviel/stable-diffusion-webui-forge
http://localhost:7860/sdapi/v1/txt2img Stopped working for my web-wrapper
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 13k
- Forks
- 1.7k
- PR merge metrics
- No merged PRs in 30d
Description
So, a disclaimer that I've not run this for around 2-3 months. But this was all working without issue before.
But I've come back to it and updated and I'm getting errors when making the API call. However, in the interface, I can 'read generation parameters' and it works fine and is even switching to the correct model.
I assume something minor has changed in the payload (it's falling over when switching to the HR fix, but I'm buggered if I can see what.
Error.
`Moving model(s) has taken 0.33 seconds
Total progress: 86%|████████████████████████████████████████████████████████▌ | 60/70 [00:06<00:01, 9.31it/s]
ERROR: Exception in ASGI application██████████████████████████████████████▌ | 60/70 [00:06<00:00, 14.42it/s]
Traceback (most recent call last):
File "N:\ai\SDForge\webui\venv\lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 403, in run_asgi
result = await app( # type: ignore[func-returns-value]
File "N:\ai\SDForge\webui\venv\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 60, in __call__
return await self.app(scope, receive, send)
File "N:\ai\SDForge\webui\venv\lib\site-packages\fastapi\applications.py", line 1106, in __call__
await super().__call__(scope, receive, send)
File "N:\ai\SDForge\webui\venv\lib\site-packages\starlette\applications.py", line 122, in __call__
await self.middleware_stack(scope, receive, send)
File "N:\ai\SDForge\webui\venv\lib\site-packages\starlette\middleware\errors.py", line 184, in __call__
raise exc
File "N:\ai\SDForge\webui\venv\lib\site-packages\starlette\middleware\errors.py", line 162, in __call__
await self.app(scope, receive, _send)
File "N:\ai\SDForge\webui\venv\lib\site-packages\gradio\route_utils.py", line 724, in __call__
await self.app(scope, receive, send)
File "N:\ai\SDForge\webui\venv\lib\site-packages\starlette\middleware\exceptions.py", line 79, in __call__
raise exc
File "N:\ai\SDForge\webui\venv\lib\site-packages\starlette\middleware\exceptions.py", line 68, in __call__
await self.app(scope, receive, sender)
File "N:\ai\SDForge\webui\venv\lib\site-packages\fastapi\middleware\asyncexitstack.py", line 20, in __call__
raise e
File "N:\ai\SDForge\webui\venv\lib\site-packages\fastapi\middleware\asyncexitstack.py", line 17, in __call__
await self.app(scope, receive, send)
File "N:\ai\SDForge\webui\venv\lib\site-packages\starlette\routing.py", line 718, in __call__
await route.handle(scope, receive, send)
File "N:\ai\SDForge\webui\venv\lib\site-packages\starlette\routing.py", line 276, in handle
await self.app(scope, receive, send)
File "N:\ai\SDForge\webui\venv\lib\site-packages\starlette\routing.py", line 66, in app
response = await func(request)
File "N:\ai\SDForge\webui\venv\lib\site-packages\fastapi\routing.py", line 274, in app
raw_response = await run_endpoint_function(
File "N:\ai\SDForge\webui\venv\lib\site-packages\fastapi\routing.py", line 193, in run_endpoint_function
return await run_in_threadpool(dependant.call, **values)
File "N:\ai\SDForge\webui\venv\lib\site-packages\starlette\concurrency.py", line 41, in run_in_threadpool
return await anyio.to_thread.run_sync(func, *args)
File "N:\ai\SDForge\webui\venv\lib\site-packages\anyio\to_thread.py", line 33, in run_sync
return await get_asynclib().run_sync_in_worker_thread(
File "N:\ai\SDForge\webui\venv\lib\site-packages\anyio\_backends\_asyncio.py", line 877, in run_sync_in_worker_thread
return await future
File "N:\ai\SDForge\webui\venv\lib\site-packages\anyio\_backends\_asyncio.py", line 807, in run
result = context.run(func, *args)
File "N:\ai\SDForge\webui\modules\api\api.py", line 495, in text2imgapi
processed = process_images(p)
File "N:\ai\SDForge\webui\modules\processing.py", line 842, in process_images
res = process_images_inner(p)
File "N:\ai\SDForge\webui\modules\processing.py", line 990, in process_images_inner
samples_ddim = p.sample(conditioning=p.c, unconditional_conditioning=p.uc, seeds=p.seeds, subseeds=p.subseeds, subseed_strength=p.subseed_strength, prompts=p.prompts)
File "N:\ai\SDForge\webui\modules\processing.py", line 1405, in sample
if 'Use same choices' not in self.hr_additional_modules:
TypeError: argument of type 'NoneType' is not iterable`
Wrapper code (payload in bold: EDIT GIT is butchering the formatting, but the code is still all there!)
`from flask import Flask, request, jsonify, send_file, stream_with_context, Response
from urllib.parse import unquote
import os
import hashlib
import requests
import logging
import base64
import threading
import time
import re
from datetime import datetime
app = Flask(__name__)
# Define the image directory and the cleanup interval (in seconds) and size thresholds
IMAGE_DIR = "images"
CLEANUP_INTERVAL = 3600 # 1 hour
MAX_CACHE_SIZE = 1 * 1024 * 1024 * 1024 # 1 GB
DELETE_SIZE = 250 * 1024 * 1024 # 250 MB
def generate_image_thread(style, prompt, width, height, seed, image_path):
logging.info(f"Generating image: {style}, {prompt}, {width}x{height}, {seed}")
**# Generate new image using SD API
api_url = "http://localhost:7860/sdapi/v1/txt2img"
payload = {
"prompt": f"full body shot, diffused light,{prompt.replace('AW', str(get_age_weight(style)))}", # Replace AW with age weight,, ,
"negative_prompt": f"warped, stretched, out of frame, lowres, text, error, cropped, worst quality, low quality, jpeg artifacts, duplicate, mutilated, extra fingers, mutated hands, poorly drawn hands, poorly drawn face, mutation, deformed, blurry, dehydrated, bad anatomy, bad proportions, extra body parts, cloned face, disfigured, gross proportions, malformed limbs, missing arms, missing legs, extra arms, extra legs, fused fingers, too many fingers, long neck, objects through body, username, watermark, signature, mutated body parts, deformed body features, bad eyes, camera flash, camera lighting, flash-photography, glare",
"styles": [style],
"override_settings": {"sd_model_checkpoint": get_model(style),"sd_vae": get_vae(style)},
"override_settings_restore_afterwards": False,
"cfg_scale": 12,
"steps": get_steps(style),
"enable_hr": True,
"denoising_strength": 0.5,
"hr_upscaler": get_upscaler(style),
"hr_scale": 2,
"hr_sampler_name": "DPM++ 2M",
"hr_second_pass_steps": get_hr_steps(style),
"sampler_index": "DPM++ 2M",
#"restore_faces": True,
"width": int(width),
"height": int(height),
"seed": int(seed)
}
headers = {
"accept": "application/json",
"Content-Type": "application/json"
}**
try:
logging.info("Sending request to SD API...")
response = requests.post(api_url, json=payload, headers=headers, timeout=60)
response.raise_for_status()
logging.info("Decoding image data...")
response_json = response.json()
image_data = response_json["images"][0] # Assuming there's only one image
image_data = base64.b64decode(image_data)
logging.info("Saving image to file...")
with open(image_path, "wb") as f:
f.write(image_data)
except requests.exceptions.RequestException as e:
logging.error(f"Error sending request to SD API: {e}")
return False
except (KeyError, ValueError, IndexError) as e:
logging.error(f"Error decoding image data: {e}")
return False
except Exception as e:
logging.error(f"Unexpected error: {e}")
return False
return True
def get_steps(style):
models = {
"2D-Disney": 60,
"Anime": 80,
"sai-anime": 80,
"Comic": 60,
"Photo": 80,
"sai-photographic": 80,
"Film-Noir": 60,
"3D-Disney": 72,
"Pixar": 60
}
return models.get(style, 60)
def get_hr_steps(style):
models = {
"2D-Disney": 10,
"Anime": 16,
"sai-anime": 16,
"Comic": 10,
"Photo": 16,
"sai-photographic": 16,
"Film-Noir": 12,
"3D-Disney": 14,
"Pixar": 12
}
return models.get(style, 16)
def get_age_weight(style):
models = {
"2D-Disney": 1.9,
"Anime": 1.9,
"sai-anime": 1.9,
"Comic": 3,
"Photo": 3,
"sai-photographic": 3,
"Film-Noir": 3,
"3D-Disney": 3,
"Pixar": 3
}
return models.get(style, 3)
def get_model(style):
models = {
"2D-Disney": "ragnarKTHLSDXL_v10FP16.safetensors",
"Anime": "sdxlYamersAnime_stageAnima.safetensors",
"sai-anime": "sdxlYamersAnime_stageAnima.safetensors",
"Comic": "aamXLAnimeMix_v10.safetensors",
"Photo": "realDream_sdxlLightning1.safetensors",
"sai-photographic": "realDream_sdxlLightning1.safetensors",
"Film-Noir": "realDream_sdxlLightning1.safetensors",
"3D-Disney": "wildcardxXLANIMATION_wildcardxXLANIMATION.safetensors",
"Pixar": "wildcardxXLANIMATION_wildcardxXLANIMATION.safetensors"
}
return models.get(style, "creapromptLightning_creapromptUltimate.safetensors")
def get_upscaler(style):
upscalers = {
"2D-Disney": "None",
"Anime": "None",
"sai-anime": "None",
"Comic": "4x-AnimeSharp",
"Photo": "4x-UltraSharp",
"sai-photographic": "4x-UltraSharp",
"Film-Noir": "Lanczos",
"3D-Disney": "4x-UltraSharp",
"Pixar": "4x-UltraSharp"
}
return upscalers.get(style, "4x-UltraSharp")
def get_vae(style):
vaes = {
"2D-Disney": "sharpspectrumvaexl_v1.safetensors",
"Anime": "sdxl_vae.safetensors",
"sai-anime": "sdxl_vae.safetensors",
"Comic": "None",
"Photo": "None",
"sai-photographic": "None",
"Film-Noir": "None",
"3D-Disney": "None",
"Pixar": "None"
}
return vaes.get(style, "sharpspectrumvaexl_v1.safetensors")
def encode_filename(filename):
sha256_hash = hashlib.sha256(filename.encode()).hexdigest()
return sha256_hash + ".png"
def get_directory_size(directory):
total_size = 0
for dirpath, dirnames, filenames in os.walk(directory):
for f in filenames:
fp = os.path.join(dirpath, f)
total_size += os.path.getsize(fp)
return total_size
def cleanup_old_images():
logging.info("Starting cleanup of old images...")
total_size = get_directory_size(IMAGE_DIR)
if total_size > MAX_CACHE_SIZE:
logging.info(f"Total size of images directory ({total_size} bytes) exceeds the maximum cache size ({MAX_CACHE_SIZE} bytes).")
# Get a list of files sorted by modification time
files = sorted(
(os.path.join(IMAGE_DIR, f) for f in os.listdir(IMAGE_DIR)),
key=os.path.getmtime
)
size_to_free = DELETE_SIZE
size_freed = 0
for file in files:
file_size = os.path.getsize(file)
os.remove(file)
size_freed += file_size
logging.info(f"Deleted {file} ({file_size} bytes).")
if size_freed >= size_to_free:
break
logging.info(f"Freed {size_freed} bytes by deleting old images.")
else:
logging.info(f"Total size of images directory ({total_size} bytes) is within the maximum cache size ({MAX_CACHE_SIZE} bytes). No cleanup needed.")
logging.info("Cleanup of old images completed.")
def start_cleanup_thread():
def run_cleanup():
while True:
cleanup_old_images()
time.sleep(CLEANUP_INTERVAL)
cleanup_thread = threading.Thread(target=run_cleanup)
cleanup_thread.daemon = True
cleanup_thread.start()
@app.route('/p//')
def generate_image(prompt):
# prompt = re.sub(r'[^\w\s-]', '', prompt).replace(' ', '_').replace('-', ' ')
width = int(request.args.get('size').split('x')[0])
height = int(request.args.get('size').split('x')[1])
seed = request.args.get('seed')
style = request.args.get('style')
if not all([style, width, height, seed]):
return jsonify({"error": "Missing required parameters"}), 400
# Validate width and height
if width > 1000 or height > 1000:
return jsonify({"error": "Width and height must be 1000 or less"}), 400
if width < 500 or height < 600:
return jsonify({"error": "Width and height too small"}), 400
logging.info(f"Received request for image generation: {style}, {prompt}, {width}x{height}, {seed}")
# Check if image already exists at URL
image_file = encode_filename(f"{style}_{prompt}_{width}x{height}_{seed}.png")
image_path = os.path.join("images", image_file)
if os.path.exists(image_path):
logging.info(f"Image already exists at {image_path}")
return send_file(image_path)
# Start a new thread to generate the image
thread = threading.Thread(target=generate_image_thread, args=(style, prompt, width, height, seed, image_path))
thread.daemon = True
thread.start()
waiting = 0
thread.join(timeout=60) # Wait for thread to finish or timeout after 1 minute
while not os.path.exists(image_path) or os.path.getsize(image_path) == 0:
logging.info("Waiting for image file to be ready...")
waiting += 5
if waiting == 60:
return jsonify({"error": "Timeout waiting to generate image"}), 500
time.sleep(5)
if os.path.exists(image_path):
def generate():
logging.info("Streaming response to client...")
with open(image_path, "rb") as f:
yield from f
return Response(stream_with_context(generate()), mimetype="image/png")
else:
return jsonify({"error": "Failed to generate image"}), 500
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
if not os.path.exists(IMAGE_DIR):
os.makedirs(IMAGE_DIR)
start_cleanup_thread()
app.run(host='0.0.0.0', debug=True)
`
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with modules/api/api.py at text2imgapi and follow the call into modules/processing.py, especially process_images_inner and sample. Compare the shown txt2img payload's high-resolution settings with the handling of hr_additional_modules, then reproduce the failure and verify that /sdapi/v1/txt2img completes successfully with HR fix enabled.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- fastapi, python
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100