Merge pull request #78 from v8hid/develop

v1.2 


    Added common prompt prefix and suffix for better user experience.
    Improved frame correction and enhancement with mask_blur and non-inpainting models.
    All main frames will be shown as a gallery view in output.
    Updated default parameter values.
    Fixed bugs related to prompts import.
    Made improvements to UI parameter Names and Frame problem (It's gone).
    Refactored code for better maintenance.
pull/89/head
vahid khroasani 2023-05-02 11:25:49 +04:00 committed by GitHub
commit d7263b3f38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1373 additions and 936 deletions

1
.gitignore vendored
View File

@ -129,3 +129,4 @@ dmypy.json
.pyre/
.vscode/settings.json
.DS_Store
/.vs

View File

@ -1,2 +1,2 @@
from .image import shrink_and_paste_on_blank
from .video import write_video
# from .ui import on_ui_tabs
# from .settings import on_ui_settings

0
iz_helpers/extra.py Normal file
View File

126
iz_helpers/helpers.py Normal file
View File

@ -0,0 +1,126 @@
import math
import os
import modules.shared as shared
import modules.sd_models
import gradio as gr
from scripts import postprocessing_upscale
from .prompt_util import readJsonPrompt
import asyncio
def fix_env_Path_ffprobe():
envpath = os.environ["PATH"]
ffppath = shared.opts.data.get("infzoom_ffprobepath", "")
if ffppath and not ffppath in envpath:
path_sep = ";" if os.name == "nt" else ":"
os.environ["PATH"] = envpath + path_sep + ffppath
def closest_upper_divisible_by_eight(num):
if num % 8 == 0:
return num
else:
return math.ceil(num / 8) * 8
def load_model_from_setting(model_field_name, progress, progress_desc):
# fix typo in Automatic1111 vs Vlad111
if hasattr(modules.sd_models, "checkpoint_alisases"):
checkPList = modules.sd_models.checkpoint_alisases
elif hasattr(modules.sd_models, "checkpoint_aliases"):
checkPList = modules.sd_models.checkpoint_aliases
else:
raise Exception(
"This is not a compatible StableDiffusion Platform, can not access checkpoints"
)
model_name = shared.opts.data.get(model_field_name)
if model_name is not None and model_name != "":
checkinfo = checkPList[model_name]
if not checkinfo:
raise NameError(model_field_name + " Does not exist in your models.")
if progress:
progress(0, desc=progress_desc + checkinfo.name)
modules.sd_models.load_model(checkinfo)
def do_upscaleImg(curImg, upscale_do, upscaler_name, upscale_by):
if not upscale_do:
return curImg
# ensure even width and even height for ffmpeg
# if odd, switch to scale to mode
rwidth = round(curImg.width * upscale_by)
rheight = round(curImg.height * upscale_by)
ups_mode = 2 # upscale_by
if (rwidth % 2) == 1:
ups_mode = 1
rwidth += 1
if (rheight % 2) == 1:
ups_mode = 1
rheight += 1
if 1 == ups_mode:
print(
"Infinite Zoom: aligning output size to even width and height: "
+ str(rwidth)
+ " x "
+ str(rheight),
end="\r",
)
pp = postprocessing_upscale.scripts_postprocessing.PostprocessedImage(curImg)
ups = postprocessing_upscale.ScriptPostprocessingUpscale()
ups.process(
pp,
upscale_mode=ups_mode,
upscale_by=upscale_by,
upscale_to_width=rwidth,
upscale_to_height=rheight,
upscale_crop=False,
upscaler_1_name=upscaler_name,
upscaler_2_name=None,
upscaler_2_visibility=0.0,
)
return pp.image
async def showGradioErrorAsync(txt, delay=1):
await asyncio.sleep(delay) # sleep for 1 second
raise gr.Error(txt)
def putPrompts(files):
try:
with open(files.name, "r") as f:
file_contents = f.read()
data = readJsonPrompt(file_contents,False)
return [
gr.Textbox.update(data["prePrompt"]),
gr.DataFrame.update(data["prompts"]),
gr.Textbox.update(data["postPrompt"]),
gr.Textbox.update(data["negPrompt"])
]
except Exception:
print(
"[InfiniteZoom:] Loading your prompt failed. It seems to be invalid. Your prompt table is preserved."
)
# error only be shown with raise, so ui gets broken.
#asyncio.run(showGradioErrorAsync("Loading your prompts failed. It seems to be invalid. Your prompt table has been preserved.",5))
return [gr.Textbox.update(), gr.DataFrame.update(), gr.Textbox.update(),gr.Textbox.update()]
def clearPrompts():
return [
gr.DataFrame.update(value=[[0, "Infinite Zoom. Start over"]]),
gr.Textbox.update(""),
gr.Textbox.update(""),
gr.Textbox.update("")
]

67
iz_helpers/prompt_util.py Normal file
View File

@ -0,0 +1,67 @@
import json
from jsonschema import validate
from .static_variables import (
empty_prompt,
invalid_prompt,
jsonprompt_schemafile,
promptTableHeaders
)
def completeOptionals(j):
if isinstance(j, dict):
# Remove header information, user dont pimp our ui
if "prompts" in j:
if "headers" in j["prompts"]:
del j["prompts"]["headers"]
j["prompts"]["headers"]=promptTableHeaders
if "negPrompt" not in j:
j["negPrompt"]=""
if "prePrompt" not in j:
if "commonPromptPrefix" in j:
j["prePrompt"]=j["commonPromptPrefix"]
else:
j["prePrompt"]=""
if "postPrompt" not in j:
if "commonPromptSuffix" in j:
j["postPrompt"]=j["commonPromptSuffix"]
else:
j["postPrompt"]=""
return j
def validatePromptJson_throws(data):
with open(jsonprompt_schemafile, "r") as s:
schema = json.load(s)
try:
validate(instance=data, schema=schema)
except Exception:
raise Exception("Your prompts are not schema valid.")
return completeOptionals(data)
def readJsonPrompt(txt, returnFailPrompt=False):
if not txt:
return empty_prompt
try:
jpr = json.loads(txt)
except Exception:
if returnFailPrompt:
print (f"Infinite Zoom: Corrupted Json structure: {txt[:24]} ...")
return invalid_prompt
raise (f"Infinite Zoom: Corrupted Json structure: {txt[:24]} ...")
try:
return validatePromptJson_throws(jpr)
except Exception:
if returnFailPrompt:
return invalid_prompt
pass

View File

@ -0,0 +1,60 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "1.1",
"type": "object",
"properties": {
"prompts": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "array",
"items": [
{
"oneOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "string"
}
]
},
{
"type": "string"
}
],
"minItems": 0,
"maxItems": 999,
"uniqueItems": false
},
"minItems": 0
},
"headers": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 2
}
},
"required": [
"data"
]
},
"negPrompt": {
"type": "string"
},
"prePrompt": {
"type": "string"
},
"postPrompt": {
"type": "string"
}
},
"required": [
"prompts"
]
}

512
iz_helpers/run.py Normal file
View File

@ -0,0 +1,512 @@
import math, time, os
import numpy as np
from PIL import Image, ImageFilter, ImageDraw
from modules.ui import plaintext_to_html
import modules.shared as shared
from modules.paths_internal import script_path
from .helpers import (
fix_env_Path_ffprobe,
closest_upper_divisible_by_eight,
load_model_from_setting,
do_upscaleImg,
)
from .sd_helpers import renderImg2Img, renderTxt2Img
from .image import shrink_and_paste_on_blank
from .video import write_video
def crop_fethear_ellipse(image, feather_margin=30, width_offset=0, height_offset=0):
# Create a blank mask image with the same size as the original image
mask = Image.new("L", image.size, 0)
draw = ImageDraw.Draw(mask)
# Calculate the ellipse's bounding box
ellipse_box = (
width_offset,
height_offset,
image.width - width_offset,
image.height - height_offset,
)
# Draw the ellipse on the mask
draw.ellipse(ellipse_box, fill=255)
# Apply the mask to the original image
result = Image.new("RGBA", image.size)
result.paste(image, mask=mask)
# Crop the resulting image to the ellipse's bounding box
cropped_image = result.crop(ellipse_box)
# Create a new mask image with a black background (0)
mask = Image.new("L", cropped_image.size, 0)
draw = ImageDraw.Draw(mask)
# Draw an ellipse on the mask image
draw.ellipse(
(
0 + feather_margin,
0 + feather_margin,
cropped_image.width - feather_margin,
cropped_image.height - feather_margin,
),
fill=255,
outline=0,
)
# Apply a Gaussian blur to the mask image
mask = mask.filter(ImageFilter.GaussianBlur(radius=feather_margin / 2))
cropped_image.putalpha(mask)
res = Image.new(cropped_image.mode, (image.width, image.height))
paste_pos = (
int((res.width - cropped_image.width) / 2),
int((res.height - cropped_image.height) / 2),
)
res.paste(cropped_image, paste_pos)
return res
def outpaint_steps(
width,
height,
common_prompt_pre,
common_prompt_suf,
prompts,
negative_prompt,
seed,
sampler,
num_inference_steps,
guidance_scale,
inpainting_denoising_strength,
inpainting_mask_blur,
inpainting_fill_mode,
inpainting_full_res,
inpainting_padding,
init_img,
outpaint_steps,
out_config,
mask_width,
mask_height,
custom_exit_image,
frame_correction=True, # TODO: add frame_Correction in UI
):
main_frames = [init_img.convert("RGB")]
for i in range(outpaint_steps):
print_out = (
"Outpaint step: "
+ str(i + 1)
+ " / "
+ str(outpaint_steps)
+ " Seed: "
+ str(seed)
)
print(print_out)
current_image = main_frames[-1]
current_image = shrink_and_paste_on_blank(
current_image, mask_width, mask_height
)
mask_image = np.array(current_image)[:, :, 3]
mask_image = Image.fromarray(255 - mask_image).convert("RGB")
# create mask (black image with white mask_width width edges)
if custom_exit_image and ((i + 1) == outpaint_steps):
current_image = custom_exit_image.resize(
(width, height), resample=Image.LANCZOS
)
main_frames.append(current_image.convert("RGB"))
# print("using Custom Exit Image")
save2Collect(current_image, out_config, f"exit_img.png")
else:
pr = prompts[max(k for k in prompts.keys() if k <= i)]
processed, newseed = renderImg2Img(
f"{common_prompt_pre}\n{pr}\n{common_prompt_suf}".strip(),
negative_prompt,
sampler,
num_inference_steps,
guidance_scale,
seed,
width,
height,
current_image,
mask_image,
inpainting_denoising_strength,
inpainting_mask_blur,
inpainting_fill_mode,
inpainting_full_res,
inpainting_padding,
)
if len(processed.images) > 0:
main_frames.append(processed.images[0].convert("RGB"))
save2Collect(processed.images[0], out_config, f"outpain_step_{i}.png")
seed = newseed
# TODO: seed behavior
if frame_correction and inpainting_mask_blur > 0:
corrected_frame = crop_inner_image(
main_frames[i + 1], mask_width, mask_height
)
enhanced_img = crop_fethear_ellipse(
main_frames[i],
30,
inpainting_mask_blur / 3 // 2,
inpainting_mask_blur / 3 // 2,
)
save2Collect(main_frames[i], out_config, f"main_frame_{i}")
save2Collect(enhanced_img, out_config, f"main_frame_enhanced_{i}")
corrected_frame.paste(enhanced_img, mask=enhanced_img)
main_frames[i] = corrected_frame
# else :TEST
# current_image.paste(prev_image, mask=prev_image)
return main_frames, processed
def create_zoom(
common_prompt_pre,
prompts_array,
common_prompt_suf,
negative_prompt,
num_outpainting_steps,
guidance_scale,
num_inference_steps,
custom_init_image,
custom_exit_image,
video_frame_rate,
video_zoom_mode,
video_start_frame_dupe_amount,
video_last_frame_dupe_amount,
inpainting_mask_blur,
inpainting_fill_mode,
zoom_speed,
seed,
outputsizeW,
outputsizeH,
batchcount,
sampler,
upscale_do,
upscaler_name,
upscale_by,
inpainting_denoising_strength=1,
inpainting_full_res=0,
inpainting_padding=0,
progress=None,
):
for i in range(batchcount):
print(f"Batch {i+1}/{batchcount}")
result = create_zoom_single(
common_prompt_pre,
prompts_array,
common_prompt_suf,
negative_prompt,
num_outpainting_steps,
guidance_scale,
num_inference_steps,
custom_init_image,
custom_exit_image,
video_frame_rate,
video_zoom_mode,
video_start_frame_dupe_amount,
video_last_frame_dupe_amount,
inpainting_mask_blur,
inpainting_fill_mode,
zoom_speed,
seed,
outputsizeW,
outputsizeH,
sampler,
upscale_do,
upscaler_name,
upscale_by,
inpainting_denoising_strength,
inpainting_full_res,
inpainting_padding,
progress,
)
return result
def prepare_output_path():
isCollect = shared.opts.data.get("infzoom_collectAllResources", False)
output_path = shared.opts.data.get("infzoom_outpath", "outputs")
save_path = os.path.join(
output_path, shared.opts.data.get("infzoom_outSUBpath", "infinite-zooms")
)
if isCollect:
save_path = os.path.join(save_path, "iz_collect" + str(int(time.time())))
if not os.path.exists(save_path):
os.makedirs(save_path)
video_filename = os.path.join(
save_path, "infinite_zoom_" + str(int(time.time())) + ".mp4"
)
return {
"isCollect": isCollect,
"save_path": save_path,
"video_filename": video_filename,
}
def save2Collect(img, out_config, name):
if out_config["isCollect"]:
img.save(f'{out_config["save_path"]}/{name}.png')
def frame2Collect(all_frames, out_config):
save2Collect(all_frames[-1], out_config, f"frame_{len(all_frames)}")
def frames2Collect(all_frames, out_config):
for i, f in enumerate(all_frames):
save2Collect(f, out_config, f"frame_{i}")
def crop_inner_image(outpainted_img, width_offset, height_offset):
width, height = outpainted_img.size
center_x, center_y = int(width / 2), int(height / 2)
# Crop the image to the center
cropped_img = outpainted_img.crop(
(
center_x - width_offset,
center_y - height_offset,
center_x + width_offset,
center_y + height_offset,
)
)
prev_step_img = cropped_img.resize((width, height), resample=Image.LANCZOS)
# resized_img = resized_img.filter(ImageFilter.SHARPEN)
return prev_step_img
def create_zoom_single(
common_prompt_pre,
prompts_array,
common_prompt_suf,
negative_prompt,
num_outpainting_steps,
guidance_scale,
num_inference_steps,
custom_init_image,
custom_exit_image,
video_frame_rate,
video_zoom_mode,
video_start_frame_dupe_amount,
video_last_frame_dupe_amount,
inpainting_mask_blur,
inpainting_fill_mode,
zoom_speed,
seed,
outputsizeW,
outputsizeH,
sampler,
upscale_do,
upscaler_name,
upscale_by,
inpainting_denoising_strength,
inpainting_full_res,
inpainting_padding,
progress,
):
# try:
# if gr.Progress() is not None:
# progress = gr.Progress()
# progress(0, desc="Preparing Initial Image")
# except Exception:
# pass
fix_env_Path_ffprobe()
out_config = prepare_output_path()
prompts = {}
for x in prompts_array:
try:
key = int(x[0])
value = str(x[1])
prompts[key] = value
except ValueError:
pass
assert len(prompts_array) > 0, "prompts is empty"
width = closest_upper_divisible_by_eight(outputsizeW)
height = closest_upper_divisible_by_eight(outputsizeH)
current_image = Image.new(mode="RGBA", size=(width, height))
mask_image = np.array(current_image)[:, :, 3]
mask_image = Image.fromarray(255 - mask_image).convert("RGB")
current_image = current_image.convert("RGB")
current_seed = seed
if custom_init_image:
current_image = custom_init_image.resize(
(width, height), resample=Image.LANCZOS
)
save2Collect(current_image, out_config, f"init_custom.png")
else:
load_model_from_setting(
"infzoom_txt2img_model", progress, "Loading Model for txt2img: "
)
pr = prompts[min(k for k in prompts.keys() if k >= 0)]
processed, newseed = renderTxt2Img(
f"{common_prompt_pre}\n{pr}\n{common_prompt_suf}".strip(),
negative_prompt,
sampler,
num_inference_steps,
guidance_scale,
current_seed,
width,
height,
)
if len(processed.images) > 0:
current_image = processed.images[0]
save2Collect(current_image, out_config, f"init_txt2img.png")
current_seed = newseed
mask_width = math.trunc(width / 4) # was initially 512px => 128px
mask_height = math.trunc(height / 4) # was initially 512px => 128px
num_interpol_frames = round(video_frame_rate * zoom_speed)
all_frames = []
if upscale_do and progress:
progress(0, desc="upscaling inital image")
load_model_from_setting(
"infzoom_inpainting_model", progress, "Loading Model for inpainting/img2img: "
)
main_frames, processed = outpaint_steps(
width,
height,
common_prompt_pre,
common_prompt_suf,
prompts,
negative_prompt,
seed,
sampler,
num_inference_steps,
guidance_scale,
inpainting_denoising_strength,
inpainting_mask_blur,
inpainting_fill_mode,
inpainting_full_res,
inpainting_padding,
current_image,
num_outpainting_steps,
out_config,
mask_width,
mask_height,
custom_exit_image,
)
all_frames.append(
do_upscaleImg(main_frames[0], upscale_do, upscaler_name, upscale_by)
if upscale_do
else main_frames[0]
)
for i in range(len(main_frames) - 1):
# interpolation steps between 2 inpainted images (=sequential zoom and crop)
for j in range(num_interpol_frames - 1):
current_image = main_frames[i + 1]
interpol_image = current_image
save2Collect(interpol_image, out_config, f"interpol_img_{i}_{j}].png")
interpol_width = math.ceil(
(
1
- (1 - 2 * mask_width / width)
** (1 - (j + 1) / num_interpol_frames)
)
* width
/ 2
)
interpol_height = math.ceil(
(
1
- (1 - 2 * mask_height / height)
** (1 - (j + 1) / num_interpol_frames)
)
* height
/ 2
)
interpol_image = interpol_image.crop(
(
interpol_width,
interpol_height,
width - interpol_width,
height - interpol_height,
)
)
interpol_image = interpol_image.resize((width, height))
save2Collect(interpol_image, out_config, f"interpol_resize_{i}_{j}.png")
# paste the higher resolution previous image in the middle to avoid drop in quality caused by zooming
interpol_width2 = math.ceil(
(1 - (width - 2 * mask_width) / (width - 2 * interpol_width))
/ 2
* width
)
interpol_height2 = math.ceil(
(1 - (height - 2 * mask_height) / (height - 2 * interpol_height))
/ 2
* height
)
prev_image_fix_crop = shrink_and_paste_on_blank(
main_frames[i], interpol_width2, interpol_height2
)
interpol_image.paste(prev_image_fix_crop, mask=prev_image_fix_crop)
save2Collect(interpol_image, out_config, f"interpol_prevcrop_{i}_{j}.png")
if upscale_do and progress:
progress(((i + 1) / num_outpainting_steps), desc="upscaling interpol")
all_frames.append(
do_upscaleImg(interpol_image, upscale_do, upscaler_name, upscale_by)
if upscale_do
else interpol_image
)
if upscale_do and progress:
progress(((i + 1) / num_outpainting_steps), desc="upscaling current")
all_frames.append(
do_upscaleImg(current_image, upscale_do, upscaler_name, upscale_by)
if upscale_do
else current_image
)
frames2Collect(all_frames, out_config)
write_video(
out_config["video_filename"],
all_frames,
video_frame_rate,
video_zoom_mode,
int(video_start_frame_dupe_amount),
int(video_last_frame_dupe_amount),
)
print("Video saved in: " + os.path.join(script_path, out_config["video_filename"]))
return (
out_config["video_filename"],
main_frames,
processed.js(),
plaintext_to_html(processed.info),
plaintext_to_html(""),
)

81
iz_helpers/sd_helpers.py Normal file
View File

@ -0,0 +1,81 @@
from modules.processing import (
process_images,
StableDiffusionProcessingTxt2Img,
StableDiffusionProcessingImg2Img,
)
import modules.shared as shared
def renderTxt2Img(
prompt, negative_prompt, sampler, steps, cfg_scale, seed, width, height
):
processed = None
p = StableDiffusionProcessingTxt2Img(
sd_model=shared.sd_model,
outpath_samples=shared.opts.outdir_txt2img_samples,
outpath_grids=shared.opts.outdir_txt2img_grids,
prompt=prompt,
negative_prompt=negative_prompt,
seed=seed,
sampler_name=sampler,
n_iter=1,
steps=steps,
cfg_scale=cfg_scale,
width=width,
height=height,
)
processed = process_images(p)
newseed = p.seed
return processed, newseed
def renderImg2Img(
prompt,
negative_prompt,
sampler,
steps,
cfg_scale,
seed,
width,
height,
init_image,
mask_image,
inpainting_denoising_strength,
inpainting_mask_blur,
inpainting_fill_mode,
inpainting_full_res,
inpainting_padding,
):
processed = None
p = StableDiffusionProcessingImg2Img(
sd_model=shared.sd_model,
outpath_samples=shared.opts.outdir_img2img_samples,
outpath_grids=shared.opts.outdir_img2img_grids,
prompt=prompt,
negative_prompt=negative_prompt,
seed=seed,
sampler_name=sampler,
n_iter=1,
steps=steps,
cfg_scale=cfg_scale,
width=width,
height=height,
init_images=[init_image],
denoising_strength=inpainting_denoising_strength,
mask_blur=inpainting_mask_blur,
inpainting_fill=inpainting_fill_mode,
inpaint_full_res=inpainting_full_res,
inpaint_full_res_padding=inpainting_padding,
mask=mask_image,
)
# p.latent_mask = Image.new("RGB", (p.width, p.height), "white")
processed = process_images(p)
# For those that use Image grids this will make sure that ffmpeg does not crash out
if (len(processed.images) > 1) and (processed.images[0].size[0] != processed.images[-1].size[0]):
processed.images.pop(0)
print("\nGrid image detected applying patch")
newseed = p.seed
return processed, newseed

108
iz_helpers/settings.py Normal file
View File

@ -0,0 +1,108 @@
import gradio as gr
import modules.shared as shared
from .static_variables import default_prompt
def on_ui_settings():
section = ("infinite-zoom", "Infinite Zoom")
shared.opts.add_option(
"infzoom_outpath",
shared.OptionInfo(
"outputs",
"Path where to store your infinite video. Default is Outputs",
gr.Textbox,
{"interactive": True},
section=section,
),
)
shared.opts.add_option(
"infzoom_outSUBpath",
shared.OptionInfo(
"infinite-zooms",
"Which subfolder name to be created in the outpath. Default is 'infinite-zooms'",
gr.Textbox,
{"interactive": True},
section=section,
),
)
shared.opts.add_option(
"infzoom_outsizeW",
shared.OptionInfo(
512,
"Default width of your video",
gr.Slider,
{"minimum": 16, "maximum": 2048, "step": 16},
section=section,
),
)
shared.opts.add_option(
"infzoom_outsizeH",
shared.OptionInfo(
512,
"Default height your video",
gr.Slider,
{"minimum": 16, "maximum": 2048, "step": 16},
section=section,
),
)
shared.opts.add_option(
"infzoom_ffprobepath",
shared.OptionInfo(
"",
"Writing videos has dependency to an existing FFPROBE executable on your machine. D/L here (https://github.com/BtbN/FFmpeg-Builds/releases) your OS variant and point to your installation path",
gr.Textbox,
{"interactive": True},
section=section,
),
)
shared.opts.add_option(
"infzoom_txt2img_model",
shared.OptionInfo(
None,
"Name of your desired model to render keyframes (txt2img)",
gr.Dropdown,
lambda: {"choices": [x for x in list(shared.list_checkpoint_tiles()) if "inpainting" not in x]},
section=section,
),
)
shared.opts.add_option(
"infzoom_inpainting_model",
shared.OptionInfo(
None,
"Name of your desired inpaint model (img2img-inpaint). Default is vanilla sd-v1-5-inpainting.ckpt ",
gr.Dropdown,
lambda: {"choices": [x for x in list(shared.list_checkpoint_tiles()) if "inpainting" in x]},
section=section,
),
)
shared.opts.add_option(
"infzoom_defPrompt",
shared.OptionInfo(
default_prompt,
"Default prompt-setup to start with'",
gr.Code,
{"interactive": True, "language": "json"},
section=section,
),
)
shared.opts.add_option(
"infzoom_collectAllResources",
shared.OptionInfo(
False,
"!!! Store all images (txt2img, init_image,exit_image, inpainting, interpolation) into one folder in your OUTPUT Path. Very slow, a lot of data. Dont do this on long runs !!!",
gr.Checkbox,
{"interactive": True},
section=section,
),
)

View File

@ -0,0 +1,51 @@
import os
from modules import scripts
import modules.sd_samplers
default_sampling_steps = 35
default_sampler = "DDIM"
default_cfg_scale = 8
default_mask_blur = 48
default_total_outpaints = 5
promptTableHeaders = ["Start at second [0,1,...]", "Prompt"]
default_prompt = """
{
"prePrompt": "Huge spectacular Waterfall in ",
"prompts": {
"data": [
[0, "a dense tropical forest"],
[2, "a Lush jungle"],
[3, "a Thick rainforest"],
[5, "a Verdant canopy"]
]
},
"postPrompt": "epic perspective,(vegetation overgrowth:1.3)(intricate, ornamentation:1.1),(baroque:1.1), fantasy, (realistic:1) digital painting , (magical,mystical:1.2) , (wide angle shot:1.4), (landscape composed:1.2)(medieval:1.1),(tropical forest:1.4),(river:1.3) volumetric lighting ,epic, style by Alex Horley Wenjun Lin greg rutkowski Ruan Jia (Wayne Barlowe:1.2)",
"negPrompt": "frames, border, edges, borderline, text, character, duplicate, error, out of frame, watermark, low quality, ugly, deformed, blur, bad-artist"
}
"""
empty_prompt = '{"prompts":{"data":[],"negPrompt":"", prePrompt:"", postPrompt:""}'
invalid_prompt = {
"prompts": {
"data": [[0, "Your prompt-json is invalid, please check Settings"]],
},
"negPrompt": "Invalid prompt-json",
"prePromp": "Invalid prompt",
"postPrompt": "Invalid prompt",
}
available_samplers = [
s.name for s in modules.sd_samplers.samplers if "UniPc" not in s.name
]
current_script_dir = scripts.basedir().split(os.sep)[
-2:
] # contains install and our extension foldername
jsonprompt_schemafile = (
current_script_dir[0]
+ "/"
+ current_script_dir[1]
+ "/iz_helpers/promptschema.json"
)

307
iz_helpers/ui.py Normal file
View File

@ -0,0 +1,307 @@
import gradio as gr
from .run import create_zoom
import modules.shared as shared
from webui import wrap_gradio_gpu_call
from modules.ui import create_output_panel
from .static_variables import (
default_prompt,
available_samplers,
default_total_outpaints,
default_sampling_steps,
default_cfg_scale,
default_mask_blur,
default_sampler,
)
from .helpers import putPrompts, clearPrompts
from .prompt_util import readJsonPrompt
from .static_variables import promptTableHeaders
def on_ui_tabs():
with gr.Blocks(analytics_enabled=False) as infinite_zoom_interface:
gr.HTML(
"""
<p style="text-align: center;">
<a target="_blank" href="https://github.com/v8hid/infinite-zoom-automatic1111-webui"><img src="https://img.shields.io/static/v1?label=github&message=repository&color=blue&style=flat&logo=github&logoColor=white" style="display: inline;" alt="GitHub Repo"/></a>
<a href="https://discord.gg/v2nHqSrWdW"><img src="https://img.shields.io/discord/1095469311830806630?color=blue&label=discord&logo=discord&logoColor=white" style="display: inline;" alt="Discord server"></a>
</p>
"""
)
with gr.Row():
generate_btn = gr.Button(value="Generate video", variant="primary")
interrupt = gr.Button(value="Interrupt", elem_id="interrupt_training")
with gr.Row():
with gr.Column(scale=1, variant="panel"):
with gr.Tab("Main"):
with gr.Row():
batchcount_slider = gr.Slider(
minimum=1,
maximum=25,
value=shared.opts.data.get("infzoom_batchcount", 1),
step=1,
label="Batch Count",
)
main_outpaint_steps = gr.Number(
label="Total video length [s]",
value=default_total_outpaints,
precision=0,
interactive=True,
)
# safe reading json prompt
pr = shared.opts.data.get("infzoom_defPrompt", default_prompt)
jpr = readJsonPrompt(pr, True)
main_common_prompt_pre = gr.Textbox(
value=jpr["prePrompt"], label="Common Prompt Prefix"
)
main_prompts = gr.Dataframe(
type="array",
headers=promptTableHeaders,
datatype=["number", "str"],
row_count=1,
col_count=(2, "fixed"),
value=jpr["prompts"],
wrap=True,
)
main_common_prompt_suf = gr.Textbox(
value=jpr["postPrompt"], label="Common Prompt Suffix"
)
main_negative_prompt = gr.Textbox(
value=jpr["negPrompt"], label="Negative Prompt"
)
# these button will be moved using JS under the dataframe view as small ones
exportPrompts_button = gr.Button(
value="Export prompts",
variant="secondary",
elem_classes="sm infzoom_tab_butt",
elem_id="infzoom_exP_butt",
)
importPrompts_button = gr.UploadButton(
label="Import prompts",
variant="secondary",
elem_classes="sm infzoom_tab_butt",
elem_id="infzoom_imP_butt",
)
exportPrompts_button.click(
None,
_js="exportPrompts",
inputs=[
main_common_prompt_pre,
main_prompts,
main_common_prompt_suf,
main_negative_prompt,
],
outputs=None,
)
importPrompts_button.upload(
fn=putPrompts,
outputs=[
main_common_prompt_pre,
main_prompts,
main_common_prompt_suf,
main_negative_prompt,
],
inputs=[importPrompts_button],
)
clearPrompts_button = gr.Button(
value="Clear prompts",
variant="secondary",
elem_classes="sm infzoom_tab_butt",
elem_id="infzoom_clP_butt",
)
clearPrompts_button.click(
fn=clearPrompts,
inputs=[],
outputs=[
main_prompts,
main_negative_prompt,
main_common_prompt_pre,
main_common_prompt_suf,
],
)
with gr.Accordion("Render settings"):
with gr.Row():
seed = gr.Number(
label="Seed", value=-1, precision=0, interactive=True
)
main_sampler = gr.Dropdown(
label="Sampler",
choices=available_samplers,
value=default_sampler,
type="value",
)
with gr.Row():
main_width = gr.Slider(
minimum=16,
maximum=2048,
value=shared.opts.data.get("infzoom_outsizeW", 512),
step=16,
label="Output Width",
)
main_height = gr.Slider(
minimum=16,
maximum=2048,
value=shared.opts.data.get("infzoom_outsizeH", 512),
step=16,
label="Output Height",
)
with gr.Row():
main_guidance_scale = gr.Slider(
minimum=0.1,
maximum=15,
step=0.1,
value=default_cfg_scale,
label="Guidance Scale",
)
sampling_step = gr.Slider(
minimum=1,
maximum=150,
step=1,
value=default_sampling_steps,
label="Sampling Steps for each outpaint",
)
with gr.Row():
init_image = gr.Image(
type="pil", label="Custom initial image"
)
exit_image = gr.Image(
type="pil", label="Custom exit image", visible=False
)
with gr.Tab("Video"):
video_frame_rate = gr.Slider(
label="Frames per second",
value=30,
minimum=1,
maximum=60,
)
video_zoom_mode = gr.Radio(
label="Zoom mode",
choices=["Zoom-out", "Zoom-in"],
value="Zoom-out",
type="index",
)
video_start_frame_dupe_amount = gr.Slider(
label="number of start frame dupe",
info="Frames to freeze at the start of the video",
value=0,
minimum=1,
maximum=60,
)
video_last_frame_dupe_amount = gr.Slider(
label="number of last frame dupe",
info="Frames to freeze at the end of the video",
value=0,
minimum=1,
maximum=60,
)
video_zoom_speed = gr.Slider(
label="Zoom Speed",
value=1.0,
minimum=0.1,
maximum=20.0,
step=0.1,
info="Zoom speed in seconds (higher values create slower zoom)",
)
with gr.Tab("Outpaint"):
inpainting_mask_blur = gr.Slider(
label="Mask Blur",
minimum=0,
maximum=64,
value=default_mask_blur,
)
inpainting_fill_mode = gr.Radio(
label="Masked content",
choices=["fill", "original", "latent noise", "latent nothing"],
value="latent noise",
type="index",
)
with gr.Tab("Post proccess"):
upscale_do = gr.Checkbox(False, label="Enable Upscale")
upscaler_name = gr.Dropdown(
label="Upscaler",
elem_id="infZ_upscaler",
choices=[x.name for x in shared.sd_upscalers],
value=shared.sd_upscalers[0].name,
)
upscale_by = gr.Slider(
label="Upscale by factor",
minimum=1,
maximum=8,
step=0.5,
value=2,
)
with gr.Accordion("Help", open=False):
gr.Markdown(
"""# Performance critical
Depending on amount of frames and which upscaler you choose it might took a long time to render.
Our best experience and trade-off is the R-ERSGAn4x upscaler.
"""
)
with gr.Column(scale=1, variant="compact"):
output_video = gr.Video(label="Output").style(width=512, height=512)
(
out_image,
generation_info,
html_info,
html_log,
) = create_output_panel(
"infinite-zoom", shared.opts.outdir_img2img_samples
)
generate_btn.click(
fn=wrap_gradio_gpu_call(create_zoom, extra_outputs=[None, "", ""]),
inputs=[
main_common_prompt_pre,
main_prompts,
main_common_prompt_suf,
main_negative_prompt,
main_outpaint_steps,
main_guidance_scale,
sampling_step,
init_image,
exit_image,
video_frame_rate,
video_zoom_mode,
video_start_frame_dupe_amount,
video_last_frame_dupe_amount,
inpainting_mask_blur,
inpainting_fill_mode,
video_zoom_speed,
seed,
main_width,
main_height,
batchcount_slider,
main_sampler,
upscale_do,
upscaler_name,
upscale_by,
],
outputs=[output_video, out_image, generation_info, html_info, html_log],
)
main_prompts.change(
fn=checkPrompts, inputs=[main_prompts], outputs=[generate_btn]
)
interrupt.click(fn=shared.state.interrupt(), inputs=[], outputs=[])
infinite_zoom_interface.queue()
return [(infinite_zoom_interface, "Infinite Zoom", "iz_interface")]
def checkPrompts(p):
return gr.Button.update(
interactive=any(0 in sublist for sublist in p)
or any("0" in sublist for sublist in p)
)

View File

@ -13,9 +13,8 @@ def write_video(file_path, frames, fps, reversed=True, start_frame_dupe_amount=1
if reversed == True:
frames = frames[::-1]
# Get dimensions of the first frames, all subsequent has to be same sized
for k in frames:
assert (k.size == frames[0].size,"Different frame sizes found!")
# Drop missformed frames
frames = [frame for frame in frames if frame.size == frames[0].size]
# Create an imageio video writer, avoid block size of 512.
writer = imageio.get_writer(file_path, fps=fps, macro_block_size=None)

View File

@ -0,0 +1,49 @@
// mouseover tooltips for various UI elements
infzoom_titles = {
"Batch Count":"How many separate videos to create",
"Total video length [s]":"For each seconds frame (FPS) will be generated. Define prompts at which time they should start wihtin this duration.",
"Common Prompt Prefix":"Prompt inserted before each step",
"Common Prompt Suffix":"Prompt inserted after each step",
"Negative Prompt":"What your model shall avoid",
"Export prompts": "Downloads a JSON file to save all prompts",
"Import prompts": "Restore Prompts table from a specific JSON file",
"Clear prompts": "Start over, remove all entries from prompt table, prefix, suffix, negative",
"Custom initial image":"An image at the end resp. begin of your movie, depending or ZoomIn or Out",
"Custom exit image":"An image at the end resp. begin of your movie, depending or ZoomIn or Out",
"Zoom Speed":"Varies additional frames per second",
"Start at second [0,1,...]": "At which time the prompt has to be occure. We need at least one prompt starting at time 0",
"Generate video": "Start rendering. If it´s disabled the prompt table is invalid, check we have a start prompt at time 0"
}
onUiUpdate(function(){
gradioApp().querySelectorAll('span, button, select, p').forEach(function(span){
tooltip = infzoom_titles[span.textContent];
if(!tooltip){
tooltip = infzoom_titles[span.value];
}
if(!tooltip){
for (const c of span.classList) {
if (c in infzoom_titles) {
tooltip = infzoom_titles[c];
break;
}
}
}
if(tooltip){
span.title = tooltip;
}
})
gradioApp().querySelectorAll('select').forEach(function(select){
if (select.onchange != null) return;
select.onchange = function(){
select.title = infzoom_titles[select.value] || "";
}
})
})

View File

@ -1,9 +1,9 @@
// Function to download data to a file
function exportPrompts(p, np, filename = "infinite-zoom-prompts.json") {
function exportPrompts(cppre,p, cpsuf,np, filename = "infinite-zoom-prompts.json") {
let J = { prompts: p, negPrompt: np }
let J = { prompts: p, negPrompt: np, prePrompt: cppre, postPrompt: cpsuf }
var file = new Blob([JSON.stringify(J)], { type: "text/csv" });
var file = new Blob([JSON.stringify(J,null,2)], { type: "text/csv" });
if (window.navigator.msSaveOrOpenBlob) // IE10+
window.navigator.msSaveOrOpenBlob(file, filename);
else { // Others

View File

@ -1,880 +1,5 @@
import sys
import os
import time
import json
from jsonschema import validate
import numpy as np
import gradio as gr
from PIL import Image
import math
import json
from iz_helpers import shrink_and_paste_on_blank, write_video
from webui import wrap_gradio_gpu_call
from modules import script_callbacks, scripts
import modules.shared as shared
from modules.processing import (
process_images,
StableDiffusionProcessingTxt2Img,
StableDiffusionProcessingImg2Img,
)
from scripts import postprocessing_upscale
from modules.ui import create_output_panel, plaintext_to_html
import modules.sd_models
import modules.sd_samplers
from modules import scripts
usefulDirs = scripts.basedir().split(os.sep)[
-2:
] # contains install and our extension foldername
jsonprompt_schemafile = (
usefulDirs[0] + "/" + usefulDirs[1] + "/scripts/promptschema.json"
)
available_samplers = [s.name for s in modules.sd_samplers.samplers if "UniPc" not in s.name]
default_prompt = """
{
"prompts":{
"headers":["outpaint steps","prompt"],
"data":[
[0,"Huge spectacular Waterfall in a dense tropical forest,epic perspective,(vegetation overgrowth:1.3)(intricate, ornamentation:1.1),(baroque:1.1), fantasy, (realistic:1) digital painting , (magical,mystical:1.2) , (wide angle shot:1.4), (landscape composed:1.2)(medieval:1.1), divine,cinematic,(tropical forest:1.4),(river:1.3)mythology,india, volumetric lighting, Hindu ,epic, Alex Horley Wenjun Lin greg rutkowski Ruan Jia (Wayne Barlowe:1.2) <lora:epiNoiseoffset_v2:0.6> "]
]
},
"negPrompt":"frames, borderline, text, character, duplicate, error, out of frame, watermark, low quality, ugly, deformed, blur bad-artist"
}
"""
empty_prompt = (
'{"prompts":{"data":[],"headers":["outpaint steps","prompt"]},"negPrompt":""}'
)
# must be python dict
invalid_prompt = {
"prompts": {
"data": [[0, "Your prompt-json is invalid, please check Settings"]],
"headers": ["outpaint steps", "prompt"],
},
"negPrompt": "Invalid prompt-json",
}
def closest_upper_divisible_by_eight(num):
if num % 8 == 0:
return num
else:
return math.ceil(num / 8) * 8
# example fail: 720 px width * 1.66 upscale => 1195.2 => 1195 crash
# 512 px * 1.66 = 513.66 = ?
# assume ffmpeg will CUT to integer
# 721 /720
def do_upscaleImg(curImg, upscale_do, upscaler_name, upscale_by):
if not upscale_do:
return curImg
# ensure even width and even height for ffmpeg
# if odd, switch to scale to mode
rwidth = round(curImg.width * upscale_by)
rheight = round(curImg.height * upscale_by)
ups_mode = 2 # upscale_by
if ( (rwidth %2) == 1 ):
ups_mode = 1
rwidth += 1
if ( (rheight %2) == 1 ):
ups_mode = 1
rheight += 1
if (1 == ups_mode ):
print ("Infinite Zoom: aligning output size to even width and height: " + str(rwidth) +" x "+str(rheight), end='\r' )
pp = postprocessing_upscale.scripts_postprocessing.PostprocessedImage(
curImg
)
ups = postprocessing_upscale.ScriptPostprocessingUpscale()
ups.process(
pp,
upscale_mode=ups_mode,
upscale_by=upscale_by,
upscale_to_width=rwidth,
upscale_to_height=rheight,
upscale_crop=False,
upscaler_1_name=upscaler_name,
upscaler_2_name=None,
upscaler_2_visibility=0.0,
)
return pp.image
def renderTxt2Img(prompt, negative_prompt, sampler, steps, cfg_scale, width, height):
processed = None
p = StableDiffusionProcessingTxt2Img(
sd_model=shared.sd_model,
outpath_samples=shared.opts.outdir_txt2img_samples,
outpath_grids=shared.opts.outdir_txt2img_grids,
prompt=prompt,
negative_prompt=negative_prompt,
# seed=-1,
sampler_name=sampler,
n_iter=1,
steps=steps,
cfg_scale=cfg_scale,
width=width,
height=height,
)
processed = process_images(p)
return processed
def renderImg2Img(
prompt,
negative_prompt,
sampler,
steps,
cfg_scale,
width,
height,
init_image,
mask_image,
inpainting_denoising_strength,
inpainting_mask_blur,
inpainting_fill_mode,
inpainting_full_res,
inpainting_padding,
):
processed = None
p = StableDiffusionProcessingImg2Img(
sd_model=shared.sd_model,
outpath_samples=shared.opts.outdir_img2img_samples,
outpath_grids=shared.opts.outdir_img2img_grids,
prompt=prompt,
negative_prompt=negative_prompt,
# seed=-1,
sampler_name=sampler,
n_iter=1,
steps=steps,
cfg_scale=cfg_scale,
width=width,
height=height,
init_images=[init_image],
denoising_strength=inpainting_denoising_strength,
mask_blur=inpainting_mask_blur,
inpainting_fill=inpainting_fill_mode,
inpaint_full_res=inpainting_full_res,
inpaint_full_res_padding=inpainting_padding,
mask=mask_image,
)
# p.latent_mask = Image.new("RGB", (p.width, p.height), "white")
processed = process_images(p)
return processed
def fix_env_Path_ffprobe():
envpath = os.environ["PATH"]
ffppath = shared.opts.data.get("infzoom_ffprobepath", "")
if ffppath and not ffppath in envpath:
path_sep = ";" if os.name == "nt" else ":"
os.environ["PATH"] = envpath + path_sep + ffppath
def load_model_from_setting(model_field_name, progress, progress_desc):
# fix typo in Automatic1111 vs Vlad111
if hasattr(modules.sd_models, "checkpoint_alisases"):
checkPList = modules.sd_models.checkpoint_alisases
elif hasattr(modules.sd_models, "checkpoint_aliases"):
checkPList = modules.sd_models.checkpoint_aliases
else:
raise Exception("This is not a compatible StableDiffusion Platform, can not access checkpoints")
model_name = shared.opts.data.get(model_field_name)
if model_name is not None and model_name != "":
checkinfo = checkPList[model_name]
if not checkinfo:
raise NameError(model_field_name + " Does not exist in your models.")
if progress:
progress(0, desc=progress_desc + checkinfo.name)
modules.sd_models.load_model(checkinfo)
def create_zoom(
prompts_array,
negative_prompt,
num_outpainting_steps,
guidance_scale,
num_inference_steps,
custom_init_image,
custom_exit_image,
video_frame_rate,
video_zoom_mode,
video_start_frame_dupe_amount,
video_last_frame_dupe_amount,
inpainting_denoising_strength,
inpainting_mask_blur,
inpainting_fill_mode,
inpainting_full_res,
inpainting_padding,
zoom_speed,
outputsizeW,
outputsizeH,
batchcount,
sampler,
upscale_do,
upscaler_name,
upscale_by,
progress=None,
):
for i in range(batchcount):
print(f"Batch {i+1}/{batchcount}")
result = create_zoom_single(
prompts_array,
negative_prompt,
num_outpainting_steps,
guidance_scale,
num_inference_steps,
custom_init_image,
custom_exit_image,
video_frame_rate,
video_zoom_mode,
video_start_frame_dupe_amount,
video_last_frame_dupe_amount,
inpainting_denoising_strength,
inpainting_mask_blur,
inpainting_fill_mode,
inpainting_full_res,
inpainting_padding,
zoom_speed,
outputsizeW,
outputsizeH,
sampler,
upscale_do,
upscaler_name,
upscale_by,
progress,
)
return result
def create_zoom_single(
prompts_array,
negative_prompt,
num_outpainting_steps,
guidance_scale,
num_inference_steps,
custom_init_image,
custom_exit_image,
video_frame_rate,
video_zoom_mode,
video_start_frame_dupe_amount,
video_last_frame_dupe_amount,
inpainting_denoising_strength,
inpainting_mask_blur,
inpainting_fill_mode,
inpainting_full_res,
inpainting_padding,
zoom_speed,
outputsizeW,
outputsizeH,
sampler,
upscale_do,
upscaler_name,
upscale_by,
progress=None,
):
# try:
# if gr.Progress() is not None:
# progress = gr.Progress()
# progress(0, desc="Preparing Initial Image")
# except Exception:
# pass
fix_env_Path_ffprobe()
prompts = {}
for x in prompts_array:
try:
key = int(x[0])
value = str(x[1])
prompts[key] = value
except ValueError:
pass
assert len(prompts_array) > 0, "prompts is empty"
width = closest_upper_divisible_by_eight(outputsizeW)
height = closest_upper_divisible_by_eight(outputsizeH)
current_image = Image.new(mode="RGBA", size=(width, height))
mask_image = np.array(current_image)[:, :, 3]
mask_image = Image.fromarray(255 - mask_image).convert("RGB")
current_image = current_image.convert("RGB")
if custom_init_image:
current_image = custom_init_image.resize(
(width, height), resample=Image.LANCZOS
)
else:
load_model_from_setting("infzoom_txt2img_model", progress, "Loading Model for txt2img: ")
processed = renderTxt2Img(
prompts[min(k for k in prompts.keys() if k >= 0)],
negative_prompt,
sampler,
num_inference_steps,
guidance_scale,
width,
height,
)
current_image = processed.images[0]
mask_width = math.trunc(width / 4) # was initially 512px => 128px
mask_height = math.trunc(height / 4) # was initially 512px => 128px
num_interpol_frames = round(video_frame_rate * zoom_speed)
all_frames = []
if upscale_do and progress:
progress(0, desc="upscaling inital image")
all_frames.append(
do_upscaleImg(current_image, upscale_do, upscaler_name, upscale_by)
if upscale_do
else current_image
)
load_model_from_setting("infzoom_inpainting_model", progress, "Loading Model for inpainting/img2img: " )
for i in range(num_outpainting_steps):
print_out = "Outpaint step: " + str(i + 1) + " / " + str(num_outpainting_steps)
print(print_out)
if progress:
progress(((i + 1) / num_outpainting_steps), desc=print_out)
prev_image_fix = current_image
prev_image = shrink_and_paste_on_blank(current_image, mask_width, mask_height)
current_image = prev_image
# create mask (black image with white mask_width width edges)
mask_image = np.array(current_image)[:, :, 3]
mask_image = Image.fromarray(255 - mask_image).convert("RGB")
# inpainting step
current_image = current_image.convert("RGB")
processed = renderImg2Img(
prompts[max(k for k in prompts.keys() if k <= i)],
negative_prompt,
sampler,
num_inference_steps,
guidance_scale,
width,
height,
current_image,
mask_image,
inpainting_denoising_strength,
inpainting_mask_blur,
inpainting_fill_mode,
inpainting_full_res,
inpainting_padding,
)
current_image = processed.images[0]
current_image.paste(prev_image, mask=prev_image)
# interpolation steps between 2 inpainted images (=sequential zoom and crop)
for j in range(num_interpol_frames - 1):
interpol_image = current_image
interpol_width = round(
(
1
- (1 - 2 * mask_width / width)
** (1 - (j + 1) / num_interpol_frames)
)
* width
/ 2
)
interpol_height = round(
(
1
- (1 - 2 * mask_height / height)
** (1 - (j + 1) / num_interpol_frames)
)
* height
/ 2
)
interpol_image = interpol_image.crop(
(
interpol_width,
interpol_height,
width - interpol_width,
height - interpol_height,
)
)
interpol_image = interpol_image.resize((width, height))
# paste the higher resolution previous image in the middle to avoid drop in quality caused by zooming
interpol_width2 = round(
(1 - (width - 2 * mask_width) / (width - 2 * interpol_width))
/ 2
* width
)
interpol_height2 = round(
(1 - (height - 2 * mask_height) / (height - 2 * interpol_height))
/ 2
* height
)
prev_image_fix_crop = shrink_and_paste_on_blank(
prev_image_fix, interpol_width2, interpol_height2
)
interpol_image.paste(prev_image_fix_crop, mask=prev_image_fix_crop)
if upscale_do and progress:
progress(((i + 1) / num_outpainting_steps), desc="upscaling interpol")
all_frames.append(
do_upscaleImg(interpol_image, upscale_do, upscaler_name, upscale_by)
if upscale_do
else interpol_image
)
if upscale_do and progress:
progress(((i + 1) / num_outpainting_steps), desc="upscaling current")
all_frames.append(
do_upscaleImg(current_image, upscale_do, upscaler_name, upscale_by)
if upscale_do
else current_image
)
video_file_name = "infinite_zoom_" + str(int(time.time())) + ".mp4"
output_path = shared.opts.data.get(
"infzoom_outpath", shared.opts.data.get("outdir_img2img_samples")
)
save_path = os.path.join(
output_path, shared.opts.data.get("infzoom_outSUBpath", "infinite-zooms")
)
if not os.path.exists(save_path):
os.makedirs(save_path)
out = os.path.join(save_path, video_file_name)
write_video(
out,
all_frames,
video_frame_rate,
video_zoom_mode,
int(video_start_frame_dupe_amount),
int(video_last_frame_dupe_amount),
)
return (
out,
processed.images,
processed.js(),
plaintext_to_html(processed.info),
plaintext_to_html(""),
)
def validatePromptJson_throws(data):
with open(jsonprompt_schemafile, "r") as s:
schema = json.load(s)
validate(instance=data, schema=schema)
def putPrompts(files):
try:
with open(files.name, "r") as f:
file_contents = f.read()
data = json.loads(file_contents)
validatePromptJson_throws(data)
return [
gr.DataFrame.update(data["prompts"]),
gr.Textbox.update(data["negPrompt"]),
]
except Exception:
gr.Error(
"loading your prompt failed. It seems to be invalid. Your prompt table is preserved."
)
print(
"[InfiniteZoom:] Loading your prompt failed. It seems to be invalid. Your prompt table is preserved."
)
return [gr.DataFrame.update(), gr.Textbox.update()]
def clearPrompts():
return [
gr.DataFrame.update(value=[[0, "Infinite Zoom. Start over"]]),
gr.Textbox.update(""),
]
def on_ui_tabs():
with gr.Blocks(analytics_enabled=False) as infinite_zoom_interface:
gr.HTML(
"""
<p style="text-align: center;">
<a target="_blank" href="https://github.com/v8hid/infinite-zoom-automatic1111-webui"><img src="https://img.shields.io/static/v1?label=github&message=repository&color=blue&style=flat&logo=github&logoColor=white" style="display: inline;" alt="GitHub Repo"/></a>
<a href="https://discord.gg/v2nHqSrWdW"><img src="https://img.shields.io/discord/1095469311830806630?color=blue&label=discord&logo=discord&logoColor=white" style="display: inline;" alt="Discord server"></a>
</p>
"""
)
with gr.Row():
generate_btn = gr.Button(value="Generate video", variant="primary")
interrupt = gr.Button(value="Interrupt", elem_id="interrupt_training")
with gr.Row():
with gr.Column(scale=1, variant="panel"):
with gr.Tab("Main"):
main_outpaint_steps = gr.Slider(
minimum=2,
maximum=100,
step=1,
value=8,
label="Total Outpaint Steps",
info="The more it is, the longer your videos will be",
)
# safe reading json prompt
pr = shared.opts.data.get("infzoom_defPrompt", default_prompt)
if not pr:
pr = empty_prompt
try:
jpr = json.loads(pr)
validatePromptJson_throws(jpr)
except Exception:
jpr = invalid_prompt
main_prompts = gr.Dataframe(
type="array",
headers=["outpaint step", "prompt"],
datatype=["number", "str"],
row_count=1,
col_count=(2, "fixed"),
value=jpr["prompts"],
wrap=True,
)
main_negative_prompt = gr.Textbox(
value=jpr["negPrompt"], label="Negative Prompt"
)
# these button will be moved using JS unde the dataframe view as small ones
exportPrompts_button = gr.Button(
value="Export prompts",
variant="secondary",
elem_classes="sm infzoom_tab_butt",
elem_id="infzoom_exP_butt",
)
importPrompts_button = gr.UploadButton(
label="Import prompts",
variant="secondary",
elem_classes="sm infzoom_tab_butt",
elem_id="infzoom_imP_butt",
)
exportPrompts_button.click(
None,
_js="exportPrompts",
inputs=[main_prompts, main_negative_prompt],
outputs=None,
)
importPrompts_button.upload(
fn=putPrompts,
outputs=[main_prompts, main_negative_prompt],
inputs=[importPrompts_button],
)
clearPrompts_button = gr.Button(
value="Clear prompts",
variant="secondary",
elem_classes="sm infzoom_tab_butt",
elem_id="infzoom_clP_butt",
)
clearPrompts_button.click(
fn=clearPrompts,
inputs=[],
outputs=[main_prompts, main_negative_prompt],
)
main_sampler = gr.Dropdown(
label="Sampler",
choices=available_samplers,
value="Euler a",
type="value",
)
with gr.Row():
main_width = gr.Slider(
minimum=16,
maximum=2048,
value=shared.opts.data.get("infzoom_outsizeW", 512),
step=16,
label="Output Width",
)
main_height = gr.Slider(
minimum=16,
maximum=2048,
value=shared.opts.data.get("infzoom_outsizeH", 512),
step=16,
label="Output Height",
)
with gr.Row():
main_guidance_scale = gr.Slider(
minimum=0.1,
maximum=15,
step=0.1,
value=7,
label="Guidance Scale",
)
sampling_step = gr.Slider(
minimum=1,
maximum=100,
step=1,
value=50,
label="Sampling Steps for each outpaint",
)
with gr.Row():
init_image = gr.Image(type="pil", label="custom initial image")
exit_image = gr.Image(
type="pil", label="custom exit image", visible=False
) # TODO: implement exit-image rendering
batchcount_slider = gr.Slider(
minimum=1,
maximum=25,
value=shared.opts.data.get("infzoom_batchcount", 1),
step=1,
label="Batch Count",
)
with gr.Tab("Video"):
video_frame_rate = gr.Slider(
label="Frames per second",
value=30,
minimum=1,
maximum=60,
)
video_zoom_mode = gr.Radio(
label="Zoom mode",
choices=["Zoom-out", "Zoom-in"],
value="Zoom-out",
type="index",
)
video_start_frame_dupe_amount = gr.Slider(
label="number of start frame dupe",
info="Frames to freeze at the start of the video",
value=0,
minimum=1,
maximum=60,
)
video_last_frame_dupe_amount = gr.Slider(
label="number of last frame dupe",
info="Frames to freeze at the end of the video",
value=0,
minimum=1,
maximum=60,
)
video_zoom_speed = gr.Slider(
label="Zoom Speed",
value=1.0,
minimum=0.1,
maximum=20.0,
step=0.1,
info="Zoom speed in seconds (higher values create slower zoom)",
)
with gr.Tab("Outpaint"):
inpainting_denoising_strength = gr.Slider(
label="Denoising Strength", minimum=0.75, maximum=1, value=1
)
inpainting_mask_blur = gr.Slider(
label="Mask Blur", minimum=0, maximum=64, value=0
)
inpainting_fill_mode = gr.Radio(
label="Masked content",
choices=["fill", "original", "latent noise", "latent nothing"],
value="latent noise",
type="index",
)
inpainting_full_res = gr.Checkbox(label="Inpaint Full Resolution")
inpainting_padding = gr.Slider(
label="masked padding", minimum=0, maximum=256, value=0
)
with gr.Tab("Post proccess"):
upscale_do = gr.Checkbox(False, label="Enable Upscale")
upscaler_name = gr.Dropdown(
label="Upscaler",
elem_id="infZ_upscaler",
choices=[x.name for x in shared.sd_upscalers],
value=shared.sd_upscalers[0].name,
)
upscale_by = gr.Slider(
label="Upscale by factor", minimum=1, maximum=8, value=1
)
with gr.Accordion("Help", open=False):
gr.Markdown(
"""# Performance critical
Depending on amount of frames and which upscaler you choose it might took a long time to render.
Our best experience and trade-off is the R-ERSGAn4x upscaler.
"""
)
with gr.Column(scale=1, variant="compact"):
output_video = gr.Video(label="Output").style(width=512, height=512)
(
out_image,
generation_info,
html_info,
html_log,
) = create_output_panel(
"infinite-zoom", shared.opts.outdir_img2img_samples
)
generate_btn.click(
fn=wrap_gradio_gpu_call(create_zoom, extra_outputs=[None, "", ""]),
inputs=[
main_prompts,
main_negative_prompt,
main_outpaint_steps,
main_guidance_scale,
sampling_step,
init_image,
exit_image,
video_frame_rate,
video_zoom_mode,
video_start_frame_dupe_amount,
video_last_frame_dupe_amount,
inpainting_denoising_strength,
inpainting_mask_blur,
inpainting_fill_mode,
inpainting_full_res,
inpainting_padding,
video_zoom_speed,
main_width,
main_height,
batchcount_slider,
main_sampler,
upscale_do,
upscaler_name,
upscale_by,
],
outputs=[output_video, out_image, generation_info, html_info, html_log],
)
interrupt.click(fn=lambda: shared.state.interrupt(), inputs=[], outputs=[])
infinite_zoom_interface.queue()
return [(infinite_zoom_interface, "Infinite Zoom", "iz_interface")]
def on_ui_settings():
section = ("infinite-zoom", "Infinite Zoom")
shared.opts.add_option(
"outputs"
"infzoom_outpath",
shared.OptionInfo(
"",
"Path where to store your infinite video. Default is Outputs",
gr.Textbox,
{"interactive": True},
section=section,
),
)
shared.opts.add_option(
"infzoom_outSUBpath",
shared.OptionInfo(
"infinite-zooms",
"Which subfolder name to be created in the outpath. Default is 'infinite-zooms'",
gr.Textbox,
{"interactive": True},
section=section,
),
)
shared.opts.add_option(
"infzoom_outsizeW",
shared.OptionInfo(
512,
"Default width of your video",
gr.Slider,
{"minimum": 16, "maximum": 2048, "step": 16},
section=section,
),
)
shared.opts.add_option(
"infzoom_outsizeH",
shared.OptionInfo(
512,
"Default height your video",
gr.Slider,
{"minimum": 16, "maximum": 2048, "step": 16},
section=section,
),
)
shared.opts.add_option(
"infzoom_ffprobepath",
shared.OptionInfo(
"",
"Writing videos has dependency to an existing FFPROBE executable on your machine. D/L here (https://github.com/BtbN/FFmpeg-Builds/releases) your OS variant and point to your installation path",
gr.Textbox,
{"interactive": True},
section=section,
),
)
shared.opts.add_option(
"infzoom_txt2img_model",
shared.OptionInfo(
None,
"Name of your desired model to render keyframes (txt2img)",
gr.Dropdown,
lambda: {"choices": shared.list_checkpoint_tiles()},
section=section,
),
)
shared.opts.add_option(
"infzoom_inpainting_model",
shared.OptionInfo(
None,
"Name of your desired inpaint model (img2img-inpaint). Default is vanilla sd-v1-5-inpainting.ckpt ",
gr.Dropdown,
lambda: {"choices": shared.list_checkpoint_tiles()},
section=section,
),
)
shared.opts.add_option(
"infzoom_defPrompt",
shared.OptionInfo(
default_prompt,
"Default prompt-setup to start with'",
gr.Code,
{"interactive": True, "language": "json"},
section=section,
),
)
from modules import script_callbacks
from iz_helpers.ui import on_ui_tabs
from iz_helpers.settings import on_ui_settings
script_callbacks.on_ui_tabs(on_ui_tabs)
script_callbacks.on_ui_settings(on_ui_settings)
script_callbacks.on_ui_settings(on_ui_settings)

View File

@ -1,49 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"prompts": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "array",
"items": [
{
"oneOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "string"
}
]
},
{
"type": "string"
}
],
"minItems": 0,
"maxItems": 999,
"uniqueItems": false
},
"minItems": 0
},
"headers": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 2
}
},
"required": ["data", "headers"]
},
"negPrompt": {
"type": "string"
}
},
"required": ["prompts", "negPrompt"]
}