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 a packaging mockup to input.mp4 Brand: AURELLE
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 a packaging mockup 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 |
|---|---|---|
BRAND | AURELLE | Name on the front and back panels; its first letter goes on the lid. |
START | 0.8 | Second the flat artwork appears. |
FOLD | (1.3, 2.3) | Seconds the panels fold into a box. |
TURN | (2.5, 5.5) | Seconds the box spins one full turn. |
REST_YAW | -28 | Degrees the box is turned at rest, to show a side. |
TILT | 20 | Degrees the camera looks down on the box. |
BOX_W, BOX_H | 0.32, 0.72 | Panel width and box height as shares of the frame width. |
YELLOW, NAVY, CREAM | (250, 176, 0), (28, 34, 78), (246, 240, 226) | Carton colors: front and lid, sides and text, back. |
BLUR, DIM | 14, 0.6 | Background blur radius and brightness behind the box. |
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
Make a packaging mockup on input.mp4 (540x960, 30fps, 6 seconds). Draw the artwork for a square carton with Pillow, four panels each 173 pixels wide (32% of the frame width) and 389 pixels tall (72% of the width), plus a 173x173 lid: a yellow (250, 176, 0) front reading "AURELLE" near the top in bold navy (28, 34, 78), a white citrus slice (navy outline, 8 yellow spokes) in the middle and "VITAMIN C", "BRIGHTENING SERUM" and "30 ml" below; a navy right side with "20%" in yellow over "VITAMIN C" and "+ HYALURONIC" in white; a cream (246, 240, 226) back reading "HOW TO USE", "3 drops", "every morning" and a small "AURELLE" in navy; a navy left side with "GLOW" in yellow over "IN 14 DAYS" in white; a yellow lid with a navy "A"; the inside of the cardboard kraft brown (190, 150, 102). Leave the clip untouched until 0.8 seconds. From 0.8 to 1.3 seconds blur the clip (Gaussian, radius rising to 14) and darken it to 60% brightness, and fade in the artwork as a flat dieline centered in the frame, in the order left side, front, right side, back, with the lid standing up off the top of the back panel, growing from 62% to 72% of full size. From 1.3 to 2.3 seconds fold the panels back along their shared edges into a closed box (side panels at 90 degrees to the front), growing to full size and turning to -28 degrees, and from 2.0 to 2.5 seconds fold the lid down over the top. From 2.5 to 5.5 seconds spin the box one full turn (360 degrees) around its vertical axis, so the back faces the camera around 4.1 seconds, then hold at -28 degrees with the front and right side showing. Center the box at 50% across and 50% down, look down on it from 20 degrees above with a slight perspective, light the panels facing the upper left brighter, and draw a soft blurred black ellipse shadow under it. Use cubic ease-in-out for every move. Use Python with Pillow and numpy for the drawing and 3D projection (draw all text with Pillow, not ffmpeg's drawtext) and ffmpeg to read and write the video, keep the audio, and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as packaging-mockup.py and run:
python3 packaging-mockup.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Packaging mockup: a flat carton dieline appears over the blurred clip, folds into a box and turns a full circle.
Usage: python3 packaging-mockup.py input.mp4 output.mp4
Needs ffmpeg, Pillow and numpy (pip install pillow numpy).
"""
import json
import math
import subprocess
import sys
from pathlib import Path
import numpy as np
from PIL import Image, ImageDraw, ImageFilter, ImageFont
BRAND = "AURELLE" # name on the front panel
START = 0.8 # second the flat artwork appears
FOLD = (1.3, 2.3) # seconds the side panels fold back into a box
LID = (2.0, 2.5) # seconds the top flap folds shut
TURN = (2.5, 5.5) # seconds the box takes to turn a full circle
REST_YAW = -28 # degrees the box is turned at rest, to show a side
TILT = 20 # degrees the camera looks down on the box
BOX_W, BOX_H = 0.32, 0.72 # panel width and box height as shares of the frame width
CENTER = (0.5, 0.50) # box center (0-1 across and down the frame)
YELLOW = (250, 176, 0) # front panel and lid
NAVY = (28, 34, 78) # side panels and text
CREAM = (246, 240, 226) # back panel
KRAFT = (190, 150, 102) # inside of the cardboard
BLUR, DIM = 14, 0.6 # background blur radius and brightness once the artwork is in
SS = 2 # supersampling for smooth box edges
FONTS = ["/System/Library/Fonts/Supplemental/Arial Bold.ttf", # macOS
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", # Linux
"C:/Windows/Fonts/arialbd.ttf"] # Windows
FONT_FILE = next((f for f in FONTS if Path(f).exists()), None) or sys.exit("No bold font found: add one to FONTS")
def font(size):
return ImageFont.truetype(FONT_FILE, round(size))
def panel(color, lines, height=None):
"""One flat panel of the carton artwork: a color with centered (text, size, y, color) lines."""
img = Image.new("RGB", (pw, height or ph), color)
draw = ImageDraw.Draw(img)
for text, size, y, fill in lines:
draw.text((pw / 2, img.height * y), text, font=font(pw * size), anchor="mm", fill=fill)
return img
def artwork():
front = panel(YELLOW, [(BRAND, 0.17, 0.16, NAVY), ("VITAMIN C", 0.11, 0.70, NAVY),
("BRIGHTENING SERUM", 0.065, 0.78, NAVY), ("30 ml", 0.07, 0.92, NAVY)])
draw = ImageDraw.Draw(front) # a citrus slice in the middle of the front
r, cx, cy = pw * 0.26, pw / 2, ph * 0.43
draw.ellipse([cx - r, cy - r, cx + r, cy + r], fill="white", outline=NAVY, width=round(pw * 0.025))
for k in range(8):
a = math.radians(k * 45)
draw.line([cx, cy, cx + r * 0.9 * math.cos(a), cy + r * 0.9 * math.sin(a)], fill=YELLOW, width=round(pw * 0.03))
return {
"front": front,
"right": panel(NAVY, [("20%", 0.22, 0.40, YELLOW), ("VITAMIN C", 0.10, 0.50, "white"),
("+ HYALURONIC", 0.075, 0.60, "white")]),
"back": panel(CREAM, [("HOW TO USE", 0.11, 0.30, NAVY), ("3 drops", 0.09, 0.45, NAVY),
("every morning", 0.08, 0.53, NAVY), (BRAND, 0.08, 0.88, NAVY)]),
"left": panel(NAVY, [("GLOW", 0.20, 0.42, YELLOW), ("IN 14 DAYS", 0.10, 0.52, "white")]),
"lid": panel(YELLOW, [(BRAND[0], 0.40, 0.5, NAVY)], height=pw),
"inside": Image.new("RGB", (pw, ph), KRAFT),
}
def ease(p):
p = min(max(p, 0), 1)
return 4 * p ** 3 if p < 0.5 else 1 - (-2 * p + 2) ** 3 / 2
def progress(t, span):
return ease((t - span[0]) / (span[1] - span[0]))
def faces(fold, lid):
"""Each panel's four corners (top-left, top-right, bottom-right, bottom-left of its artwork) in 3D,
for a fold angle in radians: 0 is the flat dieline, pi/2 a closed box. x right, y up, z to the camera."""
W, H = BOX_W * w, BOX_H * w
z0 = W / 2 # the front sits half a box in front of the turning axis
shift = np.array([-W / 2 * math.cos(fold), 0, 0]) # keeps the flat dieline centered
right_dir = np.array([math.cos(fold), 0, -math.sin(fold)])
back_dir = np.array([math.cos(2 * fold), 0, -math.sin(2 * fold)])
left_dir = np.array([-math.cos(fold), 0, -math.sin(fold)])
up = np.array([0, H / 2, 0])
fl, fr = np.array([-W / 2, 0, z0]) + shift, np.array([W / 2, 0, z0]) + shift
rr = fr + W * right_dir
br = rr + W * back_dir
ll = fl + W * left_dir
def quad(a, b): # a vertical panel from edge a to edge b
return [a + up, b + up, b - up, a - up]
back = quad(rr, br)
lid_dir = np.array([0, math.cos(lid), 0]) + math.sin(lid) * np.cross([0, 1, 0], back_dir) # up, then over the top
return {"left": quad(ll, fl), "front": quad(fl, fr), "right": quad(fr, rr), "back": back,
"lid": [back[0] + W * lid_dir, back[1] + W * lid_dir, back[1], back[0]]}
def turn(p, yaw):
x, y, z = p
return np.array([x * math.cos(yaw) + z * math.sin(yaw), y, -x * math.sin(yaw) + z * math.cos(yaw)])
def project(points, yaw, scale):
"""Turn the box, tilt the camera and project to (SS times bigger) frame pixels."""
ct, st = math.cos(math.radians(TILT)), math.sin(math.radians(TILT))
focal = 2.4 * w
out = []
for p in points:
x, y, z = turn(p, yaw)
y, z = y * ct - z * st, y * st + z * ct
k = focal / (focal - z * scale) * scale
out.append(((CENTER[0] * w + x * k) * SS, (CENTER[1] * h - y * k) * SS, z))
return out
def perspective(dst, size):
"""Coefficients for Image.transform that pull a size[0] x size[1] image onto the four dst corners."""
src = [(0, 0), (size[0], 0), (size[0], size[1]), (0, size[1])]
rows, rhs = [], []
for (x, y), (u, v) in zip(dst, src):
rows += [[x, y, 1, 0, 0, 0, -u * x, -u * y], [0, 0, 0, x, y, 1, -v * x, -v * y]]
rhs += [u, v]
return np.linalg.solve(np.array(rows, float), np.array(rhs, float))
def box_layer(t):
"""The carton (and its shadow) at time t, as a frame-sized RGBA image."""
appear = progress(t, (START, FOLD[0]))
fold = math.pi / 2 * progress(t, FOLD)
yaw = math.radians(REST_YAW * progress(t, FOLD) + 360 * progress(t, TURN))
scale = 0.62 + 0.10 * appear + 0.28 * progress(t, FOLD)
layer = Image.new("RGBA", (w * SS, h * SS))
light = np.array([-0.5, 0.5, 1.0]) / np.linalg.norm([-0.5, 0.5, 1.0])
drawn = []
for name, quad in faces(fold, math.pi / 2 * progress(t, LID)).items():
pts = project(quad, yaw, scale)
a, b, c = (np.array(p[:2]) for p in pts[:3])
facing = (b - a)[0] * (c - b)[1] - (b - a)[1] * (c - b)[0] > 0 # corners run clockwise on screen
normal = turn(np.cross(quad[1] - quad[0], quad[3] - quad[0]), yaw)
shade = 0.62 + 0.38 * abs(float(normal @ light)) / np.linalg.norm(normal) # panels facing the light are brighter
drawn.append((sum(p[2] for p in pts) / 4, name if facing else "inside", pts, shade))
for _, name, pts, shade in sorted(drawn, key=lambda d: d[0]):
tex = Image.eval(art[name], lambda v: v * shade).convert("RGBA")
corners = [p[:2] for p in pts]
x0, y0 = (math.floor(min(c[i] for c in corners)) for i in (0, 1))
x1, y1 = (math.ceil(max(c[i] for c in corners)) for i in (0, 1))
local = [(x - x0, y - y0) for x, y in corners]
piece = tex.transform((x1 - x0, y1 - y0), Image.PERSPECTIVE, tuple(perspective(local, tex.size)), Image.BICUBIC)
mask = Image.new("L", piece.size)
ImageDraw.Draw(mask).polygon(local, fill=round(255 * appear))
layer.paste(piece, (x0, y0), mask)
layer = layer.resize((w, h), Image.LANCZOS)
shadow = Image.new("RGBA", (w, h)) # a soft shadow on the floor under the box
floor_y = CENTER[1] * h + BOX_H * w / 2 * scale
half = BOX_W * w * scale * (1.3 - 0.6 * progress(t, FOLD))
ImageDraw.Draw(shadow).ellipse([w / 2 - half, floor_y - 10, w / 2 + half, floor_y + 30], fill=(0, 0, 0, round(170 * appear)))
shadow = shadow.filter(ImageFilter.GaussianBlur(12))
shadow.alpha_composite(layer)
return shadow
def background(img, t):
p = progress(t, (START, FOLD[0]))
if p <= 0:
return img
img = img.filter(ImageFilter.GaussianBlur(BLUR * p))
return Image.eval(img, lambda v: v * (1 - (1 - DIM) * p))
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)
def render(src, out):
"""Blur the background and draw the box on every frame of src, keeping the audio."""
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 raw := reader.stdout.read(w * h * 3):
t = i / fps
frame = background(Image.frombytes("RGB", (w, h), raw), t).convert("RGBA")
if t >= START:
frame.alpha_composite(box_layer(t))
writer.stdin.write(frame.convert("RGB").tobytes())
i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
sys.exit("ffmpeg failed")
src, out = sys.argv[1:3]
w, h, rate, fps = probe(src)
pw, ph = round(BOX_W * w * SS), round(BOX_H * w * SS)
art = artwork()
render(src, out)





