diff --git a/CHANGELOG.md b/CHANGELOG.md index d7d16b358..bc3da4c0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - image2video: pia and vgen pipelines - wuerstchen v3 [pr](https://github.com/huggingface/diffusers/pull/6487) - more pipelines: +- segmoe: - control api - masking api - preprocess api @@ -277,11 +278,14 @@ As of this release, default backend is set to **diffusers** as its more feature - img2img: clip and blip interrogate - img2img: sampler selection offset - img2img: support variable aspect ratio without explicit resize + - cli: add `simple-upscale.py` script + - cli: fix cmd args parsing - api: return current image in progress api if requested - api: sanitize response object - api: cleanup error logging - api: fix api-only errors - api: fix image to base64 + - api: fix upscale - refiner: fix use of sd15 model as refiners in second pass - refiner: enable none as option in xyz grid - sampler: add sampler options info to metadata @@ -297,7 +301,6 @@ As of this release, default backend is set to **diffusers** as its more feature - reference: fix links to models and use safetensors where possible - model merge: unbalanced models where not all keys are present, thanks @AI-Casanova - better sdxl model detection - - cli: fix cmd args parsing - global crlf->lf switch - model type switch if there is loaded submodels - cleanup samplers use of compute devices, thanks @Disty0 diff --git a/cli/simple-img2img.py b/cli/simple-img2img.py index 41043fc93..679c788db 100755 --- a/cli/simple-img2img.py +++ b/cli/simple-img2img.py @@ -37,6 +37,7 @@ def post(endpoint: str, dct: dict = None): else: return req.json() + def encode(f): image = Image.open(f) if image.mode == 'RGBA': @@ -48,6 +49,7 @@ def encode(f): encoded = base64.b64encode(values).decode() return encoded + def generate(args): # pylint: disable=redefined-outer-name t0 = time.time() if args.model is not None: diff --git a/cli/simple-upscale.py b/cli/simple-upscale.py new file mode 100755 index 000000000..b5a2bb5dd --- /dev/null +++ b/cli/simple-upscale.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python +import os +import io +import time +import base64 +import logging +import argparse +import requests +import urllib3 +from PIL import Image + +sd_url = os.environ.get('SDAPI_URL', "http://127.0.0.1:7860") +sd_username = os.environ.get('SDAPI_USR', None) +sd_password = os.environ.get('SDAPI_PWD', None) + +logging.basicConfig(level = logging.INFO, format = '%(asctime)s %(levelname)s: %(message)s') +log = logging.getLogger(__name__) +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + +def auth(): + if sd_username is not None and sd_password is not None: + return requests.auth.HTTPBasicAuth(sd_username, sd_password) + return None + + +def get(endpoint: str, dct: dict = None): + req = requests.get(f'{sd_url}{endpoint}', json=dct, timeout=300, verify=False, auth=auth()) + if req.status_code != 200: + return { 'error': req.status_code, 'reason': req.reason, 'url': req.url } + else: + return req.json() + + +def post(endpoint: str, dct: dict = None): + req = requests.post(f'{sd_url}{endpoint}', json = dct, timeout=300, verify=False, auth=auth()) + if req.status_code != 200: + return { 'error': req.status_code, 'reason': req.reason, 'url': req.url } + else: + return req.json() + + +def encode(f): + image = Image.open(f) + if image.mode == 'RGBA': + image = image.convert('RGB') + log.info(f'encoding image: {image}') + with io.BytesIO() as stream: + image.save(stream, 'JPEG') + image.close() + values = stream.getvalue() + encoded = base64.b64encode(values).decode() + return encoded + + +def upscale(args): # pylint: disable=redefined-outer-name + t0 = time.time() + # options['mask'] = encode(args.mask) + upscalers = get('/sdapi/v1/upscalers') + upscalers = [u['name'] for u in upscalers] + log.info(f'upscalers: {upscalers}') + options = { + "save_images": False, + "send_images": True, + 'image': encode(args.input), + 'upscaler_1': args.upscaler, + 'resize_mode': 0, # rescale_by + 'upscaling_resize': args.scale, + + } + data = post('/sdapi/v1/extra-single-image', options) + t1 = time.time() + if 'image' in data: + b64 = data['image'].split(',',1)[0] + image = Image.open(io.BytesIO(base64.b64decode(b64))) + image.save(args.output) + log.info(f'received: image={image} file={args.output} time={t1-t0:.2f}') + else: + log.warning(f'no images received: {data}') + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description = 'simple-upscale') + parser.add_argument('--input', required=True, help='input image') + parser.add_argument('--output', required=True, help='output image') + parser.add_argument('--upscaler', required=False, default='Nearest', help='upscaler name') + parser.add_argument('--scale', required=False, default=2, help='upscaler scale') + args = parser.parse_args() + log.info(f'upscale: {args}') + upscale(args) diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index cd5923ac7..17b20bb6a 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -23,7 +23,6 @@ class NetworkOnDisk: self.filename = filename self.metadata = {} self.is_safetensors = os.path.splitext(filename)[1].lower() == ".safetensors" - if self.is_safetensors: self.metadata = sd_models.read_metadata_from_safetensors(filename) if self.metadata: diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 56499703a..4bbc947a9 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -448,8 +448,9 @@ def list_available_networks(): shared.log.warning('LoRA directory not found: path="{shared.cmd_opts.lora_dir}"') if os.path.exists(shared.cmd_opts.lyco_dir) and shared.cmd_opts.lyco_dir != shared.cmd_opts.lora_dir: directories.append(shared.cmd_opts.lyco_dir) + def add_network(filename): - if os.path.isdir(filename): + if not os.path.isfile(filename): return name = os.path.splitext(os.path.basename(filename))[0] try: diff --git a/modules/face/instantid.py b/modules/face/instantid.py index 43d220566..c6afca281 100644 --- a/modules/face/instantid.py +++ b/modules/face/instantid.py @@ -58,13 +58,13 @@ def instant_id(p: processing.StableDiffusionProcessing, app, source_image, stren shared.sd_model.load_ip_adapter_instantid(face_adapter, scale=strength) shared.sd_model.set_ip_adapter_scale(strength) if not ((shared.opts.diffusers_model_cpu_offload or shared.cmd_opts.medvram) or (shared.opts.diffusers_seq_cpu_offload or shared.cmd_opts.lowvram)): - print('HERE1') shared.sd_model.to(shared.device, devices.dtype) # move pipeline if needed, but don't touch if its under automatic managment # pipeline specific args orig_prompt_attention = shared.opts.prompt_attention shared.opts.data['prompt_attention'] = 'Fixed attention' # otherwise need to deal with class_tokens_mask p.task_args['prompt'] = p.all_prompts[0] # override all logic + p.task_args['negative_prompt'] = p.all_negative_prompts[0] p.task_args['image_embeds'] = face_emb p.task_args['image'] = face_kps p.task_args['controlnet_conditioning_scale'] = float(conditioning) diff --git a/modules/ipadapter.py b/modules/ipadapter.py index c9d8a92b6..269fc9526 100644 --- a/modules/ipadapter.py +++ b/modules/ipadapter.py @@ -7,6 +7,7 @@ TODO ipadapter items: - SD/SDXL autodetect """ +import os import time from modules import processing, shared, devices @@ -110,12 +111,12 @@ def apply(pipe, p: processing.StableDiffusionProcessing, adapter_name='None', sc pipe.load_ip_adapter(base_repo, subfolder=ip_subfolder, weight_name=adapter) pipe.set_ip_adapter_scale(scale) t1 = time.time() - shared.log.info(f'IP adapter: adapter="{adapter}" scale={scale} image={image} time={t1-t0:.2f}') + shared.log.info(f'IP adapter: adapter="{ip_subfolder}/{adapter}" scale={scale} image={image} time={t1-t0:.2f}') if isinstance(image, str): from modules.api.api import decode_base64_to_image image = decode_base64_to_image(image).convert("RGB") - p.task_args['ip_adapter_image'] = p.batch_size * [image] - p.extra_generation_params["IP Adapter"] = f'{adapter}:{scale}' + p.task_args['ip_adapter_image'] = [image] + p.extra_generation_params["IP Adapter"] = f'{os.path.splitext(adapter)[0]}:{scale}' return True diff --git a/modules/sd_models.py b/modules/sd_models.py index aa7705a63..fc1abca30 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -305,10 +305,7 @@ def scrub_dict(dict_obj, keys): def read_metadata_from_safetensors(filename): global sd_metadata # pylint: disable=global-statement if sd_metadata is None: - if not os.path.isfile(sd_metadata_file): - sd_metadata = {} - else: - sd_metadata = shared.readfile(sd_metadata_file, lock=True) + sd_metadata = shared.readfile(sd_metadata_file, lock=True) if os.path.isfile(sd_metadata_file) else {} res = sd_metadata.get(filename, None) if res is not None: return res @@ -317,48 +314,48 @@ def read_metadata_from_safetensors(filename): if shared.cmd_opts.no_metadata: return {} res = {} - try: - t0 = time.time() - with open(filename, mode="rb") as file: - metadata_len = file.read(8) - metadata_len = int.from_bytes(metadata_len, "little") - json_start = file.read(2) - if metadata_len <= 2 or json_start not in (b'{"', b"{'"): - shared.log.error(f"Not a valid safetensors file: {filename}") - json_data = json_start + file.read(metadata_len-2) - json_obj = json.loads(json_data) - for k, v in json_obj.get("__metadata__", {}).items(): - if v.startswith("data:"): - v = 'data' - if k == 'format' and v == 'pt': - continue - large = True if len(v) > 2048 else False - if large and k == 'ss_datasets': - continue - if large and k == 'workflow': - continue - if large and k == 'prompt': - continue - if large and k == 'ss_bucket_info': - continue - if v[0:1] == '{': - try: - v = json.loads(v) - if large and k == 'ss_tag_frequency': - v = { i: len(j) for i, j in v.items() } - if large and k == 'sd_merge_models': - scrub_dict(v, ['sd_merge_recipe']) - except Exception: - pass - res[k] = v - sd_metadata[filename] = res - global sd_metadata_pending # pylint: disable=global-statement - sd_metadata_pending += 1 - t1 = time.time() - global sd_metadata_timer # pylint: disable=global-statement - sd_metadata_timer += (t1 - t0) - except Exception as e: - shared.log.error(f"Error reading metadata from: {filename} {e}") + # try: + t0 = time.time() + with open(filename, mode="rb") as file: + metadata_len = file.read(8) + metadata_len = int.from_bytes(metadata_len, "little") + json_start = file.read(2) + if metadata_len <= 2 or json_start not in (b'{"', b"{'"): + shared.log.error(f"Not a valid safetensors file: {filename}") + json_data = json_start + file.read(metadata_len-2) + json_obj = json.loads(json_data) + for k, v in json_obj.get("__metadata__", {}).items(): + if v.startswith("data:"): + v = 'data' + if k == 'format' and v == 'pt': + continue + large = True if len(v) > 2048 else False + if large and k == 'ss_datasets': + continue + if large and k == 'workflow': + continue + if large and k == 'prompt': + continue + if large and k == 'ss_bucket_info': + continue + if v[0:1] == '{': + try: + v = json.loads(v) + if large and k == 'ss_tag_frequency': + v = { i: len(j) for i, j in v.items() } + if large and k == 'sd_merge_models': + scrub_dict(v, ['sd_merge_recipe']) + except Exception: + pass + res[k] = v + sd_metadata[filename] = res + global sd_metadata_pending # pylint: disable=global-statement + sd_metadata_pending += 1 + t1 = time.time() + global sd_metadata_timer # pylint: disable=global-statement + sd_metadata_timer += (t1 - t0) + # except Exception as e: + # shared.log.error(f"Error reading metadata from: {filename} {e}") return res diff --git a/scripts/postprocessing_video.py b/scripts/postprocessing_video.py index 8f5e388f5..0fbe5984f 100644 --- a/scripts/postprocessing_video.py +++ b/scripts/postprocessing_video.py @@ -41,7 +41,7 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): } def postprocess(self, images, filename, video_type, duration, loop, pad, interpolate, scale, change): # pylint: disable=arguments-differ - filename = filename.strip() + filename = filename.strip() if filename is not None else '' if video_type == 'None' or len(filename) == 0 or images is None or len(images) < 2: return modules.images.save_video(p=None, filename=filename, images=images, video_type=video_type, duration=duration, loop=loop, pad=pad, interpolate=interpolate, scale=scale, change=change)