358 lines
17 KiB
Python
358 lines
17 KiB
Python
#
|
|
# Animation Script v5.0
|
|
# Inspired by Deforum Notebook
|
|
# Must have ffmpeg installed in path.
|
|
# Poor img2img implentation, will trash images that aren't moving.
|
|
#
|
|
# See https://github.com/Animator-Anon/Animator
|
|
import json
|
|
import os
|
|
import time
|
|
import gradio as gr
|
|
from scripts.functions import prepwork, sequential, loopback, export
|
|
from modules import script_callbacks, shared, sd_models, scripts, ui_common
|
|
from modules.call_queue import wrap_gradio_gpu_call
|
|
from modules.shared import cmd_opts
|
|
from modules.ui import setup_progressbar
|
|
|
|
|
|
def myprocess(*args, **kwargs):
|
|
"""
|
|
_steps
|
|
_sampler_index
|
|
_width
|
|
_height
|
|
_cfg_scale
|
|
_denoising_strength
|
|
_total_time
|
|
_fps
|
|
_smoothing
|
|
_film_interpolation
|
|
_add_noise
|
|
_noise_strength
|
|
_seed
|
|
_seed_travel
|
|
_initial_img
|
|
_loopback_mode
|
|
_prompt_interpolation
|
|
_tmpl_pos
|
|
_tmpl_neg
|
|
_key_frames
|
|
_vid_gif
|
|
_vid_mp4
|
|
_vid_webm
|
|
_style_pos
|
|
_style_neg
|
|
"""
|
|
|
|
i = 0
|
|
# if the first argument is a string that says "task(...)", it is treated as a job id
|
|
if len(args) > 0 and type(args[i]) == str and args[i][0:5] == "task(" and args[i][-1] == ")":
|
|
i += 1
|
|
|
|
# Build a dict of the settings, so we can easily pass to sub functions.
|
|
myset = {'steps': args[i + 0], # int(_steps),
|
|
'sampler_index': args[i + 1], # int(_sampler_index),
|
|
'width': args[i + 2], # int(_width),
|
|
'height': args[i + 3], # int(_height),
|
|
'cfg_scale': args[i + 4], # float(_cfg_scale),
|
|
'denoising_strength': args[i + 5], # float(_denoising_strength),
|
|
'total_time': args[i + 6], # float(_total_time),
|
|
'fps': args[i + 7], # float(_fps),
|
|
'smoothing': args[i + 8], # int(_smoothing),
|
|
'film_interpolation': args[i + 9], # int(_film_interpolation),
|
|
'add_noise': args[i + 10], # _add_noise,
|
|
'noise_strength': args[i + 11], # float(_noise_strength),
|
|
'seed': args[i + 12], # int(_seed),
|
|
'seed_travel': args[i + 13], # bool(_seed_travel),
|
|
|
|
'loopback': args[i + 15], # bool(_loopback_mode),
|
|
'prompt_interpolation': args[i + 16], # bool(_prompt_interpolation),
|
|
'tmpl_pos': args[i + 17], # str(_tmpl_pos).strip(),
|
|
'tmpl_neg': args[i + 18], # str(_tmpl_neg).strip(),
|
|
'key_frames': args[i + 19], # str(_key_frames).strip(),
|
|
'vid_gif': args[i + 20], # bool(_vid_gif),
|
|
'vid_mp4': args[i + 21], # bool(_vid_mp4),
|
|
'vid_webm': args[i + 22], # bool(_vid_webm),
|
|
'_style_pos': args[i + 23], # str(_style_pos).strip(),
|
|
'_style_neg': args[i + 24], # str(_style_neg).strip(),
|
|
'source': "",
|
|
'debug': os.path.exists('debug.txt')}
|
|
|
|
print("Script Path (myprocess): ", scripts.basedir())
|
|
|
|
# Sort out output folder
|
|
if len(shared.opts.animatoranon_output_folder.strip()) > 0:
|
|
output_parent_folder = shared.opts.animatoranon_output_folder.strip()
|
|
elif myset['loopback']:
|
|
output_parent_folder = shared.opts.outdir_img2img_samples
|
|
else:
|
|
output_parent_folder = shared.opts.outdir_samples
|
|
output_parent_folder = os.path.join(output_parent_folder, time.strftime('%Y%m%d%H%M%S'))
|
|
if not os.path.exists(output_parent_folder):
|
|
os.makedirs(output_parent_folder)
|
|
myset['output_path'] = output_parent_folder
|
|
|
|
# Save the parameters to a file.
|
|
settings_filename = os.path.join(myset['output_path'], "settings.txt")
|
|
with open(settings_filename, "w+", encoding="utf-8") as f:
|
|
json.dump(myset, f, ensure_ascii=False, indent=4)
|
|
|
|
# Have to add the initial picture later on as it doesn't serialise well.
|
|
myset['initial_img'] = args[i + 14] # _initial_img
|
|
|
|
# Prepare the processing objects with default values.
|
|
ptxt, pimg = prepwork.setup_processors(myset)
|
|
|
|
# Make bat files before video incase we interrupt it, and so we can generate vids on the fly.
|
|
export.make_batch_files(myset)
|
|
|
|
# tmp_live_previews_enable = shared.opts.live_previews_enable
|
|
# shared.opts.live_previews_enable = False
|
|
|
|
shared.state.interrupted = False
|
|
if myset['loopback']:
|
|
result = loopback.main_process(myset, ptxt, pimg)
|
|
else:
|
|
result = sequential.main_process(myset, ptxt)
|
|
|
|
if not shared.state.interrupted:
|
|
# Generation not cancelled, go ahead and render the videos without stalling.
|
|
export.make_videos(myset)
|
|
|
|
shared.state.end()
|
|
|
|
# shared.opts.live_previews_enable = tmp_live_previews_enable
|
|
|
|
return result, "done"
|
|
|
|
|
|
def ui_block_generation():
|
|
with gr.Blocks():
|
|
with gr.Accordion("Generation Parameters", open=False):
|
|
gr.HTML("<p>These parameters mirror those in txt2img and img2img mode. They are used "
|
|
"to create the initial image in loopback mode.</p>")
|
|
steps = gr.Slider(minimum=1, maximum=150, step=1, label="Sampling Steps", value=20)
|
|
from modules.sd_samplers import samplers_for_img2img
|
|
sampler_index = gr.Radio(label='Sampling method',
|
|
choices=[x.name for x in samplers_for_img2img],
|
|
value=samplers_for_img2img[0].name, type="index")
|
|
|
|
with gr.Group():
|
|
with gr.Row():
|
|
width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512)
|
|
height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512)
|
|
with gr.Group():
|
|
with gr.Row():
|
|
cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='CFG Scale', value=7.0)
|
|
denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.01,
|
|
label='Denoising strength', value=0.40)
|
|
with gr.Row():
|
|
seed = gr.Number(label='Seed', value=-1)
|
|
seed_travel = gr.Checkbox(label='Seed Travel', value=False)
|
|
|
|
with gr.Row():
|
|
with gr.Accordion("Initial Image", open=False):
|
|
initial_img = gr.inputs.Image(label='Upload starting image',
|
|
image_mode='RGB',
|
|
type='pil',
|
|
optional=True)
|
|
|
|
return steps, sampler_index, width, height, cfg_scale, denoising_strength, seed, seed_travel, initial_img
|
|
|
|
|
|
def ui_block_animation():
|
|
with gr.Blocks():
|
|
with gr.Accordion("Animation Parameters", open=False):
|
|
gr.HTML("Parameters for the animation. Total length in seconds will determine the length of"
|
|
" the animation.<br>"
|
|
"<ul>"
|
|
"<li>Total Animation Length: How long the resulting animation will be. Total number "
|
|
"of frames rendered will be this time * FPS</li> "
|
|
"<li>Smoothing Frames: The number of additional intermediate frames to insert between "
|
|
"every rendered frame. These will be a faded merge of the surrounding frames.</li> "
|
|
"<li>Add Noise: Add simple noise to the image in the form of random coloured circles. "
|
|
"These can help the loopback mode create new content.</li> "
|
|
"<li>Loopback: This is the img2img loopback mode where the resulting image, "
|
|
"before post processing, is pre-processed and fed back in..</li> "
|
|
"<li>Prop Folder: Path to folder containing transparent .png files that can be "
|
|
"superimposed in pre or post processing.</li> "
|
|
"</ul>")
|
|
with gr.Row():
|
|
total_time = gr.Number(label="Total Animation Length (s)", lines=1, value=10.0)
|
|
fps = gr.Number(label="Framerate", lines=1, value=15.0)
|
|
with gr.Row():
|
|
smoothing = gr.Slider(label="Smoothing_Frames", minimum=0, maximum=32, step=1, value=0)
|
|
film_interpolation = gr.Checkbox(label="FILM Interpolation", value=False)
|
|
with gr.Row():
|
|
add_noise = gr.Checkbox(label="Add_Noise", value=False)
|
|
noise_strength = gr.Slider(label="Noise Strength", minimum=0.0, maximum=1.0, step=0.01,
|
|
value=0.10)
|
|
with gr.Row():
|
|
loopback_mode = gr.Checkbox(label='Loopback Mode', value=True)
|
|
|
|
return total_time, fps, smoothing, film_interpolation, add_noise, noise_strength, loopback_mode
|
|
|
|
|
|
def ui_block_processing():
|
|
with gr.Blocks():
|
|
with gr.Accordion("Prompt Template, applied to each keyframe below", open=False):
|
|
gr.HTML("<p>Prompt interpolation will set both before and after prompts with a weighting that linearly "
|
|
"grows from the current prompt to the next.<br>"
|
|
"Similar to styles, these prompt templates are applied to every frame in addition to the"
|
|
" keyframe / VTT prompts below.</p>")
|
|
prompt_interpolation = gr.Checkbox(label='Prompt Interpolation', value=True)
|
|
with gr.Row():
|
|
tmpl_pos = gr.Textbox(label="Positive Prompts", lines=1, value="")
|
|
try:
|
|
style_pos = gr.Dropdown(label="Use pos prompts from style",
|
|
choices=[k for k, v in shared.prompt_styles.styles.items()],
|
|
value=next(iter(shared.prompt_styles.styles.keys())))
|
|
except StopIteration as e:
|
|
style_pos = gr.Dropdown(label="Use pos prompts from style",
|
|
choices=[k for k, v in shared.prompt_styles.styles.items()],
|
|
value=None)
|
|
with gr.Row():
|
|
tmpl_neg = gr.Textbox(label="Negative Prompts", lines=1, value="")
|
|
try:
|
|
style_neg = gr.Dropdown(label="Use neg prompts from style",
|
|
choices=[k for k, v in shared.prompt_styles.styles.items()],
|
|
value=next(iter(shared.prompt_styles.styles.keys())))
|
|
except StopIteration as e:
|
|
style_neg = gr.Dropdown(label="Use neg prompts from style",
|
|
choices=[k for k, v in shared.prompt_styles.styles.items()],
|
|
value=None)
|
|
|
|
return prompt_interpolation, tmpl_pos, style_pos, tmpl_neg, style_neg
|
|
|
|
|
|
def ui_block_keyframes():
|
|
with gr.Blocks():
|
|
with gr.Accordion("Supported Keyframes:", open=False):
|
|
gr.HTML("Copy and paste these templates, replace values as required.<br>"
|
|
"time_s | source | video, images, img2img | path<br>"
|
|
"time_s | prompt | positive_prompts | negative_prompts<br>"
|
|
"time_s | template | positive_prompts | negative_prompts<br>"
|
|
"time_s | prompt_from_png | file_path<br>"
|
|
"time_s | prompt_vtt | vtt_filepath<br>"
|
|
"time_s | seed | new_seed_int<br>"
|
|
"time_s | denoise | denoise_value<br>"
|
|
"time_s | cfg_scale | cfg_scale_value<br>"
|
|
"time_s | transform | zoom | x_shift | y_shift | rotation<br>"
|
|
"time_s | noise | added_noise_strength<br>"
|
|
"time_s | set_text | textblock_name | text_prompt | x_pos | y_pos | width | height | fore_color |"
|
|
" back_color | font_name<br> "
|
|
"time_s | clear_text | textblock_name<br>"
|
|
"time_s | prop | prop_filename | x_pos | y_pos | scale | rotation<br>"
|
|
"time_s | set_stamp | stamp_name | stamp_filename | x_pos | y_pos | scale | rotation<br>"
|
|
"time_s | clear_stamp | stamp_name<br>"
|
|
"time_s | col_set<br>"
|
|
"time_s | col_clear<br>"
|
|
"time_s | model | " + ", ".join(
|
|
sorted(
|
|
[x.model_name for x in sd_models.checkpoints_list.values()]
|
|
)) + "</p>")
|
|
|
|
return gr.Textbox(label="Keyframes:", lines=5, value="")
|
|
|
|
|
|
def ui_block_settings():
|
|
with gr.Blocks():
|
|
gr.HTML("Persistent settings moved into the main settings tab, in the group <b>Animator Extension</b>")
|
|
|
|
|
|
def ui_block_output():
|
|
with gr.Blocks():
|
|
with gr.Accordion("Output Block", open=False):
|
|
gr.HTML("<p>Video creation options. Check the formats you want automatically created."
|
|
"Otherwise manually execute the batch files in the output folder.</p>")
|
|
|
|
with gr.Row():
|
|
vid_gif = gr.Checkbox(label="GIF", value=False)
|
|
vid_mp4 = gr.Checkbox(label="MP4", value=False)
|
|
vid_webm = gr.Checkbox(label="WEBM", value=True)
|
|
|
|
with gr.Row():
|
|
btn_proc = gr.Button(value="Process", variant='primary', elem_id="animator_extension_procbutton")
|
|
btn_stop = gr.Button(value='Stop', elem_id="animator_extension_stopbutton")
|
|
|
|
# gallery = gr.Gallery(label="gallery", show_label=True).style(grid=5, height="auto")
|
|
|
|
return vid_gif, vid_mp4, vid_webm, btn_proc, btn_stop
|
|
|
|
|
|
#
|
|
# Basic layout of page
|
|
#
|
|
def on_ui_tabs():
|
|
# print("on_ui_tabs")
|
|
with gr.Blocks(analytics_enabled=False) as animator_tabs:
|
|
with gr.Row():
|
|
# left Column
|
|
with gr.Column():
|
|
with gr.Tab("Generation"):
|
|
steps, sampler_index, width, height, cfg_scale, denoising_strength, seed, seed_travel, image_list =\
|
|
ui_block_generation()
|
|
|
|
total_time, fps, smoothing, film_interpolation, add_noise, noise_strength, loopback_mode =\
|
|
ui_block_animation()
|
|
|
|
prompt_interpolation, tmpl_pos, style_pos, tmpl_neg, style_neg = ui_block_processing()
|
|
|
|
key_frames = ui_block_keyframes()
|
|
|
|
with gr.Tab("Persistent Settings"):
|
|
ui_block_settings()
|
|
|
|
# Right Column
|
|
with gr.Column():
|
|
vid_gif, vid_mp4, vid_webm, btn_proc, btn_stop = ui_block_output()
|
|
|
|
with gr.Blocks(variant="panel"):
|
|
# aa_htmlinfo_x elem_id=f'html_info_x_animator_extension'
|
|
# aa_htmlinfo elem_id=f'html_info_animator_extension'
|
|
# aa_htmllog elem_id=f'html_log_animator_extension'
|
|
# aa_info alem_id=f'generation_info_animator_extension'
|
|
# aa_gallery elem_id=f"animator_extension_gallery"
|
|
# result_gallery, generation_info if tabname != "extras" else html_info_x, html_info, html_log
|
|
aa_gallery, aa_htmlinfo_x, aa_htmlinfo, aa_htmllog = \
|
|
ui_common.create_output_panel("animator_extension", shared.opts.animatoranon_output_folder)
|
|
|
|
btn_proc.click(fn=wrap_gradio_gpu_call(myprocess, extra_outputs=[gr.update()]),
|
|
_js="start_animator",
|
|
inputs=[aa_htmllog, steps, sampler_index, width, height, cfg_scale, denoising_strength,
|
|
total_time, fps, smoothing, film_interpolation, add_noise, noise_strength, seed,
|
|
seed_travel, image_list, loopback_mode, prompt_interpolation,
|
|
tmpl_pos, tmpl_neg, key_frames, vid_gif, vid_mp4, vid_webm, style_pos, style_neg],
|
|
outputs=[aa_gallery, aa_htmlinfo])
|
|
|
|
btn_stop.click(fn=lambda: shared.state.interrupt())#,
|
|
#_js="reenable_animator")
|
|
|
|
return (animator_tabs, "Animator", "animator_extension"),
|
|
|
|
|
|
#
|
|
# Define my options that will be stored in webui config
|
|
#
|
|
def on_ui_settings():
|
|
# print("on_ui_settings")
|
|
mysection = ('animatoranon', 'Animator Extension')
|
|
|
|
shared.opts.add_option("animatoranon_film_folder",
|
|
shared.OptionInfo('C:/AI/frame_interpolation/film.bat',
|
|
label="FILM batch or script file, including full path",
|
|
section=mysection))
|
|
shared.opts.add_option("animatoranon_prop_folder",
|
|
shared.OptionInfo('c:/ai/props',
|
|
label="Prop folder",
|
|
section=mysection))
|
|
shared.opts.add_option("animatoranon_output_folder",
|
|
shared.OptionInfo('',
|
|
label="New output folder",
|
|
section=mysection))
|
|
|
|
|
|
script_callbacks.on_ui_tabs(on_ui_tabs)
|
|
script_callbacks.on_ui_settings(on_ui_settings)
|