from __future__ import annotations from PIL import Image from .config import MotionPreset from .motion import apply_camera_motion def interpolate_frames(keyframes: list[Image.Image], *, total_frames: int, motion: MotionPreset) -> list[Image.Image]: if not keyframes: raise ValueError("At least one keyframe is required.") total_frames = max(2, int(total_frames)) if len(keyframes) == 1: return [apply_camera_motion(keyframes[0], t=i / max(1, total_frames - 1), motion=motion) for i in range(total_frames)] frames: list[Image.Image] = [] spans = len(keyframes) - 1 for frame_idx in range(total_frames): global_t = frame_idx / max(1, total_frames - 1) pos = global_t * spans left = min(spans - 1, int(pos)) local_t = pos - left eased_t = _smoothstep(local_t) blended = Image.blend(keyframes[left].convert("RGB"), keyframes[left + 1].convert("RGB"), eased_t) frames.append(apply_camera_motion(blended, t=global_t, motion=motion)) return frames def _smoothstep(t: float) -> float: t = max(0.0, min(1.0, float(t))) return t * t * (3.0 - 2.0 * t)