-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathilc-animator.py
More file actions
401 lines (321 loc) · 14.2 KB
/
ilc-animator.py
File metadata and controls
401 lines (321 loc) · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
#
# ILC Animation Script v0.1
#
# Forked from https://github.com/Animator-Anon/Animator
#
# Must have ffmpeg installed in path.
#
# The main idea for the script was to smoothly only animate one parameter - the denoising parameter.
#
#
from PIL import Image, ImageFilter, ImageDraw, ImageFont
import os
import time
from scripts.ilc_mcommon import getILCBase64ImageSingleton, makePilILCMandelbrotLogo, ILC_LOGGER
import modules.scripts as scripts
import gradio as gr
from modules import processing, shared, sd_samplers, images, sd_models
from modules.processing import Processed, process_images
from modules.shared import opts, cmd_opts, state
import random
import subprocess
import numpy as np
import json
import cv2
import math
ILC_PLUGIN_NAME = "ILC-Simple-Animator"
ILC_PLUGIN_DESCRIPTION = """
Meant to play around with plugin development.
It is used by Christian K. for animating just denoise and cfg_scale parameters. Christian is hosting the i-love-chaos brand, mainly a technology adoring place with special interest to deterministic chaos.
These plugins are here for reference wether anyone is interested in seeing how something actually was done.
"""
ILC_PLUGIN_VERSION = 1.0
ILC_COMPLETE_NAME = ILC_PLUGIN_NAME+'@'+str(ILC_PLUGIN_VERSION)
LOGGER = ILC_LOGGER(ILC_PLUGIN_NAME, ILC_PLUGIN_VERSION)
def easeInOutExpo(x: float) -> float:
if x == 0:
return 0
if x == 1:
return 1
if x < 0.5:
return math.pow(2, 20 * x - 10) / 2
if x >= 0.5:
return (2 - math.pow(2, -20 * x + 10)) / 2
return 0
def easeInOutSine(x):
return -(math.cos(math.pi * x) - 1) / 2
def easeInOutCubic(x):
if x < 0.5:
return 4*x*x*x
if x >= 0.5:
return 1-math.pow(-2*x+2, 3)/2
def lerp(a: float, b: float, t: float) -> float:
return (1.0 - t) * a + t * b
def make_gif(filepath, filename, fps, create_vid, create_bat):
# Create filenames
in_filename = f"{str(filename)}_%05d.png"
out_filename = f"{str(filename)}.gif"
LOGGER("GIF", filename)
# Build cmd for bat output, local file refs only
cmd = [
'ffmpeg',
'-y',
'-r', str(fps),
'-i', in_filename.replace("%", "%%"),
out_filename
]
# create bat file
if create_bat:
LOGGER(
'Creating .bat file, use it when calculation is interrupted, or to preview output')
with open(os.path.join(filepath, filename+'-'+"makegif.bat"), "w+", encoding="utf-8") as f:
f.writelines([" ".join(cmd), "\r\n", "pause"])
# Fix paths for normal output
cmd[5] = os.path.join(filepath, in_filename)
cmd[6] = os.path.join(filepath, out_filename)
# create output if requested
if create_vid:
LOGGER('Creating GIF video file - yes, a gif is a video file as well now',
filepath, out_filename)
subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# stdout, stderr = process.communicate()
# if process.returncode != 0:
# LOGGER(stderr)
# raise RuntimeError(stderr)
def make_webm(filepath, filename, fps, create_vid, create_bat):
in_filename = f"{str(filename)}_%05d.png"
out_filename = f"{str(filename)}.webm"
LOGGER("WEBM", filename)
cmd = [
'ffmpeg',
'-y',
'-framerate', str(fps),
'-i', in_filename.replace("%", "%%"),
'-crf', str(50),
'-preset', 'veryfast',
out_filename
]
if create_bat:
LOGGER(
'Creating .bat file, use it when calculation is interrupted, or to preview output')
with open(os.path.join(filepath, filename+'-'+"makewebm.bat"), "w+", encoding="utf-8") as f:
f.writelines([" ".join(cmd), "\r\n", "pause"])
cmd[5] = os.path.join(filepath, in_filename)
cmd[10] = os.path.join(filepath, out_filename)
if create_vid:
LOGGER('Creating WEBM video file', filepath, out_filename)
subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# stdout, stderr = process.communicate()
# if process.returncode != 0:
# LOGGER(stderr)
# raise RuntimeError(stderr)
def make_mp4(filepath, filename, fps, create_vid, create_bat):
in_filename = f"{str(filename)}_%05d.png"
out_filename = f"{str(filename)}.mp4"
LOGGER("MP4", filename)
cmd = [
'ffmpeg',
'-y',
'-r', str(fps),
'-i', in_filename.replace("%", "%%"),
'-c:v', 'libx264',
'-vf',
f'fps={fps}',
'-pix_fmt', 'yuv420p',
'-crf', '17',
'-preset', 'veryfast',
out_filename
]
if create_bat:
LOGGER(
'Creating .bat file, use it when calculation is interrupted, or to preview output')
with open(os.path.join(filepath, filename+'-' + "makemp4.bat"), "w+", encoding="utf-8") as f:
f.writelines([" ".join(cmd), "\r\n", "pause"])
cmd[5] = os.path.join(filepath, in_filename)
cmd[16] = os.path.join(filepath, out_filename)
if create_vid:
LOGGER('Creating MP4 video file', filepath, out_filename)
subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# stdout, stderr = process.communicate()
# if process.returncode != 0:
# LOGGER(stderr)
# raise RuntimeError(stderr)
class Script(scripts.Script):
def title(self):
return ILC_COMPLETE_NAME
def show(self, is_img2img):
return True
def ui(self, is_img2img):
with gr.Blocks() as demo22:
with gr.Column(variant="panel"):
with gr.Row():
gr.HTML(
"""<a href="https://ilovechaos.org" target="chaos">
<img style="width:90%; border-radius: 1em; float:left;margin:1em" src="data:image/png;base64, """+getILCBase64ImageSingleton().decode('utf-8')+"""" alt="I-Love-Chaos Logo" />
</a>""")
gr.Markdown("""
### """+ILC_COMPLETE_NAME+"""
"""+ILC_PLUGIN_DESCRIPTION+"""
this particular plugin is derived and most code reused from
[Animator Script](https://github.com/Animator-Anon/Animator)
We follow a gui first directive here, so we reduce all possible parameters and provide sliders.
"""
)
# gr.Image(makePilILCMandelbrotLogo())
with gr.Column():
with gr.Tab("Edit"):
with gr.Column(visible=is_img2img):
denoise_sliderstart = gr.Slider(
label="denoise_start", minimum=0.0, maximum=1.0, value=0.0)
denoise_sliderend = gr.Slider(
label="denoise_end", minimum=0.0, maximum=1.0, value=1.0)
with gr.Column():
cfgscalesliderstart = gr.Slider(
label="cfg scale start", minimum=0.0, maximum=30.0, value=5)
cfgscale_sliderend = gr.Slider(
label="cfg scale end", minimum=0.0, maximum=30.0, value=7)
# end ck
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.Column():
totaltime = gr.Slider(
label="Total Animation Length (s)", minimum=0.0, maximum=600.0, value=30)
fps = gr.Slider(
label="Framerate", minimum=0.0, maximum=120, value=15, step=1)
with gr.Tab("Info/Help"):
gr.Markdown("""
##### ILC Simple Animator
"""+ILC_PLUGIN_DESCRIPTION+"""
The interpolation is eased by SineInOut.
###### Reference:
| Parameter | Description |
|---|---|
| denoise_start/end | the denoise animation values |
| cfg scale start/end | The cfg scale animation values |
| animation length| The length of the animation in seconds |
| fps | The frames to be generated per second |
"""
)
i1 = gr.Markdown(
"""
##### Save videos
'ffmpeg' is required to be found in $PATH be sure to have it in place before starting webui:
Windows PowerShell:
$env:Path +=";[PATH TO YOUR FFMPEG BIN]"
"""
)
return [totaltime, fps, vid_gif, vid_mp4, vid_webm, denoise_sliderstart, denoise_sliderend, cfgscalesliderstart, cfgscale_sliderend]
def run(self, p, totaltimeIn, fpsIn, vid_gif, vid_mp4, vid_webm, denoise_start, denoise_end, cfg_scale_start, cfg_scale_end):
initial_seed_save = p.seed
all_images_collection = []
all_images = []
# for step in range(0,p.n_iter):
# Fix variable types, i.e. text boxes giving strings.
p.seed = initial_seed_save
totaltime = float(totaltimeIn)
fps = float(fpsIn)
cfg_scale_end = float(cfg_scale_end)
cfg_scale_start = float(cfg_scale_start)
denoise_start = float(denoise_start)
denoise_end = float(denoise_end)
apply_colour_corrections = False
outfilename = time.strftime('%Y%m%d%H%M%S')
outpath = os.path.join(p.outpath_samples, outfilename)
if not os.path.exists(outpath):
os.mkdir(outpath)
p.do_not_save_samples = True
p.do_not_save_grid = True
processing.fix_seed(p)
batch_count = p.n_iter
# Save extra parameters for the UI
p.extra_generation_params = {
"Create GIF": vid_gif,
"Create MP4": vid_mp4,
"Create WEBM": vid_webm,
"Total Time (s)": totaltime,
"FPS": fps,
"Denoise Start": denoise_start,
"Denoise End": denoise_end,
"CFG Scale Value Start": cfg_scale_start,
"CFG Scale Value End": cfg_scale_end,
"End": denoise_end,
}
# save settings, just dump out the extra_generation dict
settings_filename = os.path.join(
outpath, f"{str(outfilename)}_settings.txt")
with open(settings_filename, "w+", encoding="utf-8") as f:
json.dump(dict(p.extra_generation_params),
f, ensure_ascii=False, indent=4)
# This doesn't work, still some information missing if you don't drop an image into the img2img page.
# if p.init_images[0] is None:
# a = np.random.rand(p.width, p.height, 3) * 255
# p.init_images.append(Image.fromarray(a.astype('uint8')).convert('RGB'))
# p.n_iter = 1
# output_images, info = None, None
initial_seed = None
initial_info = None
# Make bat files before we start rendering video, so we could run them manually to preview output.
for i in range(0, p.n_iter):
make_gif(outpath, str(i+1)+'-'+outfilename, fps, False, True)
make_mp4(outpath, str(i+1)+'-'+outfilename, fps, False, True)
make_webm(outpath, str(i+1)+'-' + outfilename, fps, False, True)
frame_count = int(fps * totaltime)
p.batch_size = 1
state.job_count = frame_count * batch_count
# initial_color_corrections = [
# processing.setup_color_correction(p.init_images[0])]
# Iterate through range of frames
for frame_no in range(frame_count):
if state.interrupted:
# Interrupt button pressed in WebUI
break
normalized_frame = (frame_no)/(frame_count-1)
eased_frame = easeInOutSine(normalized_frame)
# ck full interpolation 0...1 of denoising strength hard coded
p.denoising_strength = lerp(
denoise_start, denoise_end, eased_frame)
p.cfg_scale = lerp(
cfg_scale_start, cfg_scale_end, eased_frame)
LOGGER("Denoise:", p.denoising_strength)
LOGGER("cfg_scale", p.cfg_scale)
LOGGER("seed", p.seed)
LOGGER("frame", frame_no+1, "/", frame_count)
state.job = f"Iteration {frame_no + 1}/{frame_count}"
# Process current frame
processed = processing.process_images(p)
if initial_seed is None:
initial_seed = processed.seed
initial_info = processed.info
index = 0
for imga in processed.images:
index = index+1
imga.save(os.path.join(
outpath, f"{index}-{outfilename}_{frame_no:05}.png"))
if (len(all_images_collection) < index):
all_images_collection.append([])
if (frame_no % int(fps) == 0 or frame_no == frame_count-1):
# all_images.append(imga)
all_images_collection[index-1].append(imga)
for i in range(0, p.n_iter):
try: # If not interrupted, make requested movies. Otherise the bat files exist.
make_gif(outpath, str(i+1)+'-' + outfilename, fps, vid_gif &
(not state.interrupted), False)
make_mp4(outpath, str(i+1)+'-' + outfilename, fps, vid_mp4 &
(not state.interrupted), False)
make_webm(outpath, str(i+1)+'-' + outfilename, fps, vid_webm &
(not state.interrupted), False)
except Exception as e:
LOGGER("Error happened", e)
LOGGER(
"Presumably a missing ffmpeg installation is the cause, make sure it is in the search $PATH")
dickies = []
for images in all_images_collection:
for image in images:
dickies.append(image)
processed = Processed(p, dickies, initial_seed, initial_info)
return processed