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
commit
d7263b3f38
|
|
@ -129,3 +129,4 @@ dmypy.json
|
|||
.pyre/
|
||||
.vscode/settings.json
|
||||
.DS_Store
|
||||
/.vs
|
||||
|
|
|
|||
|
|
@ -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,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("")
|
||||
]
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
@ -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(""),
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
|
@ -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)
|
||||
)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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] || "";
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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"]
|
||||
}
|
||||
Loading…
Reference in New Issue