2022-09-09 17:04:32 +02:00
|
|
|
import json
|
2022-09-02 10:28:36 +02:00
|
|
|
import traceback
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
|
2022-10-06 10:58:02 +02:00
|
|
|
SD_DIR = os.getcwd()
|
|
|
|
print('started in ', SD_DIR)
|
2022-09-02 10:28:36 +02:00
|
|
|
|
|
|
|
SD_UI_DIR = os.getenv('SD_UI_PATH', None)
|
|
|
|
sys.path.append(os.path.dirname(SD_UI_DIR))
|
|
|
|
|
2022-10-06 10:58:02 +02:00
|
|
|
CONFIG_DIR = os.path.abspath(os.path.join(SD_UI_DIR, '..', 'scripts'))
|
|
|
|
MODELS_DIR = os.path.abspath(os.path.join(SD_DIR, '..', 'models'))
|
2022-09-09 17:04:32 +02:00
|
|
|
|
2022-09-02 10:28:36 +02:00
|
|
|
OUTPUT_DIRNAME = "Stable Diffusion UI" # in the user's home folder
|
|
|
|
|
|
|
|
from fastapi import FastAPI, HTTPException
|
2022-09-22 17:01:30 +02:00
|
|
|
from fastapi.staticfiles import StaticFiles
|
2022-09-14 13:22:03 +02:00
|
|
|
from starlette.responses import FileResponse, StreamingResponse
|
2022-09-02 10:28:36 +02:00
|
|
|
from pydantic import BaseModel
|
2022-09-09 17:35:24 +02:00
|
|
|
import logging
|
2022-09-02 10:28:36 +02:00
|
|
|
|
|
|
|
from sd_internal import Request, Response
|
|
|
|
|
|
|
|
app = FastAPI()
|
|
|
|
|
|
|
|
model_loaded = False
|
|
|
|
model_is_loading = False
|
|
|
|
|
|
|
|
modifiers_cache = None
|
|
|
|
outpath = os.path.join(os.path.expanduser("~"), OUTPUT_DIRNAME)
|
|
|
|
|
2022-09-24 12:25:45 +02:00
|
|
|
# don't show access log entries for URLs that start with the given prefix
|
|
|
|
ACCESS_LOG_SUPPRESS_PATH_PREFIXES = ['/ping', '/modifier-thumbnails']
|
|
|
|
|
|
|
|
app.mount('/media', StaticFiles(directory=os.path.join(SD_UI_DIR, 'media/')), name="media")
|
|
|
|
|
2022-09-02 10:28:36 +02:00
|
|
|
# defaults from https://huggingface.co/blog/stable_diffusion
|
|
|
|
class ImageRequest(BaseModel):
|
2022-09-14 18:59:42 +02:00
|
|
|
session_id: str = "session"
|
2022-09-02 10:28:36 +02:00
|
|
|
prompt: str = ""
|
2022-09-27 15:39:28 +02:00
|
|
|
negative_prompt: str = ""
|
2022-09-02 10:28:36 +02:00
|
|
|
init_image: str = None # base64
|
|
|
|
mask: str = None # base64
|
|
|
|
num_outputs: int = 1
|
|
|
|
num_inference_steps: int = 50
|
|
|
|
guidance_scale: float = 7.5
|
|
|
|
width: int = 512
|
|
|
|
height: int = 512
|
|
|
|
seed: int = 42
|
|
|
|
prompt_strength: float = 0.8
|
2022-09-22 20:49:05 +02:00
|
|
|
sampler: str = None # "ddim", "plms", "heun", "euler", "euler_a", "dpm2", "dpm2_a", "lms"
|
2022-09-02 10:28:36 +02:00
|
|
|
# allow_nsfw: bool = False
|
2022-09-05 13:55:25 +02:00
|
|
|
save_to_disk_path: str = None
|
2022-09-02 10:28:36 +02:00
|
|
|
turbo: bool = True
|
|
|
|
use_cpu: bool = False
|
|
|
|
use_full_precision: bool = False
|
2022-09-09 17:35:24 +02:00
|
|
|
use_face_correction: str = None # or "GFPGANv1.3"
|
|
|
|
use_upscale: str = None # or "RealESRGAN_x4plus" or "RealESRGAN_x4plus_anime_6B"
|
2022-10-06 10:58:02 +02:00
|
|
|
use_stable_diffusion_model: str = "sd-v1-4"
|
2022-09-09 17:35:24 +02:00
|
|
|
show_only_filtered_image: bool = False
|
2022-10-06 11:35:34 +02:00
|
|
|
output_format: str = "jpeg" # or "png"
|
2022-09-02 10:28:36 +02:00
|
|
|
|
2022-09-14 13:22:03 +02:00
|
|
|
stream_progress_updates: bool = False
|
2022-09-14 18:59:42 +02:00
|
|
|
stream_image_progress: bool = False
|
2022-09-14 13:22:03 +02:00
|
|
|
|
2022-09-09 17:04:32 +02:00
|
|
|
class SetAppConfigRequest(BaseModel):
|
|
|
|
update_branch: str = "main"
|
|
|
|
|
2022-09-02 10:28:36 +02:00
|
|
|
@app.get('/')
|
|
|
|
def read_root():
|
2022-09-09 18:24:13 +02:00
|
|
|
headers = {"Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0"}
|
|
|
|
return FileResponse(os.path.join(SD_UI_DIR, 'index.html'), headers=headers)
|
2022-09-02 10:28:36 +02:00
|
|
|
|
|
|
|
@app.get('/ping')
|
|
|
|
async def ping():
|
|
|
|
global model_loaded, model_is_loading
|
|
|
|
|
|
|
|
try:
|
|
|
|
if model_loaded:
|
|
|
|
return {'OK'}
|
|
|
|
|
|
|
|
if model_is_loading:
|
|
|
|
return {'ERROR'}
|
|
|
|
|
|
|
|
model_is_loading = True
|
|
|
|
|
|
|
|
from sd_internal import runtime
|
2022-09-28 11:40:05 +02:00
|
|
|
|
2022-10-06 10:58:02 +02:00
|
|
|
runtime.load_model_ckpt(ckpt_to_use=get_initial_model_to_load())
|
2022-09-02 10:28:36 +02:00
|
|
|
|
|
|
|
model_loaded = True
|
|
|
|
model_is_loading = False
|
|
|
|
|
|
|
|
return {'OK'}
|
|
|
|
except Exception as e:
|
|
|
|
print(traceback.format_exc())
|
|
|
|
return HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
2022-10-06 10:58:02 +02:00
|
|
|
# needs to support the legacy installations
|
|
|
|
def get_initial_model_to_load():
|
|
|
|
custom_weight_path = os.path.join(SD_DIR, 'custom-model.ckpt')
|
|
|
|
ckpt_to_use = "sd-v1-4" if not os.path.exists(custom_weight_path) else "custom-model"
|
|
|
|
|
|
|
|
ckpt_to_use = os.path.join(SD_DIR, ckpt_to_use)
|
|
|
|
|
|
|
|
config = getConfig()
|
|
|
|
if 'model' in config and 'stable-diffusion' in config['model']:
|
|
|
|
model_name = config['model']['stable-diffusion']
|
|
|
|
model_path = resolve_model_to_use(model_name)
|
|
|
|
|
|
|
|
if os.path.exists(model_path + '.ckpt'):
|
|
|
|
ckpt_to_use = model_path
|
|
|
|
else:
|
|
|
|
print('Could not find the configured custom model at:', model_path + '.ckpt', '. Using the default one:', ckpt_to_use + '.ckpt')
|
|
|
|
|
|
|
|
return ckpt_to_use
|
|
|
|
|
|
|
|
def resolve_model_to_use(model_name):
|
|
|
|
if model_name in ('sd-v1-4', 'custom-model'):
|
|
|
|
model_path = os.path.join(MODELS_DIR, 'stable-diffusion', model_name)
|
|
|
|
|
|
|
|
legacy_model_path = os.path.join(SD_DIR, model_name)
|
|
|
|
if not os.path.exists(model_path + '.ckpt') and os.path.exists(legacy_model_path + '.ckpt'):
|
|
|
|
model_path = legacy_model_path
|
|
|
|
else:
|
|
|
|
model_path = os.path.join(MODELS_DIR, 'stable-diffusion', model_name)
|
|
|
|
|
|
|
|
return model_path
|
|
|
|
|
|
|
|
def save_model_to_config(model_name):
|
|
|
|
config = getConfig()
|
|
|
|
if 'model' not in config:
|
|
|
|
config['model'] = {}
|
|
|
|
|
|
|
|
config['model']['stable-diffusion'] = model_name
|
|
|
|
|
|
|
|
setConfig(config)
|
|
|
|
|
2022-09-02 10:28:36 +02:00
|
|
|
@app.post('/image')
|
2022-09-13 16:29:41 +02:00
|
|
|
def image(req : ImageRequest):
|
2022-09-02 10:28:36 +02:00
|
|
|
from sd_internal import runtime
|
|
|
|
|
|
|
|
r = Request()
|
2022-09-14 18:59:42 +02:00
|
|
|
r.session_id = req.session_id
|
2022-09-02 10:28:36 +02:00
|
|
|
r.prompt = req.prompt
|
2022-09-27 15:39:28 +02:00
|
|
|
r.negative_prompt = req.negative_prompt
|
2022-09-02 10:28:36 +02:00
|
|
|
r.init_image = req.init_image
|
|
|
|
r.mask = req.mask
|
|
|
|
r.num_outputs = req.num_outputs
|
|
|
|
r.num_inference_steps = req.num_inference_steps
|
|
|
|
r.guidance_scale = req.guidance_scale
|
|
|
|
r.width = req.width
|
|
|
|
r.height = req.height
|
|
|
|
r.seed = req.seed
|
|
|
|
r.prompt_strength = req.prompt_strength
|
2022-09-22 20:49:05 +02:00
|
|
|
r.sampler = req.sampler
|
2022-09-02 10:28:36 +02:00
|
|
|
# r.allow_nsfw = req.allow_nsfw
|
|
|
|
r.turbo = req.turbo
|
|
|
|
r.use_cpu = req.use_cpu
|
|
|
|
r.use_full_precision = req.use_full_precision
|
2022-09-05 13:55:25 +02:00
|
|
|
r.save_to_disk_path = req.save_to_disk_path
|
2022-09-09 17:35:24 +02:00
|
|
|
r.use_upscale: str = req.use_upscale
|
|
|
|
r.use_face_correction = req.use_face_correction
|
|
|
|
r.show_only_filtered_image = req.show_only_filtered_image
|
2022-10-06 11:35:34 +02:00
|
|
|
r.output_format = req.output_format
|
2022-09-02 10:28:36 +02:00
|
|
|
|
2022-09-22 19:25:57 +02:00
|
|
|
r.stream_progress_updates = True # the underlying implementation only supports streaming
|
2022-09-14 18:59:42 +02:00
|
|
|
r.stream_image_progress = req.stream_image_progress
|
2022-09-14 13:22:03 +02:00
|
|
|
|
2022-10-06 10:58:02 +02:00
|
|
|
r.use_stable_diffusion_model = resolve_model_to_use(req.use_stable_diffusion_model)
|
|
|
|
|
|
|
|
save_model_to_config(req.use_stable_diffusion_model)
|
|
|
|
|
2022-09-02 10:28:36 +02:00
|
|
|
try:
|
2022-09-22 19:25:57 +02:00
|
|
|
if not req.stream_progress_updates:
|
|
|
|
r.stream_image_progress = False
|
|
|
|
|
2022-09-14 13:22:03 +02:00
|
|
|
res = runtime.mk_img(r)
|
2022-09-02 10:28:36 +02:00
|
|
|
|
2022-09-22 19:25:57 +02:00
|
|
|
if req.stream_progress_updates:
|
2022-09-14 13:22:03 +02:00
|
|
|
return StreamingResponse(res, media_type='application/json')
|
2022-09-22 19:25:57 +02:00
|
|
|
else: # compatibility mode: buffer the streaming responses, and return the last one
|
|
|
|
last_result = None
|
|
|
|
|
|
|
|
for result in res:
|
|
|
|
last_result = result
|
2022-09-02 10:28:36 +02:00
|
|
|
|
2022-09-22 19:25:57 +02:00
|
|
|
return json.loads(last_result)
|
2022-09-02 10:28:36 +02:00
|
|
|
except Exception as e:
|
|
|
|
print(traceback.format_exc())
|
|
|
|
return HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
2022-09-13 16:29:41 +02:00
|
|
|
@app.get('/image/stop')
|
|
|
|
def stop():
|
|
|
|
try:
|
|
|
|
if model_is_loading:
|
|
|
|
return {'ERROR'}
|
|
|
|
|
|
|
|
from sd_internal import runtime
|
|
|
|
runtime.stop_processing = True
|
|
|
|
|
|
|
|
return {'OK'}
|
|
|
|
except Exception as e:
|
|
|
|
print(traceback.format_exc())
|
|
|
|
return HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
2022-09-14 18:59:42 +02:00
|
|
|
@app.get('/image/tmp/{session_id}/{img_id}')
|
|
|
|
def get_image(session_id, img_id):
|
|
|
|
from sd_internal import runtime
|
|
|
|
buf = runtime.temp_images[session_id + '/' + img_id]
|
|
|
|
buf.seek(0)
|
|
|
|
return StreamingResponse(buf, media_type='image/jpeg')
|
|
|
|
|
2022-09-09 17:04:32 +02:00
|
|
|
@app.post('/app_config')
|
|
|
|
async def setAppConfig(req : SetAppConfigRequest):
|
|
|
|
try:
|
|
|
|
config = {
|
|
|
|
'update_branch': req.update_branch
|
|
|
|
}
|
|
|
|
|
|
|
|
config_json_str = json.dumps(config)
|
|
|
|
config_bat_str = f'@set update_branch={req.update_branch}'
|
|
|
|
config_sh_str = f'export update_branch={req.update_branch}'
|
|
|
|
|
|
|
|
config_json_path = os.path.join(CONFIG_DIR, 'config.json')
|
|
|
|
config_bat_path = os.path.join(CONFIG_DIR, 'config.bat')
|
|
|
|
config_sh_path = os.path.join(CONFIG_DIR, 'config.sh')
|
|
|
|
|
|
|
|
with open(config_json_path, 'w') as f:
|
|
|
|
f.write(config_json_str)
|
|
|
|
|
|
|
|
with open(config_bat_path, 'w') as f:
|
|
|
|
f.write(config_bat_str)
|
|
|
|
|
|
|
|
with open(config_sh_path, 'w') as f:
|
|
|
|
f.write(config_sh_str)
|
|
|
|
|
|
|
|
return {'OK'}
|
|
|
|
except Exception as e:
|
|
|
|
print(traceback.format_exc())
|
|
|
|
return HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
2022-10-14 09:42:43 +02:00
|
|
|
def getConfig(default_val={}):
|
2022-09-09 17:04:32 +02:00
|
|
|
try:
|
|
|
|
config_json_path = os.path.join(CONFIG_DIR, 'config.json')
|
2022-09-09 20:59:46 +02:00
|
|
|
if not os.path.exists(config_json_path):
|
2022-10-14 09:42:43 +02:00
|
|
|
return default_val
|
2022-09-09 17:04:32 +02:00
|
|
|
with open(config_json_path, 'r') as f:
|
2022-10-06 10:58:02 +02:00
|
|
|
return json.load(f)
|
2022-09-09 17:04:32 +02:00
|
|
|
except Exception as e:
|
2022-10-14 09:42:43 +02:00
|
|
|
print(str(e))
|
2022-09-09 17:04:32 +02:00
|
|
|
print(traceback.format_exc())
|
2022-10-14 09:42:43 +02:00
|
|
|
return default_val
|
2022-10-06 10:58:02 +02:00
|
|
|
|
|
|
|
def setConfig(config):
|
|
|
|
try:
|
|
|
|
config_json_path = os.path.join(CONFIG_DIR, 'config.json')
|
|
|
|
|
|
|
|
with open(config_json_path, 'w') as f:
|
|
|
|
return json.dump(config, f)
|
|
|
|
except:
|
2022-10-14 09:42:43 +02:00
|
|
|
print(str(e))
|
2022-10-06 10:58:02 +02:00
|
|
|
print(traceback.format_exc())
|
|
|
|
|
|
|
|
def getModels():
|
|
|
|
models = {
|
|
|
|
'active': {
|
|
|
|
'stable-diffusion': 'sd-v1-4',
|
|
|
|
},
|
|
|
|
'options': {
|
|
|
|
'stable-diffusion': ['sd-v1-4'],
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
# custom models
|
|
|
|
sd_models_dir = os.path.join(MODELS_DIR, 'stable-diffusion')
|
|
|
|
for file in os.listdir(sd_models_dir):
|
|
|
|
if file.endswith('.ckpt'):
|
|
|
|
model_name = os.path.splitext(file)[0]
|
|
|
|
models['options']['stable-diffusion'].append(model_name)
|
|
|
|
|
|
|
|
# legacy
|
|
|
|
custom_weight_path = os.path.join(SD_DIR, 'custom-model.ckpt')
|
|
|
|
if os.path.exists(custom_weight_path):
|
|
|
|
models['active']['stable-diffusion'] = 'custom-model'
|
|
|
|
models['options']['stable-diffusion'].append('custom-model')
|
|
|
|
|
|
|
|
config = getConfig()
|
|
|
|
if 'model' in config and 'stable-diffusion' in config['model']:
|
|
|
|
models['active']['stable-diffusion'] = config['model']['stable-diffusion']
|
|
|
|
|
|
|
|
return models
|
|
|
|
|
2022-10-14 09:42:43 +02:00
|
|
|
@app.get('/get')
|
|
|
|
def read_web_data(key:str=None):
|
|
|
|
if key is None: # /get without parameters, stable-diffusion easter egg.
|
|
|
|
return HTTPException(status_code=418, detail="StableDiffusion is drawing a teapot!") # HTTP418 I'm a teapot
|
|
|
|
elif key == 'app_config':
|
|
|
|
config = getConfig(default_val=None)
|
|
|
|
if config is None:
|
|
|
|
return HTTPException(status_code=500, detail="Config file is missing or unreadable")
|
|
|
|
return config
|
|
|
|
elif key == 'models':
|
|
|
|
return getModels()
|
|
|
|
elif key == 'modifiers': return FileResponse(os.path.join(SD_UI_DIR, 'modifiers.json'), headers=NOCACHE_HEADERS)
|
|
|
|
elif key == 'output_dir': return {outpath}
|
|
|
|
else:
|
|
|
|
return HTTPException(status_code=404, detail=f'Request for unknown {key}') # HTTP404 Not Found
|
2022-09-02 10:28:36 +02:00
|
|
|
|
2022-09-24 12:25:45 +02:00
|
|
|
# don't log certain requests
|
|
|
|
class LogSuppressFilter(logging.Filter):
|
2022-09-09 17:35:24 +02:00
|
|
|
def filter(self, record: logging.LogRecord) -> bool:
|
2022-09-24 12:25:45 +02:00
|
|
|
path = record.getMessage()
|
|
|
|
for prefix in ACCESS_LOG_SUPPRESS_PATH_PREFIXES:
|
|
|
|
if path.find(prefix) != -1:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
2022-09-09 17:35:24 +02:00
|
|
|
|
2022-09-24 12:25:45 +02:00
|
|
|
logging.getLogger('uvicorn.access').addFilter(LogSuppressFilter())
|
2022-09-09 17:35:24 +02:00
|
|
|
|
2022-09-02 10:28:36 +02:00
|
|
|
# start the browser ui
|
2022-09-22 17:01:30 +02:00
|
|
|
import webbrowser; webbrowser.open('http://localhost:9000')
|