callum-canavan commited on
Commit
381e596
·
1 Parent(s): a65ed45

Add animation file

Browse files
Files changed (1) hide show
  1. animate.py +180 -0
animate.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from tqdm import tqdm
2
+ import numpy as np
3
+ from PIL import Image, ImageDraw, ImageFont
4
+ import imageio
5
+
6
+ import torchvision.transforms.functional as TF
7
+
8
+ from visual_anagrams.views import get_views
9
+ from visual_anagrams.utils import get_courier_font_path
10
+
11
+
12
+ def draw_text(image, text, fill=(0,0,0), frame_size=384, im_size=256):
13
+ image = image.copy()
14
+
15
+ # Font info
16
+ font_path = get_courier_font_path()
17
+ font_size = 16
18
+
19
+ # Make PIL objects
20
+ draw = ImageDraw.Draw(image)
21
+ font = ImageFont.truetype(font_path, font_size)
22
+
23
+ # Center text horizontally, and vertically between
24
+ # illusion bottom and frame bottom
25
+ text_position = (0, 0)
26
+ bbox = draw.textbbox(text_position, text, font=font, align='center')
27
+ text_width = bbox[2] - bbox[0]
28
+ text_height = bbox[3] - bbox[1]
29
+ text_left = (frame_size - text_width) // 2
30
+ text_top = int(3/4 * frame_size + 1/4 * im_size - 1/2 * text_height)
31
+ text_position = (text_left, text_top)
32
+
33
+ # Draw text on image
34
+ draw.text(text_position, text, font=font, fill=fill, align='center')
35
+ return image
36
+
37
+
38
+ def easeInOutQuint(x):
39
+ # From Matthew Tancik:
40
+ # https://github.com/tancik/Illusion-Diffusion/blob/main/IllusionDiffusion.ipynb
41
+ if x < 0.5:
42
+ return 4 * x**3
43
+ else:
44
+ return 1 - (-2 * x + 2)**3 / 2
45
+
46
+
47
+ def animate_two_view(
48
+ im_path,
49
+ view,
50
+ prompt_1,
51
+ prompt_2,
52
+ save_video_path='tmp.mp4',
53
+ hold_duration=120,
54
+ text_fade_duration=10,
55
+ transition_duration=60,
56
+ im_size=256,
57
+ frame_size=384,
58
+ ):
59
+ '''
60
+ TODO: Assuming two views, first one is identity
61
+ '''
62
+ im = Image.open(im_path)
63
+
64
+ # Make list of frames
65
+ frames = []
66
+
67
+ # Make frames for two views
68
+ frame_1 = view.make_frame(im, 0.0)
69
+ frame_2 = view.make_frame(im, 1.0)
70
+
71
+ # Display frame 1 with text
72
+ frame_1_text = draw_text(frame_1,
73
+ prompt_1,
74
+ frame_size=frame_size,
75
+ im_size=im_size)
76
+ frames += [frame_1_text] * (hold_duration // 2)
77
+
78
+ # Fade out text 1
79
+ for t in np.linspace(0,1,text_fade_duration):
80
+ c = int(t * 255)
81
+ fill = (c,c,c)
82
+ frame = draw_text(frame_1,
83
+ prompt_1,
84
+ fill=fill,
85
+ frame_size=frame_size,
86
+ im_size=im_size)
87
+ frames.append(frame)
88
+
89
+ # Transition view 1 -> view 2
90
+ for t in tqdm(np.linspace(0,1,transition_duration)):
91
+ t_ease = easeInOutQuint(t)
92
+ frames.append(view.make_frame(im, t_ease))
93
+
94
+ # Fade in text 2
95
+ for t in np.linspace(1,0,text_fade_duration):
96
+ c = int(t * 255)
97
+ fill = (c,c,c)
98
+ frame = draw_text(frame_2,
99
+ prompt_2,
100
+ fill=fill,
101
+ frame_size=frame_size,
102
+ im_size=im_size)
103
+ frames.append(frame)
104
+
105
+ # Display frame 2 with text
106
+ frame_2_text = draw_text(frame_2,
107
+ prompt_2,
108
+ frame_size=frame_size,
109
+ im_size=im_size)
110
+ frames += [frame_2_text] * (hold_duration // 2)
111
+
112
+ # "Boomerang" the clip, so we get back to view 1
113
+ frames = frames + frames[::-1]
114
+
115
+ # Move last bit of clip to front
116
+ frames = frames[-hold_duration//2:] + frames[:-hold_duration//2]
117
+
118
+ # Convert PIL images to numpy arrays
119
+ image_array = [imageio.core.asarray(frame) for frame in frames]
120
+
121
+ # Save as video
122
+ print('Making video...')
123
+ imageio.mimsave(save_video_path, image_array, fps=30)
124
+
125
+
126
+
127
+ if __name__ == '__main__':
128
+ import argparse
129
+ import pickle
130
+ from pathlib import Path
131
+
132
+ parser = argparse.ArgumentParser()
133
+ parser.add_argument("--im_path", required=True, type=str, help='Path to the illusion to animate')
134
+ parser.add_argument("--save_video_path", default=None, type=str,
135
+ help='Path to save video to. If None, defaults to `im_path`, with extension `.mp4`')
136
+ parser.add_argument("--metadata_path", default=None, type=str, help='Path to metadata. If specified, overrides `view` and `prompt` args')
137
+ parser.add_argument("--view", default=None, type=str, help='Name of view to use')
138
+ parser.add_argument("--prompt_1", default='', nargs='+', type=str,
139
+ help='Prompt for first view. Passing multiple will join them with newlines.')
140
+ parser.add_argument("--prompt_2", default='', nargs='+', type=str,
141
+ help='Prompt for first view. Passing multiple will join them with newlines.')
142
+ args = parser.parse_args()
143
+
144
+
145
+ # Load image
146
+ im_path = Path(args.im_path)
147
+
148
+ # Get save dir
149
+ if args.save_video_path is None:
150
+ save_video_path = im_path.with_suffix('.mp4')
151
+
152
+ if args.metadata_path is None:
153
+ # Join prompts with newlines
154
+ prompt_1 = '\n'.join(args.prompt_1)
155
+ prompt_2 = '\n'.join(args.prompt_2)
156
+
157
+ # Get paths and views
158
+ view = get_views([args.view])[0]
159
+ else:
160
+ with open(args.metadata_path, 'rb') as f:
161
+ metadata = pickle.load(f)
162
+ view = metadata['views'][1]
163
+ m_args = metadata['args']
164
+ prompt_1 = f'{m_args.style} {m_args.prompts[0]}'.strip()
165
+ prompt_2 = f'{m_args.style} {m_args.prompts[1]}'.strip()
166
+
167
+
168
+ # Animate
169
+ animate_two_view(
170
+ im_path,
171
+ view,
172
+ prompt_1,
173
+ prompt_2,
174
+ save_video_path=save_video_path,
175
+ hold_duration=120,
176
+ text_fade_duration=10,
177
+ transition_duration=45,
178
+ im_size=256,
179
+ frame_size=384,
180
+ )