#!/usr/bin/env python # pylint: disable=no-member """generate batches of images from prompts and upscale them params: run with `--help` default workflow runs infinite loop and prints stats when interrupted: 1. choose random scheduler lookup all available and pick one 2. generate dynamic prompt based on styles, embeddings, places, artists, suffixes 3. beautify prompt 4. generate 3x3 images 5. create image grid 6. upscale images with face restoration """ import argparse import asyncio import base64 import io import json import logging import math import os import pathlib import secrets import time import sys import importlib from random import randrange from PIL import Image from PIL.ExifTags import TAGS from PIL.TiffImagePlugin import ImageFileDirectory_v2 from sdapi import close, get, interrupt, post, session from util import Map, log, safestring sd = {} random = {} stats = Map({ 'images': 0, 'wall': 0, 'generate': 0, 'upscale': 0 }) avg = {} def grid(data): if len(data.image) > 1: w, h = data.image[0].size rows = round(math.sqrt(len(data.image))) cols = math.ceil(len(data.image) / rows) image = Image.new('RGB', size = (cols * w, rows * h), color = 'black') for i, img in enumerate(data.image): image.paste(img, box=(i % cols * w, i // cols * h)) short = data.info.prompt[:min(len(data.info.prompt), 96)] # limit prompt part of filename to 96 chars name = '{seed:0>9} {short}'.format(short = short, seed = data.info.all_seeds[0]) # pylint: disable=consider-using-f-string name = safestring(name) + '.jpg' f = os.path.join(sd.paths.root, sd.paths.grid, name) log.info({ 'grid': { 'name': f, 'size': image.size, 'images': len(data.image) } }) image.save(f, 'JPEG', exif = exif(data.info, None, 'grid'), optimize = True, quality = 70) return image return data.image def exif(info, i = None, op = 'generate'): seed = [info.all_seeds[i]] if len(info.all_seeds) > 0 and i is not None else info.all_seeds # always returns list seed = ', '.join([str(x) for x in seed]) # int list to str list to single str template = '{prompt} | negative {negative_prompt} | seed {s} | steps {steps} | cfgscale {cfg_scale} | sampler {sampler_name} | batch {batch_size} | timestamp {job_timestamp} | model {model} | vae {vae}'.format(s = seed, model = sd.options['sd_model_checkpoint'], vae = sd.options['sd_vae'], **info) # pylint: disable=consider-using-f-string if op == 'upscale': template += ' | faces gfpgan' if sd.upscale.gfpgan_visibility > 0 else '' template += ' | faces codeformer' if sd.upscale.codeformer_visibility > 0 else '' template += ' | upscale {resize}x {upscaler}'.format(resize = sd.upscale.upscaling_resize, upscaler = sd.upscale.upscaler_1) if sd.upscale.upscaler_1 != 'None' else '' # pylint: disable=consider-using-f-string template += ' | upscale {resize}x {upscaler}'.format(resize = sd.upscale.upscaling_resize, upscaler = sd.upscale.upscaler_2) if sd.upscale.upscaler_2 != 'None' else '' # pylint: disable=consider-using-f-string if op == 'grid': template += ' | grid {num}'.format(num = sd.generate.batch_size * sd.generate.n_iter) # pylint: disable=consider-using-f-string ifd = ImageFileDirectory_v2() exif_stream = io.BytesIO() _TAGS = {v: k for k, v in TAGS.items()} # enumerate possible exif tags ifd[_TAGS['ImageDescription']] = template ifd.save(exif_stream) val = b'Exif\x00\x00' + exif_stream.getvalue() return val def randomize(lst): if len(lst) > 0: return secrets.choice(lst) else: return '' def prompt(params): # generate dynamic prompt or use one if provided sd.generate.prompt = params.prompt if params.prompt != 'dynamic' else randomize(random.prompts) sd.generate.negative_prompt = params.negative if params.negative != 'dynamic' else randomize(random.negative) embedding = params.embedding if params.embedding != 'random' else randomize(random.embeddings) sd.generate.prompt = sd.generate.prompt.replace('', embedding) artist = params.artist if params.artist != 'random' else randomize(random.artists) sd.generate.prompt = sd.generate.prompt.replace('', artist) style = params.style if params.style != 'random' else randomize(random.styles) sd.generate.prompt = sd.generate.prompt.replace('