| 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, |
| ) |