Phillnet-2 / VideoGen /motion.py
ayjays132's picture
Upload 478 files
101858b verified
from __future__ import annotations
from PIL import Image
from .config import MotionPreset
def apply_camera_motion(frame: Image.Image, *, t: float, motion: MotionPreset) -> Image.Image:
img = frame.convert("RGB")
w, h = img.size
t = max(0.0, min(1.0, float(t)))
if motion in {"push_in", "zoom"}:
scale = 1.0 + 0.055 * t
return _center_crop_zoom(img, scale)
if motion == "pull_out":
scale = 1.055 - 0.055 * t
return _center_crop_zoom(img, scale)
if motion == "pan_left":
return _translate(img, dx=int((t - 0.5) * w * 0.06), dy=0)
if motion == "pan_right":
return _translate(img, dx=int((0.5 - t) * w * 0.06), dy=0)
if motion == "slow_orbit":
return _center_crop_zoom(img.rotate((t - 0.5) * 2.5, resample=Image.Resampling.BICUBIC), 1.02)
if motion == "ken_burns":
moved = _translate(img, dx=int((0.5 - t) * w * 0.035), dy=int((t - 0.5) * h * 0.025))
return _center_crop_zoom(moved, 1.015 + 0.04 * t)
if motion == "product_turntable_fake":
moved = _translate(img, dx=int((0.5 - t) * w * 0.025), dy=0)
return _center_crop_zoom(moved, 1.015 + 0.015 * (1.0 - abs(0.5 - t) * 2.0))
if motion == "handheld_subtle":
dx = int(w * 0.008 * _wave(t, 1.0))
dy = int(h * 0.006 * _wave(t, 1.7))
return _center_crop_zoom(_translate(img, dx=dx, dy=dy), 1.015)
return img
def _center_crop_zoom(img: Image.Image, scale: float) -> Image.Image:
w, h = img.size
scale = max(1.0, float(scale))
nw, nh = max(1, int(w / scale)), max(1, int(h / scale))
left = max(0, (w - nw) // 2)
top = max(0, (h - nh) // 2)
return img.crop((left, top, left + nw, top + nh)).resize((w, h), Image.Resampling.BICUBIC)
def _translate(img: Image.Image, *, dx: int, dy: int) -> Image.Image:
w, h = img.size
shifted = img.transform((w, h), Image.Transform.AFFINE, (1, 0, dx, 0, 1, dy), resample=Image.Resampling.BICUBIC)
if dx == 0 and dy == 0:
return shifted
return _center_crop_zoom(shifted, 1.01)
def _wave(t: float, phase: float) -> float:
import math
return math.sin((float(t) + phase) * math.tau)