parent
a67683d318
commit
9ee09ac842
Binary file not shown.
After Width: | Height: | Size: 2.7 MiB |
Binary file not shown.
After Width: | Height: | Size: 443 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.3 MiB |
@ -0,0 +1,40 @@
|
||||
from imaginairy import ImaginePrompt, LazyLoadingImage, imagine_image_files
|
||||
|
||||
|
||||
def main():
|
||||
prompts = [
|
||||
ImaginePrompt(
|
||||
"make her wear clown makeup",
|
||||
seed=952243488,
|
||||
model="edit",
|
||||
init_image=LazyLoadingImage(
|
||||
url="https://github.com/brycedrennan/imaginAIry/raw/2a3e19f5a1a864fcee18c23f17aea02cc0f61bbf/assets/girl_with_a_pearl_earring.jpg"
|
||||
),
|
||||
steps=30,
|
||||
),
|
||||
ImaginePrompt(
|
||||
"make her wear clown makeup",
|
||||
seed=952243488,
|
||||
model="edit",
|
||||
init_image=LazyLoadingImage(
|
||||
url="https://github.com/brycedrennan/imaginAIry/raw/2a3e19f5a1a864fcee18c23f17aea02cc0f61bbf/assets/girl_with_a_pearl_earring.jpg"
|
||||
),
|
||||
steps=30,
|
||||
),
|
||||
ImaginePrompt(
|
||||
"make it a color professional photo headshot",
|
||||
negative_prompt="old, ugly, blurry",
|
||||
seed=390919410,
|
||||
model="edit",
|
||||
init_image=LazyLoadingImage(
|
||||
url="https://github.com/brycedrennan/imaginAIry/raw/2a3e19f5a1a864fcee18c23f17aea02cc0f61bbf/assets/mona-lisa.jpg"
|
||||
),
|
||||
steps=30,
|
||||
),
|
||||
]
|
||||
|
||||
imagine_image_files(prompts, outdir="./outputs", make_gif=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -0,0 +1,112 @@
|
||||
import os.path
|
||||
|
||||
import cv2
|
||||
import torch
|
||||
|
||||
from imaginairy.img_utils import (
|
||||
add_caption_to_image,
|
||||
imgpaths_to_imgs,
|
||||
model_latents_to_pillow_imgs,
|
||||
pillow_img_to_opencv_img,
|
||||
)
|
||||
from imaginairy.utils import shrink_list
|
||||
|
||||
|
||||
def make_bounce_animation(
|
||||
imgs,
|
||||
outpath,
|
||||
transition_duration_ms=500,
|
||||
start_pause_duration_ms=1000,
|
||||
end_pause_duration_ms=2000,
|
||||
):
|
||||
first_img = imgs[0]
|
||||
last_img = imgs[-1]
|
||||
middle_imgs = imgs[1:-1]
|
||||
max_fps = 20
|
||||
max_frames = int(round(transition_duration_ms / 1000 * max_fps))
|
||||
min_duration = int(1000 / 20)
|
||||
if middle_imgs:
|
||||
progress_duration = int(round(transition_duration_ms / len(middle_imgs)))
|
||||
else:
|
||||
progress_duration = 0
|
||||
progress_duration = max(progress_duration, min_duration)
|
||||
|
||||
middle_imgs = shrink_list(middle_imgs, max_frames)
|
||||
|
||||
frames = [first_img] + middle_imgs + [last_img] + list(reversed(middle_imgs))
|
||||
|
||||
# convert from latents
|
||||
converted_frames = []
|
||||
for frame in frames:
|
||||
if isinstance(frame, torch.Tensor):
|
||||
frame = model_latents_to_pillow_imgs(frame)[0]
|
||||
converted_frames.append(frame)
|
||||
frames = converted_frames
|
||||
|
||||
durations = (
|
||||
[start_pause_duration_ms]
|
||||
+ [progress_duration] * len(middle_imgs)
|
||||
+ [end_pause_duration_ms]
|
||||
+ [progress_duration] * len(middle_imgs)
|
||||
)
|
||||
|
||||
make_animation(imgs=frames, outpath=outpath, frame_duration_ms=durations)
|
||||
|
||||
|
||||
def make_animation(imgs, outpath, frame_duration_ms=100, captions=None):
|
||||
imgs = imgpaths_to_imgs(imgs)
|
||||
ext = os.path.splitext(outpath)[1].lower().strip(".")
|
||||
|
||||
if captions:
|
||||
if len(captions) != len(imgs):
|
||||
raise ValueError("Captions and images must be of same length.")
|
||||
for img, caption in zip(imgs, captions):
|
||||
add_caption_to_image(img, caption)
|
||||
|
||||
if ext == "gif":
|
||||
make_gif_animation(
|
||||
imgs=imgs, outpath=outpath, frame_duration_ms=frame_duration_ms
|
||||
)
|
||||
elif ext == "mp4":
|
||||
make_mp4_animation(
|
||||
imgs=imgs, outpath=outpath, frame_duration_ms=frame_duration_ms
|
||||
)
|
||||
|
||||
|
||||
def make_gif_animation(imgs, outpath, frame_duration_ms=100, loop=0):
|
||||
imgs = imgpaths_to_imgs(imgs)
|
||||
imgs[0].save(
|
||||
outpath,
|
||||
save_all=True,
|
||||
append_images=imgs[1:],
|
||||
duration=frame_duration_ms,
|
||||
loop=loop,
|
||||
optimize=False,
|
||||
)
|
||||
|
||||
|
||||
def make_mp4_animation(imgs, outpath, frame_duration_ms=50, fps=30, codec="mp4v"):
|
||||
imgs = imgpaths_to_imgs(imgs)
|
||||
frame_size = imgs[0].size
|
||||
fourcc = cv2.VideoWriter_fourcc(*codec)
|
||||
out = cv2.VideoWriter(outpath, fourcc, fps, frame_size)
|
||||
if not isinstance(frame_duration_ms, list):
|
||||
frame_duration_ms = [frame_duration_ms] * len(imgs)
|
||||
try:
|
||||
for image in select_images_by_duration_at_fps(imgs, frame_duration_ms, fps):
|
||||
image = pillow_img_to_opencv_img(image)
|
||||
out.write(image)
|
||||
finally:
|
||||
out.release()
|
||||
|
||||
|
||||
def select_images_by_duration_at_fps(images, durations_ms, fps=30):
|
||||
"""select the proper image to show for each frame of a video."""
|
||||
for i, image in enumerate(images):
|
||||
duration = durations_ms[i] / 1000
|
||||
num_frames = int(round(duration * fps))
|
||||
print(
|
||||
f"Showing image {i} for {num_frames} frames for {durations_ms[i]}ms at {fps} fps."
|
||||
)
|
||||
for j in range(num_frames):
|
||||
yield image
|
@ -0,0 +1,74 @@
|
||||
import csv
|
||||
import re
|
||||
from copy import copy
|
||||
|
||||
from imaginairy import ImaginePrompt
|
||||
from imaginairy.utils import frange
|
||||
|
||||
|
||||
def parse_schedule_str(schedule_str):
|
||||
"""Parse a schedule string into a list of values."""
|
||||
pattern = re.compile(r"([a-zA-Z0-9_-]+)\[([a-zA-Z0-9_:,. -]+)\]")
|
||||
match = pattern.match(schedule_str)
|
||||
if not match:
|
||||
raise ValueError(f"Invalid kwarg schedule: {schedule_str}")
|
||||
|
||||
arg_name = match.group(1).replace("-", "_")
|
||||
if not hasattr(ImaginePrompt(), arg_name):
|
||||
raise ValueError(
|
||||
f"Invalid kwarg schedule. Not a valid argument name: {arg_name}"
|
||||
)
|
||||
|
||||
arg_values = match.group(2)
|
||||
if ":" in arg_values:
|
||||
start, end, step = arg_values.split(":")
|
||||
arg_values = list(frange(float(start), float(end), float(step)))
|
||||
else:
|
||||
arg_values = parse_csv_line(arg_values)
|
||||
return arg_name, arg_values
|
||||
|
||||
|
||||
def parse_schedule_strs(schedule_strs):
|
||||
"""Parse and validate input prompt schedules."""
|
||||
schedules = {}
|
||||
for schedule_str in schedule_strs:
|
||||
arg_name, arg_values = parse_schedule_str(schedule_str)
|
||||
schedules[arg_name] = arg_values
|
||||
|
||||
# Validate that all schedules have the same length
|
||||
schedule_lengths = [len(v) for v in schedules.values()]
|
||||
if len(set(schedule_lengths)) > 1:
|
||||
raise ValueError("All schedules must have the same length")
|
||||
|
||||
return schedules
|
||||
|
||||
|
||||
def prompt_mutator(prompt, schedules):
|
||||
"""
|
||||
Given a prompt and a list of kwarg schedules, return a series of prompts that follow the schedule.
|
||||
|
||||
kwarg_schedules example:
|
||||
{
|
||||
"prompt_strength": [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
|
||||
}
|
||||
|
||||
"""
|
||||
schedule_length = len(list(schedules.values())[0])
|
||||
for i in range(schedule_length):
|
||||
new_prompt = copy(prompt)
|
||||
for attr_name, schedule in schedules.items():
|
||||
setattr(new_prompt, attr_name, schedule[i])
|
||||
new_prompt.validate()
|
||||
yield new_prompt
|
||||
|
||||
|
||||
def parse_csv_line(line):
|
||||
reader = csv.reader([line])
|
||||
for row in reader:
|
||||
parsed_row = []
|
||||
for value in row:
|
||||
try:
|
||||
parsed_row.append(float(value))
|
||||
except ValueError:
|
||||
parsed_row.append(value)
|
||||
return parsed_row
|
@ -0,0 +1,21 @@
|
||||
import pytest
|
||||
|
||||
from imaginairy.prompt_schedules import parse_schedule_str
|
||||
from imaginairy.utils import frange
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"schedule_str,expected",
|
||||
[
|
||||
("prompt_strength[2:40:1]", ("prompt_strength", list(range(2, 40)))),
|
||||
("prompt_strength[2:40:0.5]", ("prompt_strength", list(frange(2, 40, 0.5)))),
|
||||
("prompt_strength[2,5,10,15]", ("prompt_strength", [2, 5, 10, 15])),
|
||||
(
|
||||
"prompt_strength[red,blue,10,15]",
|
||||
("prompt_strength", ["red", "blue", 10, 15]),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_parse_schedule_str(schedule_str, expected):
|
||||
cleaned_schedules = parse_schedule_str(schedule_str)
|
||||
assert cleaned_schedules == expected
|
Loading…
Reference in New Issue