Get this effect
Switch to Code in your Claude Desktop App
Install the skill (once)
Show the prompt
Install the Recharm video effects skill: download https://recharm-growth-staging.s3.us-east-2.amazonaws.com/website/downloads/recharm-video-effects.zip and unzip it into ~/.claude/skillsAsk for the effect
Add an instant photo frame to input.mp4 Second the video starts to shrink into the photo: 0.5 Seconds the shrink and tilt take: 0.7
Several videos, several effects, one ask
Put your clips in the folder, mention them with @, and ask for everything at once. Claude runs one tested script after another on each clip, then joins them.
Add an instant photo frame to @clip1.mp4, put a sale badge on @clip2.mp4, give @clip3.mp4 a VHS look, then join all three with crossfades.
Settings you can change
Ask Claude to change any of these, or edit them at the top of the script.
| Setting | Default | What it does |
|---|---|---|
START | 0.5 | Second the video starts to shrink into the photo. |
SETTLE | 0.7 | Seconds the shrink and tilt take. |
WIDTH | 0.78 | Photo width, border included, as a share of the frame width. |
BORDER, BOTTOM | 0.055, 4 | Side and top border as a share of the photo width, and the bottom margin as a multiple of it. |
TILT | -3 | Degrees of tilt (negative leans clockwise). |
PAPER, BACKDROP | (250, 248, 242), (198, 172, 138) | Border and table colors. |
SHADOW | 0.5 | Shadow strength (0-1). |
Without the skill
Don’t want to install the skill? Paste this prompt into Claude Code and Claude writes the script itself.
Show the prompt
Turn input.mp4 into an instant photo. Play it full screen until 0.5 seconds, then over 0.7 seconds (ease-out) shrink it into an instant-photo print: the video inside a warm white (250, 248, 242) border that is 5.5% of the print's width on the top, left and right and 4 times that at the bottom. The print ends up 78% of the frame width across (border included), its center 50% across and 49% down, tilted 3 degrees clockwise (the tilt grows with the shrink), on a solid (198, 172, 138) kraft-brown backdrop. Give it a soft black drop shadow at 50% opacity, blurred by 2.5% of the frame width and shifted down by 2% of the frame width. Hold it to the end. Use Python with Pillow for the border, tilt and shadow (draw anything with Pillow, not ffmpeg's drawtext) and ffmpeg to read and write the video, keep the audio and the original size, and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as instant-photo-frame.py and run:
python3 instant-photo-frame.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Instant photo frame: the video settles into a tilted white instant-photo border with a deep bottom margin.
Usage: python3 instant-photo-frame.py input.mp4 output.mp4
Needs ffmpeg and Pillow (pip install pillow).
"""
import json
import subprocess
import sys
from PIL import Image, ImageDraw, ImageFilter
START = 0.5 # second the video starts to shrink into the photo
SETTLE = 0.7 # seconds the shrink and tilt take
WIDTH = 0.78 # photo width (border included) as a share of the frame width
BORDER = 0.055 # side and top border as a share of the photo width
BOTTOM = 4 # bottom margin as a multiple of the side border
X, Y = 0.5, 0.49 # where the photo's center ends up (0-1 across and down the frame)
TILT = -3 # degrees (negative leans clockwise)
PAPER = (250, 248, 242) # border color (warm white)
BACKDROP = (198, 172, 138) # table color behind the photo (kraft brown)
SHADOW = 0.5 # shadow strength (0-1)
SHADOW_BLUR = 0.025 # shadow softness as a share of the frame width
SHADOW_DROP = 0.02 # how far the shadow falls below the photo, as a share of the frame width
def ease_out_cubic(p):
p = min(max(p, 0), 1)
return 1 - (1 - p) ** 3
def photo_frame(video, t):
"""The frame at time t: the video inside its paper border, scaled, tilted and dropped onto the backdrop."""
p = ease_out_cubic((t - START) / SETTLE)
if p == 0:
return video
# the photo at full size: the video is the picture, the paper is built around it
side = round(w * BORDER / (1 - 2 * BORDER))
card = Image.new("RGBA", (w + 2 * side, h + side + BOTTOM * side), PAPER)
card.paste(video, (side, side))
# start with the picture filling the frame, end at WIDTH across, centered on X, Y
end_scale = WIDTH * w / card.width
scale = 1 + (end_scale - 1) * p
start_x, start_y = w / 2, h / 2 + (card.height / 2 - side - h / 2)
cx = start_x + (w * X - start_x) * p
cy = start_y + (h * Y - start_y) * p
card = card.resize((round(card.width * scale), round(card.height * scale)), Image.LANCZOS)
card = card.rotate(TILT * p, resample=Image.BICUBIC, expand=True)
pos = (round(cx - card.width / 2), round(cy - card.height / 2))
shadow = Image.new("L", (w, h))
drop = round(w * SHADOW_DROP * p)
shadow.paste(card.getchannel("A").point(lambda a: round(a * SHADOW * p)), (pos[0], pos[1] + drop))
shadow = shadow.filter(ImageFilter.GaussianBlur(w * SHADOW_BLUR))
img = Image.new("RGB", (w, h), BACKDROP)
img.paste((0, 0, 0), (0, 0), shadow)
img.paste(card, pos, card)
return img
def probe(path):
info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
"stream=width,height,r_frame_rate", "-of", "json", path]))
s = info["streams"][0]
num, den = s["r_frame_rate"].split("/")
return s["width"], s["height"], s["r_frame_rate"], float(num) / float(den)
src, out = sys.argv[1:3]
w, h, rate, fps = probe(src)
reader = subprocess.Popen(["ffmpeg", "-v", "error", "-i", src, "-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
stdout=subprocess.PIPE)
writer = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}",
"-r", rate, "-i", "-", "-i", src, "-map", "0:v", "-map", "1:a?",
"-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p", "-c:a", "copy", "-shortest", out],
stdin=subprocess.PIPE)
i = 0
while chunk := reader.stdout.read(w * h * 3):
writer.stdin.write(photo_frame(Image.frombytes("RGB", (w, h), chunk), i / fps).tobytes())
i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
sys.exit("ffmpeg failed")





