Update app.py
Browse files
app.py
CHANGED
|
@@ -72,28 +72,41 @@ def ease_bounce(t):
|
|
| 72 |
else: t-=2.625/2.75; return 7.5625*t*t+.984375
|
| 73 |
|
| 74 |
def ken_burns(pil, duration_sec=6, fps=30, style="premium"):
|
| 75 |
-
TW,TH=720,1280
|
|
|
|
|
|
|
| 76 |
total=duration_sec*fps
|
| 77 |
|
| 78 |
-
# Prepare image
|
| 79 |
img=pil.convert("RGB"); sw,sh=img.size
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
img
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
Y,X=np.ogrid[:TH,:TW]
|
| 89 |
dist=np.sqrt(((X-TW/2)/(TW/2))**2+((Y-TH/2)/(TH/2))**2)
|
| 90 |
-
vmask=np.clip(1.-0.
|
| 91 |
|
|
|
|
| 92 |
SEG=[
|
| 93 |
-
(0.00,0.
|
| 94 |
-
(0.
|
| 95 |
-
(0.
|
| 96 |
-
(0.
|
| 97 |
]
|
| 98 |
|
| 99 |
tmp=tempfile.NamedTemporaryFile(suffix=".mp4",delete=False)
|
|
@@ -107,9 +120,7 @@ def ken_burns(pil, duration_sec=6, fps=30, style="premium"):
|
|
| 107 |
te=ease_cubic((tg-t0)/(t1-t0))
|
| 108 |
zoom=z0+(z1-z0)*te; pan_x=int(px0+(px1-px0)*te); pan_y=int(py0+(py1-py0)*te); break
|
| 109 |
if zoom is None: zoom,pan_x,pan_y=1.,0,0
|
| 110 |
-
|
| 111 |
-
s=(0.20-tg)/0.20*1.8
|
| 112 |
-
pan_x+=int(s*math.sin(i*1.4)); pan_y+=int(s*math.cos(i*1.0))
|
| 113 |
|
| 114 |
cw,ch=int(TW/zoom),int(TH/zoom)
|
| 115 |
ox,oy=BW//2+pan_x,BH//2+pan_y
|
|
|
|
| 72 |
else: t-=2.625/2.75; return 7.5625*t*t+.984375
|
| 73 |
|
| 74 |
def ken_burns(pil, duration_sec=6, fps=30, style="premium"):
|
| 75 |
+
TW,TH=720,1280
|
| 76 |
+
# Small pad — just enough for gentle movement, no aggressive zoom
|
| 77 |
+
pad=60; BW,BH=TW+pad*2,TH+pad*2
|
| 78 |
total=duration_sec*fps
|
| 79 |
|
| 80 |
+
# Prepare image — fit full image, letterbox if needed
|
| 81 |
img=pil.convert("RGB"); sw,sh=img.size
|
| 82 |
+
# Fit entire image inside TH height, pad sides with blurred bg
|
| 83 |
+
scale=TH/sh; nw=int(sw*scale); nh=TH
|
| 84 |
+
if nw>TW: scale=TW/sw; nw=TW; nh=int(sh*scale)
|
| 85 |
+
img_resized=img.resize((nw,nh),Image.LANCZOS)
|
| 86 |
+
# Blurred background fill
|
| 87 |
+
bg=img.resize((TW,TH),Image.LANCZOS)
|
| 88 |
+
bg=bg.filter(ImageFilter.GaussianBlur(radius=20))
|
| 89 |
+
bg_arr=np.array(ImageEnhance.Brightness(bg).enhance(0.5))
|
| 90 |
+
canvas=Image.fromarray(bg_arr)
|
| 91 |
+
# Paste sharp image centered
|
| 92 |
+
px=(TW-nw)//2; py=(TH-nh)//2
|
| 93 |
+
canvas.paste(img_resized,(px,py))
|
| 94 |
+
canvas=canvas.filter(ImageFilter.UnsharpMask(radius=0.8,percent=110,threshold=2))
|
| 95 |
+
canvas=ImageEnhance.Contrast(canvas).enhance(1.05)
|
| 96 |
+
canvas=ImageEnhance.Color(canvas).enhance(1.08)
|
| 97 |
+
base=np.array(canvas.resize((BW,BH),Image.LANCZOS))
|
| 98 |
+
|
| 99 |
+
# Pre-baked vignette mask (very subtle)
|
| 100 |
Y,X=np.ogrid[:TH,:TW]
|
| 101 |
dist=np.sqrt(((X-TW/2)/(TW/2))**2+((Y-TH/2)/(TH/2))**2)
|
| 102 |
+
vmask=np.clip(1.-0.22*np.maximum(dist-0.85,0)**2,0,1).astype(np.float32)
|
| 103 |
|
| 104 |
+
# GENTLE zoom: 1.00→1.06 max — full image always visible
|
| 105 |
SEG=[
|
| 106 |
+
(0.00,0.30, 1.00,1.04, 0, -int(pad*.40), 0, -int(pad*.40)),
|
| 107 |
+
(0.30,0.60, 1.04,1.06, -int(pad*.30), int(pad*.30), -int(pad*.40),-int(pad*.70)),
|
| 108 |
+
(0.60,0.80, 1.06,1.04, int(pad*.30), int(pad*.50), -int(pad*.70),-int(pad*.40)),
|
| 109 |
+
(0.80,1.00, 1.04,1.00, int(pad*.50), 0, -int(pad*.40), 0),
|
| 110 |
]
|
| 111 |
|
| 112 |
tmp=tempfile.NamedTemporaryFile(suffix=".mp4",delete=False)
|
|
|
|
| 120 |
te=ease_cubic((tg-t0)/(t1-t0))
|
| 121 |
zoom=z0+(z1-z0)*te; pan_x=int(px0+(px1-px0)*te); pan_y=int(py0+(py1-py0)*te); break
|
| 122 |
if zoom is None: zoom,pan_x,pan_y=1.,0,0
|
| 123 |
+
# No shake — keeps image stable and well-framed
|
|
|
|
|
|
|
| 124 |
|
| 125 |
cw,ch=int(TW/zoom),int(TH/zoom)
|
| 126 |
ox,oy=BW//2+pan_x,BH//2+pan_y
|