Spaces:
Sleeping
Sleeping
File size: 7,336 Bytes
cf1a3cd | 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 | """
Animation Builder Module
Converts frame sequences into GIF/MP4 animations.
"""
from PIL import Image
from typing import List, Optional
import imageio
import os
from pathlib import Path
class AnimationBuilder:
"""
Builds animations (GIF/MP4) from frame sequences.
"""
def __init__(self, output_dir: str = "outputs"):
"""
Initialize animation builder.
Args:
output_dir: Directory to save animations
"""
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
print(f"Output directory: {self.output_dir.absolute()}")
def save_frames(
self,
frames: List[Image.Image],
prefix: str = "frame",
subfolder: Optional[str] = None
) -> List[Path]:
"""
Save individual frames as PNG files.
Args:
frames: List of PIL Images
prefix: Filename prefix
subfolder: Optional subfolder name
Returns:
List of saved file paths
"""
if subfolder:
save_dir = self.output_dir / subfolder
else:
save_dir = self.output_dir
save_dir.mkdir(parents=True, exist_ok=True)
saved_paths = []
for i, frame in enumerate(frames):
filename = f"{prefix}_{i:04d}.png"
filepath = save_dir / filename
frame.save(filepath)
saved_paths.append(filepath)
print(f"Saved {len(frames)} frames to {save_dir}")
return saved_paths
def create_gif(
self,
frames: List[Image.Image],
output_filename: str = "animation.gif",
fps: int = 8,
loop: int = 0,
optimize: bool = True
) -> Path:
"""
Create GIF animation from frames.
Args:
frames: List of PIL Images
output_filename: Output GIF filename
fps: Frames per second
loop: Number of loops (0 = infinite)
optimize: Optimize GIF size
Returns:
Path to saved GIF
"""
output_path = self.output_dir / output_filename
# Calculate duration per frame in milliseconds
duration_ms = int(1000 / fps)
# Save as GIF
frames[0].save(
output_path,
save_all=True,
append_images=frames[1:],
duration=duration_ms,
loop=loop,
optimize=optimize
)
file_size_mb = output_path.stat().st_size / (1024 * 1024)
print(f"GIF saved: {output_path} ({file_size_mb:.2f} MB)")
return output_path
def create_mp4(
self,
frames: List[Image.Image],
output_filename: str = "animation.mp4",
fps: int = 8,
quality: int = 8
) -> Path:
"""
Create MP4 video from frames.
Args:
frames: List of PIL Images
output_filename: Output MP4 filename
fps: Frames per second
quality: Video quality (1-10, 10 is best)
Returns:
Path to saved MP4
"""
output_path = self.output_dir / output_filename
# Convert PIL Images to numpy arrays
frame_arrays = [imageio.core.util.Array(frame) for frame in frames]
# Write video
imageio.mimsave(
output_path,
frame_arrays,
fps=fps,
quality=quality,
codec='libx264',
pixelformat='yuv420p'
)
file_size_mb = output_path.stat().st_size / (1024 * 1024)
print(f"MP4 saved: {output_path} ({file_size_mb:.2f} MB)")
return output_path
def create_side_by_side_comparison(
self,
frames_a: List[Image.Image],
frames_b: List[Image.Image],
output_filename: str = "comparison.gif",
fps: int = 8,
labels: Optional[tuple] = None
) -> Path:
"""
Create side-by-side comparison animation.
Args:
frames_a: First frame sequence
frames_b: Second frame sequence
output_filename: Output filename
fps: Frames per second
labels: Optional tuple of labels for each sequence
Returns:
Path to saved animation
"""
combined_frames = []
for frame_a, frame_b in zip(frames_a, frames_b):
# Ensure same height
height = max(frame_a.height, frame_b.height)
frame_a = frame_a.resize((int(frame_a.width * height / frame_a.height), height))
frame_b = frame_b.resize((int(frame_b.width * height / frame_b.height), height))
# Combine horizontally
combined = Image.new('RGB', (frame_a.width + frame_b.width, height))
combined.paste(frame_a, (0, 0))
combined.paste(frame_b, (frame_a.width, 0))
combined_frames.append(combined)
return self.create_gif(combined_frames, output_filename, fps)
def create_grid_animation(
self,
frame_sequences: List[List[Image.Image]],
output_filename: str = "grid.gif",
fps: int = 8,
grid_size: Optional[tuple] = None
) -> Path:
"""
Create grid animation showing multiple sequences.
Args:
frame_sequences: List of frame sequences
output_filename: Output filename
fps: Frames per second
grid_size: Optional (rows, cols) for grid layout
Returns:
Path to saved animation
"""
num_sequences = len(frame_sequences)
if grid_size is None:
# Auto-calculate grid size
cols = int(num_sequences ** 0.5)
rows = (num_sequences + cols - 1) // cols
else:
rows, cols = grid_size
# Get frame count (use minimum)
num_frames = min(len(seq) for seq in frame_sequences)
grid_frames = []
for frame_idx in range(num_frames):
# Get all frames at this index
current_frames = [seq[frame_idx] for seq in frame_sequences]
# Resize all to same size
target_size = (256, 256)
current_frames = [f.resize(target_size) for f in current_frames]
# Create grid
grid_width = cols * target_size[0]
grid_height = rows * target_size[1]
grid_image = Image.new('RGB', (grid_width, grid_height))
for i, frame in enumerate(current_frames):
row = i // cols
col = i % cols
x = col * target_size[0]
y = row * target_size[1]
grid_image.paste(frame, (x, y))
grid_frames.append(grid_image)
return self.create_gif(grid_frames, output_filename, fps)
if __name__ == "__main__":
print("Animation builder module loaded.")
print("Use to convert frame sequences into GIF/MP4 animations.")
|