|
import cv2 |
|
from tqdm import tqdm |
|
import numpy as np |
|
from PIL import Image, ImageDraw, ImageFont, ImageChops |
|
import imageio |
|
from pygifsicle import optimize |
|
|
|
import torchvision.transforms.functional as TF |
|
|
|
from visual_anagrams.views import get_views |
|
from visual_anagrams.utils import get_courier_font_path |
|
|
|
|
|
def draw_text(image, text, fill=(0,0,0), frame_size=384, im_size=256): |
|
image = image.copy() |
|
|
|
|
|
font_path = get_courier_font_path() |
|
font_size = 16 |
|
|
|
|
|
draw = ImageDraw.Draw(image) |
|
font = ImageFont.truetype(font_path, font_size) |
|
|
|
|
|
|
|
text_position = (0, 0) |
|
bbox = draw.textbbox(text_position, text, font=font, align='center') |
|
text_width = bbox[2] - bbox[0] |
|
text_height = bbox[3] - bbox[1] |
|
text_left = (frame_size - text_width) // 2 |
|
text_top = int(3/4 * frame_size + 1/4 * im_size - 1/2 * text_height) |
|
text_position = (text_left, text_top) |
|
|
|
|
|
draw.text(text_position, text, font=font, fill=fill, align='center') |
|
return image |
|
|
|
|
|
def easeInOutQuint(x): |
|
|
|
|
|
if x < 0.5: |
|
return 4 * x**3 |
|
else: |
|
return 1 - (-2 * x + 2)**3 / 2 |
|
|
|
|
|
def animate_two_view( |
|
im_path, |
|
view, |
|
prompt_1, |
|
prompt_2, |
|
save_video_path='tmp.mp4', |
|
hold_duration=60, |
|
text_fade_duration=10, |
|
transition_duration=80, |
|
im_size=256, |
|
frame_size=384, |
|
): |
|
''' |
|
TODO: Assuming two views, first one is identity |
|
''' |
|
im = Image.open(im_path) |
|
|
|
|
|
frames = [] |
|
|
|
|
|
frame_1 = view.make_frame(im, 0.0) |
|
frame_2 = view.make_frame(im, 1.0) |
|
|
|
|
|
frame_1_text = draw_text(frame_1, |
|
prompt_1, |
|
frame_size=frame_size, |
|
im_size=im_size) |
|
frames += [frame_1_text] * (hold_duration // 2) |
|
|
|
|
|
for t in np.linspace(0,1,text_fade_duration): |
|
c = int(t * 255) |
|
fill = (c,c,c) |
|
frame = draw_text(frame_1, |
|
prompt_1, |
|
fill=fill, |
|
frame_size=frame_size, |
|
im_size=im_size) |
|
frames.append(frame) |
|
|
|
|
|
for t in tqdm(np.linspace(0,1,transition_duration)): |
|
t_ease = easeInOutQuint(t) |
|
frames.append(view.make_frame(im, t_ease)) |
|
|
|
|
|
for t in np.linspace(1,0,text_fade_duration): |
|
c = int(t * 255) |
|
fill = (c,c,c) |
|
frame = draw_text(frame_2, |
|
prompt_2, |
|
fill=fill, |
|
frame_size=frame_size, |
|
im_size=im_size) |
|
frames.append(frame) |
|
|
|
|
|
frame_2_text = draw_text(frame_2, |
|
prompt_2, |
|
frame_size=frame_size, |
|
im_size=im_size) |
|
frames += [frame_2_text] * (hold_duration // 2) |
|
|
|
|
|
frames = frames + frames[::-1] |
|
|
|
|
|
frames = frames[-hold_duration//2:] + frames[:-hold_duration//2] |
|
images = frames |
|
|
|
|
|
|
|
image_array = [imageio.core.asarray(frame) for frame in frames] |
|
|
|
|
|
print('Making video...') |
|
imageio.mimsave(save_video_path, image_array, fps=30) |
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
import argparse |
|
import pickle |
|
from pathlib import Path |
|
|
|
parser = argparse.ArgumentParser() |
|
parser.add_argument("--im_path", required=True, type=str, help='Path to the illusion to animate') |
|
parser.add_argument("--save_video_path", default=None, type=str, |
|
help='Path to save video to. If None, defaults to `im_path`, with extension `.mp4`') |
|
parser.add_argument("--metadata_path", default=None, type=str, help='Path to metadata. If specified, overrides `view` and `prompt` args') |
|
parser.add_argument("--view", default=None, type=str, help='Name of view to use') |
|
parser.add_argument("--prompt_1", default='', nargs='+', type=str, |
|
help='Prompt for first view. Passing multiple will join them with newlines.') |
|
parser.add_argument("--prompt_2", default='', nargs='+', type=str, |
|
help='Prompt for first view. Passing multiple will join them with newlines.') |
|
args = parser.parse_args() |
|
|
|
|
|
|
|
im_path = Path(args.im_path) |
|
|
|
|
|
if args.save_video_path is None: |
|
save_video_path = im_path.with_suffix('.mp4') |
|
|
|
if args.metadata_path is None: |
|
|
|
prompt_1 = '\n'.join(args.prompt_1) |
|
prompt_2 = '\n'.join(args.prompt_2) |
|
|
|
|
|
view = get_views([args.view])[0] |
|
else: |
|
with open(args.metadata_path, 'rb') as f: |
|
metadata = pickle.load(f) |
|
view = metadata['views'][1] |
|
m_args = metadata['args'] |
|
prompt_1 = f'{m_args.style} {m_args.prompts[0]}'.strip() |
|
prompt_2 = f'{m_args.style} {m_args.prompts[1]}'.strip() |
|
|
|
|
|
|
|
animate_two_view( |
|
im_path, |
|
view, |
|
prompt_1, |
|
prompt_2, |
|
save_video_path=save_video_path, |
|
hold_duration=120, |
|
text_fade_duration=10, |
|
transition_duration=45, |
|
im_size=256, |
|
frame_size=384, |
|
) |